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