Android 多线程后台下载,查看下载列表/通知栏

原创
2014/08/27 17:17
阅读数 894

先看看下面的几张图片

第一张是任务列表,第二是点击下载之后的页面,第三是在通知栏显示的下载进度。可以查看下载任务,又可以在通知栏显示这里面这里边用到了

数据库(sqlite)。把下载信息保存在数据中,然后通过ContentObserver来监听数据库的变化,在thread里面不断的更新数据库中的下载信息,从而ContentObserver

检测到数据库的变化会调用ContentObserver里面的onChange(boolean selfChange)方法。使用ContentObserver又牵扯到了Android的ContentProvider组件,

ContentObserver和ContentProvider的结合完成了数据库监听动作,为什么要用ContentObserver监听数据库而不是直接开启一个线程不断的读取数据里面的下载信息呢?

这里面的效率就不言而喻了。如果从下载开始开启一个线程不停的读取数据库,那过不了多久手机就可以“煎蛋”了,而且里面的许多细节不好处理。把下载拆分为

三部实现可以这样做:

首先,为下载做前期的准备,方便控制下载过程中的“暂停”、“删除”、“回复”操作,可以建立一些下载的标志例如:

/**
 * Created by huangsm.
 * Email:huangsanm@foxmail.com
 */
public class DownloadStatus {

    // 下载失败
    public static final int STATUS_FAILED = -1;

    // 成功
    public static final int STATUS_SUCCESSFUL = 4;

    //下载中
    public static final int STATUS_RUNNING = 1;

    // 暂停
    public static final int STATUS_PAUSED = 2;

    // 继续
    public static final int STATUS_PENDING = 3;

    //delete
    public static final int STATUS_DELETEED = -2;

}

把下载的相关操作都放到Service里面去执行,Service里面的做的事情很简单,这里Service的生命周期之类就不介绍了,在Service里面的onStartCommand方法里面接受点击

下载按钮传递过来的下载参数【下载地址,名称,等等】。


其次,下载的基本参数都准备好了,这个时候就真正实现在的内容了。把下载的内容放到Thread里面来实现,这个时候你考虑Thread会太多降低性能,我们可以开启线程池来管理

这些Thread

//保存下载内容  key:downloadID
    //private Map<Integer, DownloadItem> mDownloadMap;
    //线程池
    private ExecutorService mExecutor;
    private Context mContext;
    private DownloadManager mDownloadManager;

    @Override
    public void onCreate() {
        mContext = this;
        //mDownloadMap = Collections.synchronizedMap(new HashMap<Integer, DownloadItem>());
        mExecutor = Executors.newCachedThreadPool();
        mDownloadManager = new DownloadManager(getContentResolver());
        super.onCreate();
    }

在下载过程中有一个很巧妙的运用就是下载的临时文件,在开启下载的过程中,可以先判断临时文件是否存在,如果存在则继续下载,如果不存在从头开始。下载文件里面继续

下载的时候主要有涉及到要设置http的header部分和RandomAccessFile类,RandomAccessFile类里面有一个方法可以设置下载的节点。下载过程中先判断临时文件是否存在

如果存在先读取临时文件的大小

if (mTempFile.exists()) {
    mTempSize = (int) mTempFile.length();
    request.addHeader("RANGE", "bytes=" + mTempFile.length() + "-");//下载从这个节点开始
    aClient.close();
    aClient = AndroidHttpClient.newInstance("Linux; Android");
    response = aClient.execute(request);
}
long storage = DownloadUtils.getAvailableStorage();
if (totalSize - mTempSize > storage) {
     notifyNotification(NOTIFICATION_STATUS_FLAG_ERROR, "您的手机内存不足", 0, 0);
     return;
}
RandomAccessFile outStream = new RandomAccessFile(mTempFile, "rwd");
outStream.seek(mTempSize);//文件写入也从这个节点开始写入

然后在读取文件的循环里面判断当前下载是否被暂停、删除等等

InputStream is = response.getEntity().getContent();
            byte[] buffer = new byte[BUFFER_SIZE];
            bufferedInputStream = new BufferedInputStream(is, BUFFER_SIZE);
            int b = 0;
            long updateStart = System.currentTimeMillis();
            long updateDelta = 0;
            int progress = mTempSize;
            while (true) {
                //check download status
                if (checkDownloadCancel()) {
                    notifyNotification(NOTIFICATION_STATUS_FLAG_DELETE, "下载已被取消", 0, 0);
                    aClient.close();
                    break;
                }

                if (checkDownloadPause()) {
                    notifyNotification(NOTIFICATION_STATUS_FLAG_DELETE, "暂停下载", 0, 0);
                    aClient.close();
                    break;
                }

                b = bufferedInputStream.read(buffer, 0, BUFFER_SIZE);
                if (b == -1) {
                    break;
                }
                outStream.write(buffer, 0, b);
                progress += b;

                //1s 更新一次通知栏
                if (updateDelta > NOTIFY_INTERVAL) {
                    Globals.log(progress);
                    notifyNotification(NOTIFICATION_STATUS_FLAG_NORMAL, "", (int) totalSize, progress);
                    updateStart = System.currentTimeMillis();

                    //更新下载进度
                    updateProgress(progress);
                }
                //时间
                updateDelta = System.currentTimeMillis() - updateStart;
            }
            is.close();
            //下载完成
            if (progress == totalSize) {
                notifyNotification(NOTIFICATION_STATUS_FLAG_DONE, "下载完成,点此安装", progress, (int) totalSize);
            }

