8.0APK下载并跳转安装--DownloadManager、FileProvider、BroadcastReceiver的结合使用

xiaoxiao2021-02-28  60

我们希望应用的下载更新可以不受UI周期的约束,这里下载就涉及到Google提供的大文件下载管理类DownloadManager,下载完成后通过BroadcastReceive接收下载完成的消息开启应用安装。下面正式开启步骤解析

本博客Demo地址:https://download.csdn.net/download/g_ying_jie/10697856

第一步,传入apk的下载地址,利用DownloadManager下载安装包

protected static void downloadApk(Context context, String apkUrl, String apkName) { //获取DownloadManager对象 DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkUrl)); //指定APK缓存路径和应用名称,可在SD卡/Android/data/包名/file/Download文件夹中查看 request.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, apkName.concat(".apk")); //设置网络下载环境为wifi request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI); //设置显示通知栏,下载完成后通知栏自动消失 request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); //设置通知栏标题 request.setTitle("we_chat"); request.setDescription("APK下载中..."); request.setAllowedOverRoaming(false); //获得唯一下载id DOWNLOAD_ID = downloadManager.enqueue(request); } DOWNLOAD_ID是下载的唯一标识,后期可以用来查询下载的文件信息。

第二步,新建BroadcastReceiver接收下载完成消息,并回调状态

package com.example.gu.download; import android.app.DownloadManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.provider.Settings; import android.support.annotation.RequiresApi; import android.support.v4.content.FileProvider; import android.text.TextUtils; import java.io.File; import java.util.Objects; public class UpdateBroadcastReceiver extends BroadcastReceiver { private DownloadCompleteCallBack callBack; public UpdateBroadcastReceiver(DownloadCompleteCallBack callBack) { this.callBack = callBack; } public void onReceive(Context context, Intent intent) { //下载完成 long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); if (id == UpdateUtil.DOWNLOAD_ID) { callBack.canInstall(id); } } public interface DownloadCompleteCallBack { void canInstall(long id); } }

第三步,在MainActivity注册广播,注入DownloadCompleteCallBack的回调

receiver = new UpdateBroadcastReceiver(this); IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); registerReceiver(receiver, filter);

    不要忘记注销

@Override protected void onDestroy() { super.onDestroy(); if (receiver != null) unregisterReceiver(receiver); }

第四步,在回调的canInstall中执行安装流程

protected static void installApp(Context mContext, long id) { File apkFile = queryApkPath(mContext, id); if (apkFile != null) { try { //8.0跳转设置允许安装未知应用 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { boolean hasInstallPermission = mContext.getPackageManager().canRequestPackageInstalls(); if (!hasInstallPermission) { startInstallPermissionSettingActivity(mContext); return; } } Intent intent = new Intent(Intent.ACTION_VIEW); //兼容7.0之后禁用在应用外部公开file://URI,以FileProvider替换 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //给目标应用一个临时授权 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //注意此处的authority必须与manifest的provider保持一致 intent.setDataAndType(FileProvider.getUriForFile(mContext, mContext.getPackageName().concat(".fileprovider"), apkFile), "application/vnd.android.package-archive"); } else { intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive"); } //验证是否有APP可以接受此Intent,防止FC if (mContext.getPackageManager().queryIntentActivities(intent, 0).size() > 0) { mContext.startActivity(intent); } } catch (Throwable e) { e.printStackTrace(); } } }

  其中queryApkPath方法调用下载返回的ID查询apk文件信息,代码如下

private static File queryApkPath(Context context, long id) { File apkFile = null; DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); DownloadManager.Query query = new DownloadManager.Query(); query.setFilterById(id); Cursor cur = manager.query(query); if (cur != null) { if (cur.moveToFirst()) { // 获取文件下载路径 String filePath = cur.getString(cur.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); if (!TextUtils.isEmpty(filePath)) { apkFile = new File(Objects.requireNonNull(Uri.parse(filePath).getPath())); } } cur.close(); } return apkFile; }

  startInstallPermissionSettingActivity方法用于8.0之后跳转允许安装未知应用的设置页面,代码如下

/** * 跳转到设置-允许安装未知来源-页面 */ @RequiresApi(api = Build.VERSION_CODES.O) private static void startInstallPermissionSettingActivity(Context mContext) { Uri packageURI = Uri.parse("package:".concat(mContext.getPackageName())); //注意这个是8.0新API Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI); ((Activity) mContext).startActivityForResult(intent, INSTALL_REQUESTCODE); }

第五步,在onActivityResult方法中监听8.0未知应用授权情况,授权成功再次发起安装流程

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == UpdateUtil.INSTALL_REQUESTCODE && resultCode == RESULT_OK) { UpdateUtil.installApp(MainActivity.this, DOWNLOAD_ID); } else { Toast.makeText(this, "授权失败,应用未能安装", Toast.LENGTH_LONG).show(); } }

敲黑板划重点,整体的流程都已经走完了,细心的读者会发现在7.0之后引入了FileProvider来防止在应用外部公开file://URI,

那么这个该怎么配置各种属性呢?接着往下看

FileProvider使用第一步,在res下新建xml文件夹,在其下新建一个file_paths.xml文件,如下

<?xml version="1.0" encoding="utf-8"?> <external-files-path name="download" path="Download" />

path就是FileProvider共享的文件夹目录,即/storage/emulated/0/Android/data/包名/files/Download文件夹被共享

其他属性汇总:更多有关FileProvider属性可前往FileProvider详解

files-path ==> /data/data/包名/files cache-path ==> /data/data/com.jph.simple/cache external-path ==> /storage/emulated/0 external-cache-path ==> /storage/emulated/0/Android/data/包名/cache

等同于如下路径:

<root-path/> 代表设备的根目录new File("/"); <files-path/> 代表context.getFilesDir() <cache-path/> 代表context.getCacheDir() <external-path/> 代表Environment.getExternalStorageDirectory() <external-files-path>代表context.getExternalFilesDirs() <external-cache-path>代表getExternalCacheDirs()

 

FileProvider使用第二步,在manifest中申明provider

<provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.gu.download.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> 注解: name的值一般都固定为android.support.v4.content.FileProvider。如果开发者继承了FileProvider,则可以写上其绝对路径。 authorities字段的值用来表明使用的使用者,在FileProvider的函数getUriForFile需要传入该参数,通用写法是包名+fileprovider。 exported 的值为false,表示该FileProvider只能本应用使用,不是public的。 grantUriPermissions 的值为true,表示允许赋予临时权限。

 

 

 

 

 

 

转载请注明原文地址: https://www.6miu.com/read-67364.html

最新回复(0)