class CountingInputStream extends BufferedInputStream {
private long bytesReadMark = 0; //用于存储文件位置标识
private long bytesRead = 0; //当前读取文件字节数
//构造一个BufferedInputStream,缓存大小为size
public CountingInputStream(InputStream in, int size) {
super(in, size);
}
//构造一个BufferedInputStream,缓存大小为8192bytes
public CountingInputStream(InputStream in) {
super(in);
}
//获得当前读取文件的字节数
public long getBytesRead() {
return bytesRead;
}
//每次读取一个字节,并将读取字节数加1
//使用synchronized关键字,使该函数同时只能被同一个实例调用一次
public synchronized int read() throws IOException {
int read = super.read();
if (read >= 0) {
bytesRead++;
}
return read;
}
//读取不超过len长度的字节,存储在buffer b中从偏移量为off开始的位置,
//read为实际读取到的字节数,并将当前读取本身数加上read
public synchronized int read(byte[] b, int off, int len) throws IOException {
int read = super.read(b, off, len);
if (read >= 0) {
bytesRead += read;
}
return read;
}
//跳过最多n字节,返回实际跳过的字节数,并将当前字节数加skipped
public synchronized long skip(long n) throws IOException {
long skipped = super.skip(n);
if (skipped >= 0) {
bytesRead += skipped;
}
return skipped;
}
//将当前位置保存到bytesReadMark这个变量中
//若读取偏移量超过readlimit,则该mark的位置失效
public synchronized void mark(int readlimit) {
super.mark(readlimit);
bytesReadMark = bytesRead;
}
//将当前文件位置恢复到bytesReadMark所指的位置
//若是文件关闭,或者没有mark一个位置,或者以前mark的位置已经失效,将会抛出一个IOException
public synchronized void reset() throws IOException {
super.reset();
bytesRead = bytesReadMark;
}
}
//数据文件下载,主要是负责解压缩data.zip文件
class DataDownloader extends Thread
{
public StatusWriter Status;//用于显示进度,额外信息等
public boolean DownloadComplete = false;
public boolean DownloadFailed = false;
private MainActivity Parent;//用于传递主界面类,以进行界面相关操做
private String outFilesDir = null;//输出文件目录
class StatusWriter
{
private ProgressDialog Status; //用于显示信息
private MainActivity Parent;//用于与主界面交互
private String oldText = "";
//实例化成员变量,将主界面元素传递过来,方便进行更新
public StatusWriter( ProgressDialog _Status, MainActivity _Parent )
{
Status = _Status;
Parent = _Parent;
}
public void setParent( ProgressDialog _Status, MainActivity _Parent )
{
//锁定一个对象,当是同一个DataDownloader实例时是线程同步的
//可是不一样实例仍是不一样步,想要不一样对象也同步,这个对象必须是静态对象
synchronized(DataDownloader.this) {
Status = _Status;
Parent = _Parent;
setText( oldText );//初始化的时候TextView显示空
}
}
public void setText(final String str)
{
//用于更新TextView中的内容
class Callback implements Runnable
{
public ProgressDialog Status;
public String text;
public void run()
{
Status.setMessage(text);
}
}
synchronized(DataDownloader.this) {
Callback cb = new Callback();
oldText = new String(str);
cb.text = new String(str);
cb.Status = Status;
//为了防止程序崩溃,价格判断是值得的
if( Parent != null && Status != null )
Parent.runOnUiThread(cb);//运行在UI线程中,以更新界面元素
}
}
}
//类DataDownloader的构造函数,传入主界面相应的元素,为更新界面信息做准备
public DataDownloader( MainActivity _Parent, ProgressDialog _Status )
{
Parent = _Parent;
Status = new StatusWriter( _Status, _Parent );//构建StatusWriter类,专门用于进行界面的更新操做
//Status.setText( "Connecting to " + Globals.DataDownloadUrl );
outFilesDir = Globals.DataDir;//初始化目标目录的路径
DownloadComplete = false; //初始化DownloadComplete
this.start(); //运行该类的核心函数
}
public void setStatusField(ProgressDialog _Status)
{
synchronized(this) {
Status.setParent( _Status, Parent );
}
}
//核心函数
@Override
public void run()
{
//检查目标目录的文件是否完整和正确,传入压缩文件名和要检查的标识文件名
if( ! DownloadDataFile(Globals.DataDownloadUrl, "DownloadFinished.flag") )
{
DownloadFailed = true;
return;
}
//若是运行到了这里,说明数据是正确的
DownloadComplete = true;
//初始化
initParent();
}
public boolean DownloadDataFile(final String DataDownloadUrl, final String DownloadFlagFileName)
{
//初始化资源实例
Resources res = Parent.getResources();
//检查目标文件是否包含指定的数据
String path = getOutFilePath(DownloadFlagFileName);
InputStream checkFile = null;
try {
checkFile = new FileInputStream( path );
} catch( FileNotFoundException e ) {
} catch( SecurityException e ) { };
if( checkFile != null )
{
try {
//构造一个比标准数据稍大的buffer,用于存储文件数据
byte b[] = new byte[ Globals.DataDownloadUrl.getBytes("UTF-8").length + 1 ];
int readed = checkFile.read(b);
String compare = new String( b, 0, readed, "UTF-8" ); //DataDownloadUrl=data.zip
boolean matched = false;
//若compare=data.zip
if( compare.compareTo(DataDownloadUrl) == 0 )
matched = true;
//若是不匹配,抛出异常,直接跳转到1所在的位置
if( ! matched )
throw new IOException();
Status.setText( res.getString(R.string.download_unneeded) );
return true;
} catch ( IOException e ) {};
}
checkFile = null; //----1
//mkdirs 将会产生全部中间必要的目录
try {
(new File( outFilesDir )).mkdirs();
OutputStream out = new FileOutputStream( getOutFilePath(".nomedia") );
out.flush();
out.close();
}
catch( SecurityException e ) {}
catch( FileNotFoundException e ) {}
catch( IOException e ) {};
long totalLen = 0;
CountingInputStream stream;
byte[] buf = new byte[16384];
String url = DataDownloadUrl;
System.out.println("Unpacking from assets: '" + url + "'");
try {
//打开assets下的文件
stream = new CountingInputStream(Parent.getAssets().open(url), 8192);
//跳到文件末尾以肯定文件大小
while( stream.skip(65536) > 0 ) { };
//将文件大小存储到totalLen
totalLen = stream.getBytesRead();
stream.close();
//从新打开文件,以重置文件当前读取位置
stream = new CountingInputStream(Parent.getAssets().open(url), 8192);
} catch( IOException e ) {
System.out.println("Unpacking from assets '" + url + "' - error: " + e.toString());
Status.setText( res.getString(R.string.error_dl_from, url) );//载入出错
return false;
}
ZipInputStream zip = new ZipInputStream(stream);
while(true)
{
ZipEntry entry = null;
try {
//取得压缩文件的一个个子元素
entry = zip.getNextEntry();
if( entry != null )
System.out.println("Reading from zip file '" + url + "' entry '" + entry.getName() + "'");
} catch( java.io.IOException e ) {
Status.setText( res.getString(R.string.error_dl_from, url) );
System.out.println("Error reading from zip file '" + url + "': " + e.toString());
return false;
}
//若是元素为空,代表压缩文件已经读取完毕
if( entry == null )
{
System.out.println("Reading from zip file '" + url + "' finished");
break;
}
//若是是目录,则建立相应目录结构,接着直接读取下一个元素
if( entry.isDirectory() )
{
System.out.println("Creating dir '" + getOutFilePath(entry.getName()) + "'");
try {
(new File( getOutFilePath(entry.getName()) )).mkdirs();
} catch( SecurityException e ) { };
continue;
}
OutputStream out = null;
path = getOutFilePath(entry.getName());
//安全起见,建立目标文件所须要的目录结构,虽然这里不须要,但之后可能会须要
try {
(new File( path.substring(0, path.lastIndexOf("/") ))).mkdirs();
} catch( SecurityException e ) { };
try {
//使用CRC32校验目标文件
CheckedInputStream check = new CheckedInputStream( new FileInputStream(path), new CRC32() );
while( check.read(buf, 0, buf.length) > 0 ) {};
check.close();
//检查校验和是否正确
if( check.getChecksum().getValue() != entry.getCrc() )
{
File ff = new File(path);
ff.delete();//校验和不正确则删除文件,从新建立文件
throw new Exception(); //跳转到catch
}
System.out.println("File '" + path + "' exists and passed CRC check - not overwriting it");
//若校验成功,则继续校验下一个文件
continue;
} catch( Exception e )
{
}
try {
out = new FileOutputStream( path );
} catch( FileNotFoundException e ) {
System.out.println("Saving file '" + path + "' - cannot create file: " + e.toString());
} catch( SecurityException e ) {
System.out.println("Saving file '" + path + "' - cannot create file: " + e.toString());
};
//若是建立文件失败
if( out == null )
{
Status.setText( res.getString(R.string.error_write, path) );
System.out.println("Saving file '" + path + "' - cannot create file");
return false;
}
//将检查结果以百分比显示到progress dialog中
float percent = 0.0f;
if( totalLen > 0 )
percent = stream.getBytesRead() * 100.0f / totalLen;
Status.setText( res.getString(R.string.dl_progress, percent, path) );
try {
//读取当前元素
int len = zip.read(buf);
while (len >= 0)
{
if(len > 0)
out.write(buf, 0, len);//将当前元素的内容拷贝到目标文件
len = zip.read(buf);
percent = 0.0f;
if( totalLen > 0 )
percent = stream.getBytesRead() * 100.0f / totalLen;
Status.setText( res.getString(R.string.dl_progress, percent, path) );
}
out.flush();
out.close();
out = null;
} catch( java.io.IOException e ) {
Status.setText( res.getString(R.string.error_write, path) );
System.out.println("Saving file '" + path + "' - error writing or downloading: " + e.toString());
return false;
}
try {
//拷贝完以后一样还要校验一下
CheckedInputStream check = new CheckedInputStream( new FileInputStream(path), new CRC32() );
while( check.read(buf, 0, buf.length) > 0 ) {};
check.close();
if( check.getChecksum().getValue() != entry.getCrc() )
{
File ff = new File(path);
ff.delete();
throw new Exception();
}
} catch( Exception e )
{
Status.setText( res.getString(R.string.error_write, path) );
System.out.println("Saving file '" + path + "' - CRC check failed");
return false;
}
System.out.println("Saving file '" + path + "' done");
}
OutputStream out = null;
//所有完成以后将制定信息写入校验文件中
path = getOutFilePath(DownloadFlagFileName);
try {
out = new FileOutputStream( path );
out.write(DataDownloadUrl.getBytes("UTF-8"));
out.flush();
out.close();
} catch( FileNotFoundException e ) {
} catch( SecurityException e ) {
} catch( java.io.IOException e ) {
Status.setText( res.getString(R.string.error_write, path) );
return false;
};
Status.setText( res.getString(R.string.dl_finished) );
try {
stream.close();
} catch( java.io.IOException e ) {
};
return true;
};
//若前面的数据都正确,则执行界面的初始化,将数据文件以列表的形式呈现出来
private void initParent()
{
class Callback implements Runnable
{
public MainActivity Parent;
public void run()
{
Parent.getFileList();//得到目标目录的文件列表
Log.e("guojs","initParent!");
}
}
Callback cb = new Callback();
synchronized(this) {
cb.Parent = Parent;//传入主界面类的实例
if(Parent != null)
Parent.runOnUiThread(cb);//由于须要更新界面,所以须要运行在UI线程
}
}
//返回目标输出文件的绝对路径
private String getOutFilePath(final String filename)
{
return outFilesDir + "/" + filename;
};
}java