package com.letv.commonjar.download; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.sql.Date; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import com.letv.commonjar.CLog; import com.letv.commonjar.download.JDownloadDBHelper.JDownloadTaskColumn; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.net.http.AndroidHttpClient; import android.os.Process; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; public class JDownloadThread extends Thread { private static final String TAG = CLog.makeTag(JDownloadThread.class); static class State { public long id; public String url; public int status; public long totalBytes; public long currentBytes; public String targetFile; public String description; public int mProgress; public long mBytesNotified = 0; public long mTimeLastNotified = 0; public boolean mContinuingDownload; public FileOutputStream outputStream; State(JDownloadInfo info) { this.id = info.mId; this.url = info.mUri; this.targetFile = info.mData; this.currentBytes = info.mCurrentBytes; this.totalBytes = info.mTotalBytes; this.description = info.mDescription; this.status = info.mStatus; } } private Context mCtx; private Date mBeginDate; private JDownloadInfo mInfo; private JDownloadDBUtils mDbUtils; private LocalBroadcastManager mBroadcastManager; private boolean isStop; public JDownloadThread(Context context, JDownloadInfo info) { this.mCtx = context; this.mInfo = info; this.mDbUtils = new JDownloadDBUtils(context); this.mBroadcastManager = LocalBroadcastManager.getInstance(context); } private boolean checkConnectivity() { return true; } /** * 执行断点下载 * * @param state * @param httpGet */ private void handlerContinueDownload(State state, HttpGet httpGet) throws FileNotFoundException { if (TextUtils.isEmpty(state.targetFile)) { throw new FileNotFoundException(); } File target = new File(state.targetFile); File targetParent = new File(target.getParent()); if (!targetParent.exists()) { targetParent.mkdir(); } if (target.exists()) { long targetLength = target.length(); if (targetLength == 0) { target.delete(); state.currentBytes = 0; CLog.d(TAG, "handler continue download, but old content length is 0, so to download new again"); } else { httpGet.setHeader("Range", "bytes=" + targetLength + "-"); state.outputStream = new FileOutputStream(state.targetFile, true); state.currentBytes = targetLength; state.mContinuingDownload = true; CLog.d(TAG, "handlerContinueDownload currentBytes=" + state.currentBytes + " totalBytes=" + state.totalBytes); } } } private void handlerDownloadPrepare(State state, HttpResponse response) throws FileNotFoundException, JDownloadError.PrepareException { int responseCode = response.getStatusLine().getStatusCode(); long contentLength = response.getEntity().getContentLength(); CLog.d(TAG, "processPrepare response code=" + responseCode + " service content length = " + contentLength); if (contentLength <= 0) { throw new JDownloadError.PrepareException("processPrepare file size <= 0"); } // 不支持断点续传 if (state.mContinuingDownload && responseCode != 206) { state.mContinuingDownload = false; if (state.outputStream != null) { try { state.outputStream.close(); state.outputStream = null; } catch (Exception e) {} } CLog.e(TAG, "handlerDownloadPrepare ---> web server is not support 206"); } if (!state.mContinuingDownload) { state.outputStream = new FileOutputStream(state.targetFile); state.currentBytes = 0; state.totalBytes = contentLength; } ContentValues values = new ContentValues(); values.put(JDownloadTaskColumn.STATUS, state.status); values.put(JDownloadTaskColumn.CURRENT_BYTES, state.currentBytes); values.put(JDownloadTaskColumn.TOTAL_BYTES, state.totalBytes); mDbUtils.update(state.id, values); } /** * 按间隔写入database * * @param state */ private void brushDatabase(State state) { long now = System.currentTimeMillis(); if (state.currentBytes - state.mBytesNotified > JConstants.MIN_PROGRESS_STEP // && now - state.mTimeLastNotified > JConstants.MIN_PROGRESS_TIME) { ContentValues values = new ContentValues(); values.put(JDownloadTaskColumn.CURRENT_BYTES, state.currentBytes); mDbUtils.update(state.id, values); state.mBytesNotified = state.currentBytes; } reportProgress(state); } /** * 进度增加>=1, notify一次 * * @param state */ private void reportProgress(State state) { if (state.totalBytes > 0) { int temp = (int) (state.currentBytes * 100 / state.totalBytes); if (temp - state.mProgress >= 1) { state.mProgress = temp; Intent intent = new Intent(JDownloadManager.ACTION_DOWNLOAD_PROGRESS); intent.putExtra(JDownloadManager.COLUMN_DOWNLOAD_ID, state.id); intent.putExtra(JDownloadManager.COLUMN_DESCRIPTION, state.description); intent.putExtra(JDownloadManager.COLUMN_PROGRESS, state.mProgress); mBroadcastManager.sendBroadcast(intent); CLog.d(TAG, "reportProgress task id=" + mInfo.mId + " " + state.mProgress); } } } private void handlerDownloadEnd(State state) { long usetime = new Date(System.currentTimeMillis()).getTime() - mBeginDate.getTime(); ContentValues values = new ContentValues(); values.put(JDownloadTaskColumn.TIME_USE, usetime); values.put(JDownloadTaskColumn.STATUS, state.status); values.put(JDownloadTaskColumn.CURRENT_BYTES, state.currentBytes); mDbUtils.update(mInfo.mId, values); } private void notifyDownloadCompleted(State state) { Intent intent = new Intent(JDownloadManager.ACTION_DOWNLOAD_COMPLETED); intent.putExtra(JDownloadManager.COLUMN_DOWNLOAD_ID, state.id); intent.putExtra(JDownloadManager.COLUMN_DESCRIPTION, state.description); intent.putExtra(JDownloadManager.COLUMN_STATUS, state.status); mBroadcastManager.sendBroadcast(intent); clean(state); } private void clean(State state) { if (state.status != JDownloadManager.STATUS_SUCCESSFUL) { File f = new File(state.targetFile); if (f != null) { f.delete(); } } } private String statusToString(int status, String errorMsg) { StringBuilder sb = new StringBuilder(); switch (status) { case JDownloadManager.STATUS_SUCCESSFUL: sb.append("STATUS_SUCCESSFUL"); break; case JDownloadManager.STATUS_FAILED: sb.append("STATUS_FAILED"); sb.append("---" + errorMsg); break; default: sb.append("STATUS_FAILED"); sb.append("---" + errorMsg); break; } return sb.toString(); } public void breakTask() { isStop = true; } @Override public void run() { CLog.d(TAG, "JDownloadThread run " + mInfo.mId + " " + mInfo.mUri); if (isStop) { return; } Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); mBeginDate = new Date(System.currentTimeMillis()); State state = new State(mInfo); state.status = JDownloadManager.STATUS_RUNNING; StringBuilder sb = new StringBuilder(); HttpGet httpGet = null; InputStream inputStream = null; AndroidHttpClient httpClient = null; try { if (!checkConnectivity()) { throw new JDownloadError.NetTimeOut("net error"); } httpGet = new HttpGet(mInfo.mUri); httpClient = AndroidHttpClient.newInstance(TAG, mCtx); // 处理是否可以断点续传 handlerContinueDownload(state, httpGet); // 获取响应 HttpResponse httpResponse = httpClient.execute(httpGet); // 检测服务器端文件,并更新数据库 handlerDownloadPrepare(state, httpResponse); // 获取文件流 inputStream = httpResponse.getEntity().getContent(); int offset = 0; byte buffer[] = new byte[4096]; while (!isStop && (offset = inputStream.read(buffer)) != -1) { state.outputStream.write(buffer, 0, offset); state.currentBytes += offset; brushDatabase(state); } if (state.currentBytes == state.totalBytes) { state.status = JDownloadManager.STATUS_SUCCESSFUL; handlerDownloadEnd(state); } else { state.status = JDownloadManager.STATUS_FAILED; sb.append("down size not match"); } } catch (JDownloadError.NetTimeOut e) { sb.append("NetTimeOut"); state.status = JDownloadManager.STATUS_FAILED; } catch (FileNotFoundException e) { sb.append("FileNotFoundException"); state.status = JDownloadManager.STATUS_FAILED; } catch (JDownloadError.PrepareException e) { sb.append("PrepareException"); state.status = JDownloadManager.STATUS_FAILED; } catch (IOException e) { sb.append("IOException"); state.status = JDownloadManager.STATUS_FAILED; } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } inputStream = null; } if (state.outputStream != null) { try { state.outputStream.close(); } catch (IOException e) { e.printStackTrace(); } state.outputStream = null; } if (httpClient != null) { httpClient.close(); httpClient = null; } if (httpGet != null) { httpGet.abort(); httpGet = null; } CLog.d(TAG, "run finally state = " + statusToString(state.status, sb.toString())); if (!isStop) { notifyDownloadCompleted(state); } } } }