zoukankan      html  css  js  c++  java
  • DownloadProvider 源码详细分析

    DownloadProvider 简介
    DownloadProvider 是Android提供的DownloadManager的增强版,亮点是支持断点下载,提供了“开始下载”,“暂停下载”,“重新下载”,“删除下载”接口。源码下载地址

    DownloadProvider 详细分析

    DownloadProvider开始下载的是由DownloadManager 的 enqueue方法启动的,启动一个新的下载任务的时序图 


    开始新的下载时候会调用DownloadManager的enqueue方法,然后再执行DownloadProvider的insert方法,将下载信息写入数据库,包括下载链接地址等,然后再调用DownloadService的onCreate或者onStartCommand方法。 DownloadProvider类是非常重要的类,所有操作都跟此类有关,因为要保存下载状态。 在分析DownloadProvider的insert方法前,先看看insert方法的源码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    @Override
    public Uri insert(final Uri uri, final ContentValues values) {
        checkInsertPermissions(values);
        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
     
        // note we disallow inserting into ALL_DOWNLOADS
        int match = sURIMatcher.match(uri);
        if (match != MY_DOWNLOADS) {
            Log.d(Constants.TAG, "calling insert on an unknown/invalid URI: "
                    + uri);
            throw new IllegalArgumentException("Unknown/Invalid URI " + uri);
        }
     
        ContentValues filteredValues = new ContentValues();
     
        ......
     
        Integer dest = values.getAsInteger(Downloads.COLUMN_DESTINATION);
        if (dest != null) {
         
            ......
             
        }
        Integer vis = values.getAsInteger(Downloads.COLUMN_VISIBILITY);
        if (vis == null) {
            if (dest == Downloads.DESTINATION_EXTERNAL) {
                filteredValues.put(Downloads.COLUMN_VISIBILITY,
                        Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
            } else {
                filteredValues.put(Downloads.COLUMN_VISIBILITY,
                        Downloads.VISIBILITY_HIDDEN);
            }
        } else {
            filteredValues.put(Downloads.COLUMN_VISIBILITY, vis);
        }
        ......
     
        String pckg = values.getAsString(Downloads.COLUMN_NOTIFICATION_PACKAGE);
        String clazz = values.getAsString(Downloads.COLUMN_NOTIFICATION_CLASS);
        if (pckg != null && (clazz != null || isPublicApi)) {
            ......
        }
        ......
     
        //启动下载服务
        Context context = getContext();
        context.startService(new Intent(context, DownloadService.class));
     
        //插入数据库
        long rowID = db.insert(DB_TABLE, null, filteredValues);
        if (rowID == -1) {
            Log.d(Constants.TAG, "couldn't insert into downloads database");
            return null;
        }
     
        insertRequestHeaders(db, rowID, values);
        //启动下载服务
        context.startService(new Intent(context, DownloadService.class));
        notifyContentChanged(uri, match);
        return ContentUris.withAppendedId(Downloads.CONTENT_URI, rowID);
    }
    每次开始一个新的下载任务,都会插入数据库,然后启动启动下载服务类DownloadService,所以真正处理下载的是后台服务DownloadService,DownloadService中有个下载线程类DownloadThread,DownloadService时序图 


    如果DownloadService没有启动将会执行onCreate()------>onStartCommand()方法,否则执行onStartCommand()方法。然后执行updateFromProvider()方法启动UpdateThread线程,准备启动DownloadThread线程。 分析UpdateThread的run方法前先看看run方法的源码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
     
        //如果数据里的存储的达到了1000以上时候,将会删除status>200即失败的记录
        trimDatabase();
        removeSpuriousFiles();
     
        boolean keepService = false;
        // for each update from the database, remember which download is
        // supposed to get restarted soonest in the future
        long wakeUp = Long.MAX_VALUE;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
            //会一直在此循环,直到启动完所有下载任务
        for (;;) {
            synchronized (DownloadService.this) {
                if (mUpdateThread != this) {
                    throw new IllegalStateException(
                            "multiple UpdateThreads in DownloadService");
                }
                if (!mPendingUpdate) {
                    mUpdateThread = null;
                    if (!keepService) {
                        stopSelf();
                    }
                    if (wakeUp != Long.MAX_VALUE) {
                        scheduleAlarm(wakeUp);
                    }
                    return;
                }
                mPendingUpdate = false;
            }
     
            long now = mSystemFacade.currentTimeMillis();
            keepService = false;
            wakeUp = Long.MAX_VALUE;
            Set<long> idsNoLongerInDatabase = new HashSet<long>(
                    mDownloads.keySet());
     
            Cursor cursor = getContentResolver().query(
                    Downloads.ALL_DOWNLOADS_CONTENT_URI, null, null, null,
                    null);
            if (cursor == null) {
                continue;
            }
            try {
                DownloadInfo.Reader reader = new DownloadInfo.Reader(
                        getContentResolver(), cursor);
                int idColumn = cursor.getColumnIndexOrThrow(Downloads._ID);
     
                for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor
                        .moveToNext()) {
                    long id = cursor.getLong(idColumn);
                    idsNoLongerInDatabase.remove(id);
                    DownloadInfo info = mDownloads.get(id);
                    if (info != null) {
                        updateDownload(reader, info, now);
                    } else {
                        info = insertDownload(reader, now);
                    }
                    if (info.hasCompletionNotification()) {
                        keepService = true;
                    }
                    long next = info.nextAction(now);
                    if (next == 0) {
                        keepService = true;
                    } else if (next > 0 && next < wakeUp) {
                        wakeUp = next;
                    }
                }
            } finally {
                cursor.close();
            }
     
            for (Long id : idsNoLongerInDatabase) {
                deleteDownload(id);
            }
     
            // is there a need to start the DownloadService? yes, if there
            // are rows to be deleted.
     
            for (DownloadInfo info : mDownloads.values()) {
                if (info.mDeleted) {
                    keepService = true;
                    break;
                }
            }
     
            mNotifier.updateNotification(mDownloads.values());
     
            // look for all rows with deleted flag set and delete the rows
            // from the database
            // permanently
            for (DownloadInfo info : mDownloads.values()) {
                if (info.mDeleted) {
                    Helpers.deleteFile(getContentResolver(), info.mId,
                            info.mFileName, info.mMimeType);
                }
            }
        }
    }</long></long>
    UpdateThread线程负责从数据库中获取下载任务,该线程不会每次都新建新的对象,只有UpdateThread为空时候才会新建,在此线程的run()方法中有两个for循环,外循环是从数据获取下载任务,确保插入数据库的任务都能被启动,直到启动完所有的下载任务才会退出。内循环是遍历从数据库获取的没完成或者刚开始的下载任务,启动下载。 DownloadThrea线程是真正负责下载的线程,每次启动一个任务都会创建一个新的下载线程对象(对手机来说会耗很大的CPU,所有要加上同步锁或者线程池来控制同时下载的数量),看看DownloadThread run方法的时序图: 
    从这个时序图可以看出,这里涉及的源码比较多,在这没有写,看的时候一定要对照的源码来看。其实在下载的时候会发生很多的异常,如网络异常,内存卡容量不足等,所以捕获异常很重要的,捕获后进行相关的处理,这也是体现出考虑全面的地方。 


  • 相关阅读:
    Android开源项目收藏
    ubuntu修改ip获取方式(静态,动态)
    linux内核调用用户空间程序
    linux 改变目录下所有文件及其子文件夹下的权限
    jquery remove() empty()
    jquery之attr()和removeAttr() prop的使用场所
    jquery 获取对象的八种总结
    html子标签浮动父标签无法扩充
    静态代码块
    java static介绍
  • 原文地址:https://www.cnblogs.com/bigben0123/p/4300671.html
Copyright © 2011-2022 走看看