到这里就完成了下载和写入数据库的操作。


最后,从数据库里面读取下载信息,继承ContentProvider自定义类,来实现数据的操作

public class DownLoadContentProvider extends ContentProvider {

    private static final String AUTHORITY = "com.huashengmi.ui.android.ui.download";

    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + DownloadColumn.TABLE_NAME);

    // 返回集合类型
    private static final String CONTENT_TYPE = "vnd.android.cursor.dir/" + DownloadColumn.TABLE_NAME;
    // 非集合类型
    private static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/" + DownloadColumn.TABLE_NAME;

    private static final int CODE_DOWNLOADS = 2;
    private static final int CODE_DOWNLOAD = 1;

    // 地址匹配
    private static final UriMatcher mUriMatcher = new UriMatcher(
            UriMatcher.NO_MATCH);

    private Context mContext;
    private DownloadDataBase.DatabaseHelper mDBHelper;

    static {
        // 操作整张表
        mUriMatcher.addURI(AUTHORITY, DownloadColumn.TABLE_NAME, CODE_DOWNLOADS);
        // 通过id查询
        mUriMatcher.addURI(AUTHORITY, DownloadColumn.TABLE_NAME + "/#", CODE_DOWNLOAD);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int result = -1;
        int flag = mUriMatcher.match(uri);
        if (flag == CODE_DOWNLOAD) {
            long id = ContentUris.parseId(uri);
            selection = DownloadColumn._ID + "=?";
            selectionArgs = new String[]{id + ""};
            result = mDBHelper.getWritableDatabase().delete(DownloadColumn.TABLE_NAME, selection, selectionArgs);
        } else if (flag == CODE_DOWNLOADS) {
            result = mDBHelper.getWritableDatabase().delete(DownloadColumn.TABLE_NAME, selection, selectionArgs);
        }
        return result;
    }

    @Override
    public String getType(Uri uri) {
        final int matcherItem = mUriMatcher.match(uri);
        if (matcherItem == CODE_DOWNLOAD) {
            return CONTENT_TYPE_ITEM;
        } else if (matcherItem == CODE_DOWNLOADS) {
            return CONTENT_TYPE;
        }
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        long id = mDBHelper.getWritableDatabase().insert(DownloadColumn.TABLE_NAME, null, values);
        return ContentUris.withAppendedId(uri, id);
    }

    @Override
    public boolean onCreate() {
        mContext = getContext();
        mDBHelper = DownloadDataBase.getInstance(mContext);
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        Cursor cursor = null;
        int flag = mUriMatcher.match(uri);
        if (flag == CODE_DOWNLOAD) {
            long id = ContentUris.parseId(uri);
            selection = DownloadColumn._ID + "=?";
            selectionArgs = new String[]{id + ""};
            cursor = mDBHelper.getReadableDatabase().query(DownloadColumn.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
        } else if (flag == CODE_DOWNLOADS) {
            cursor = mDBHelper.getReadableDatabase().query(DownloadColumn.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
        }
        return cursor;

    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        int result = -1;
        int flag = mUriMatcher.match(uri);
        if(flag == CODE_DOWNLOAD){
            long id = ContentUris.parseId(uri);
            selection = DownloadColumn._ID + "=?";
            selectionArgs = new String[]{id + ""};
            result = mDBHelper.getWritableDatabase().update(DownloadColumn.TABLE_NAME, values, selection, selectionArgs);
        }else{
            result = mDBHelper.getWritableDatabase().update(DownloadColumn.TABLE_NAME, values, selection, selectionArgs);
        }
        return result;
    }
}

在操作数据库的时候需要特别注意,要加上notifyChange();方法,不然ContentObserver是监听不到的

/**
 * Created by huangsm
 * Email:huangsanm@foxmail.com
 */
public class DownloadManager {

    private ContentResolver mResolver;
    public DownloadManager (ContentResolver resolver){
        mResolver = resolver;
    }

    public Long addTask(DownloadItem item){
        ContentValues values = new ContentValues();
        values.put(DownloadColumn.NAME, item.getName());
        values.put(DownloadColumn.DOWNLOAD_ID, item.getDownloadID());
        values.put(DownloadColumn.FILE_SIZE, item.getFileSize());
        values.put(DownloadColumn.ICON_URI, item.getIconUri());
        values.put(DownloadColumn.PATH, item.getPath());
        values.put(DownloadColumn.PKG_NAME, item.getPkg());
        values.put(DownloadColumn.VERSION, item.getVersion());
        values.put(DownloadColumn.STATUS, item.getStatus());
        values.put(DownloadColumn.SUFFIX,item.getSuffix());
        values.put(DownloadColumn.URI, item.getUri());
        Uri uri = mResolver.insert(DownLoadContentProvider.CONTENT_URI, values);
        notifyChange();
        return Long.valueOf(uri.getLastPathSegment());
    }

    public int deleteTask(int downloadID){
        Uri uri = ContentUris.withAppendedId(DownLoadContentProvider.CONTENT_URI, downloadID);
        notifyChange();
        return mResolver.delete(uri, null, null);
    }

    public int updateTask(int downloadID, ContentValues values){
        Uri uri = ContentUris.withAppendedId(DownLoadContentProvider.CONTENT_URI, downloadID);
        notifyChange();
        return mResolver.update(uri, values, null, null);
    }

    public DownloadItem queryTask(int downloadID){
        DownloadItem item = null;
        notifyChange();
        Uri uri = ContentUris.withAppendedId(DownLoadContentProvider.CONTENT_URI, downloadID);
        Cursor cursor = mResolver.query(uri, null, null, null, null);
        try {
            if(cursor != null && cursor.getCount() > 0){
                cursor.moveToFirst();
                item = convertTaskItem(cursor);
            }
        }finally {
            cursor.close();
        }
        return item;
    }

    public boolean checkTaskStatus(int downloadID, int status){
        Globals.log("checkTaskStatus:" + downloadID + ",status:" + status);
        int checkResult = 0;
        Cursor cursor = mResolver.query(DownLoadContentProvider.CONTENT_URI, new String[]{DownloadColumn.STATUS}, DownloadColumn._ID + "=?", new String[]{downloadID + ""}, null);
        try {
            if(cursor != null && cursor.getCount() > 0){
                cursor.moveToFirst();
                checkResult = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadColumn.STATUS));
            }
        } finally {
            cursor.close();
        }
        return checkResult == status;
    }

    public Cursor queryTask(){
        return mResolver.query(DownLoadContentProvider.CONTENT_URI, null, DownloadColumn.STATUS + "<>" + DownloadStatus.STATUS_DELETEED, null, null);
    }

    public String getStringVal(Cursor cursor, String columnName){
        return cursor.getString(cursor.getColumnIndexOrThrow(columnName));
    }

    public int getIntVal(Cursor cursor, String columnName){
        return cursor.getInt(cursor.getColumnIndexOrThrow(columnName));
    }

    public DownloadItem convertTaskItem(Cursor cursor){
        DownloadItem item = new DownloadItem();
        item.setId(cursor.getInt(cursor.getColumnIndexOrThrow(DownloadColumn._ID)));
        item.setName(cursor.getString(cursor.getColumnIndexOrThrow(DownloadColumn.NAME)));
        item.setPkg(cursor.getString(cursor.getColumnIndexOrThrow(DownloadColumn.PKG_NAME)));
        item.setDownloadID(cursor.getInt(cursor.getColumnIndexOrThrow(DownloadColumn.DOWNLOAD_ID)));
        item.setUri(cursor.getString(cursor.getColumnIndexOrThrow(DownloadColumn.URI)));
        item.setPath(cursor.getString(cursor.getColumnIndexOrThrow(DownloadColumn.PATH)));
        item.setFileSize(cursor.getString(cursor.getColumnIndexOrThrow(DownloadColumn.FILE_SIZE)));
        item.setSuffix(cursor.getString(cursor.getColumnIndexOrThrow(DownloadColumn.SUFFIX)));
        item.setStatus(cursor.getInt(cursor.getColumnIndexOrThrow(DownloadColumn.STATUS)));
        item.setType(cursor.getInt(cursor.getColumnIndexOrThrow(DownloadColumn.TYPE)));
        item.setVersion(cursor.getString(cursor.getColumnIndexOrThrow(DownloadColumn.VERSION)));
        item.setIconUri(cursor.getString(cursor.getColumnIndexOrThrow(DownloadColumn.ICON_URI)));
        item.setCurrentByte(cursor.getLong(cursor.getColumnIndexOrThrow(DownloadColumn.CURRENT_BYTE)));
        item.setTotalByte(cursor.getLong(cursor.getColumnIndexOrThrow(DownloadColumn.TOTAL_BYTE)));
        item.setPercent(DownloadUtils.getProgressValue(item.getTotalByte(), item.getCurrentByte()));
        return item;
    }

    private void notifyChange(){
        mResolver.notifyChange(DownLoadContentProvider.CONTENT_URI, null);
    }

这样下载的信息在Thread里面都保存在了数据库,在activity里面就通过ContentObserver监听数据库,借助ContentProvider来实现读取数据库的信息,就完成了一系列的操作。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部