文章

实现 DownloadManager 下载完 apk 自动提示安装的功能

运行环境

  • Android 5.1.1, API 22

解决方案

  1. 下载新版本的 apk
    1
    2
    3
    4
    5
    6
    7
    
     public void downloadNewVersion() {
         mDownloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
         // apkDownloadUrl 是 apk 的下载地址
         DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkDownloadUrl));
         // 获取下载队列 id
         enqueueId = mDownloadManager.enqueue(request);
     }
    
  2. 注册接收下载完成的广播
    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
    
     BroadcastReceiver receiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             long downloadCompletedId = intent.getLongExtra(
                     DownloadManager.EXTRA_DOWNLOAD_ID, 0);
             // 检查是否是自己的下载队列 id, 有可能是其他应用的
             if (enqueueId != downloadCompletedId) {
                 return;
             }
             DownloadManager.Query query = new DownloadManager.Query();
             query.setFilterById(enqueueId);
             Cursor c = mDownloadManager.query(query);
             if (c.moveToFirst()) {
                 int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
                 // 下载失败也会返回这个广播,所以要判断下是否真的下载成功
                 if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
                     // 获取下载好的 apk 路径
                     String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
                     // 提示用户安装
                     promptInstall(Uri.parse("file://" + uriString));
                 }
             }
         }
     };
     // 注册广播, 设置只接受下载完成的广播
     registerReceiver(receiver, new IntentFilter(
             DownloadManager.ACTION_DOWNLOAD_COMPLETE));
    
  3. 取消注册广播, 不取消注册的话, 调用recreate时会报Are you missing a call to unregisterReceiver()?错误
    1
    2
    3
    4
    5
    6
    
     @Override
     protected void onDestroy() {
     	super.onDestroy();
        
     	unregisterReceiver(mBroadcastReceiver);
     }
    
  4. 提示用户安装
    1
    2
    3
    4
    5
    6
    7
    
     private void promptInstall(Uri data) {
         Intent promptInstall = new Intent(Intent.ACTION_VIEW)
                 .setDataAndType(data, "application/vnd.android.package-archive");
         // FLAG_ACTIVITY_NEW_TASK 可以保证安装成功时可以正常打开 app
         promptInstall.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         startActivity(promptInstall);
     }
    

    就是这样,运行 app 看下效果吧

总结

坑比较多,花了3天时间才完全实现这个效果 1个坑是必须使用setDataAndType(),而不能单独使用setData()setType() 另外一个坑是getUriForDownloadedFile现在返回的是content://这种格式的链接了,无法用于启动intent 最后一个坑是intent必须设置FLAG_ACTIVITY_NEW_TASK, 否则安装完成后无法正常打开app

常见问题

提示解析程序包时出现问题

mDownloadManager.getUriForDownloadedFile(enqueueId)返回的Uri是这种格式的content://downloads/my_downloads/83, 这种格式的Uri启动intent会报解析程序包时出现问题

No Activity found to handle Intent

No Activity found to handle Intent { act=android.intent.action.VIEW dat=/storage/sdcard/download/demo.apk typ=application/vnd.android.package-archive

可能原因1: 有可能是没有权限读取这个文件或者文件路径错误

可能原因2:

1
2
3
4
5
// Intent promptInstall = new Intent(Intent.ACTION_VIEW)
//            .setData(data)
//            .setType("application/vnd.android.package-archive");
Intent promptInstall = new Intent(Intent.ACTION_VIEW)
                .setDataAndType(data, "application/vnd.android.package-archive");

单独设置datatype也会造成这个错误,原因请看http://developer.android.com/reference/android/content/Intent.html#setData(android.net.Uri) 你会发现无论何时你去设置data或者type, 另外一个都会自动的变为空, 例如: setData()会使得type参数值为空, 如果你想让两者都生效的话只能使用setDataAndType()

提示应用程序未安装

有可能是因为APK 签名不一致, 比如之前是debug版(无签名), 现在你更新安装release版(有签名), 就会出现这个问题

参考链接

更新纪录

  • 2016年3月8日 添加取消注册广播的代码
  • 2016年1月22日 发布
本文由作者按照 CC BY 4.0 进行授权

Comments powered by Disqus.