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

先看看下面的几张图片

163557_gBf9_127095.jpg

163558_GYZV_127095.jpg

163558_ebQS_127095.jpg

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

数据库(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来实现读取数据库的信息,就完成了一系列的操作。

转载于:https://my.oschina.net/huangsm/blog/307329

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android平台上实现多线程下载音乐可以通过以下步骤: 1. 首先,在AndroidManifest.xml文件中添加访问网络的权限。 2. 创建一个Service来执行下载任务,这样可以保证在活动销毁或离开前台时下载任务仍然可以继续。 3. 在Service中创建线程池,可以使用Java的Executor框架来实现,例如ThreadPoolExecutor。 4. 在要进行下载的Activity中,通过按钮点击或其他触发方式来启动Service,并传递音乐下载链接等参数。 5. 在Service的onStartCommand方法中,从传递过来的参数中获取音乐下载链接,然后根据需求将链接拆分为多个部分。 6. 使用多线程分别下载这些部分,每个线程可以使用Java的URLConnection或HttpClient等工具类库进行网络请求。 7. 下载完成后,将每个部分的数据合并为完整的音乐文件。 8. 在下载过程中,可以使用BroadcastReceiver来发送下载进度或状态的广播,然后在Activity中注册该广播接收器来更新UI界面。 9. 在下载过程中,可以通过判断当前网络状态来控制下载速度,例如在移动数据网络下可限制下载速度,而在WIFI网络下可充分利用带宽。 10. 需要注意的是,下载过程中要处理异常情况,例如网络中断、服务器异常等,可以使用try-catch块来捕获异常并进行相应的处理。 以上就是Android平台上实现多线程下载音乐的大致步骤,通过合理地利用多线程技术,可以加快下载速度,提升用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值