当前位置:   article > 正文

Android 5.0、6.0、7.0、8.0、9.0 新特性,DownloadManager踩坑记_downloadmanager之大坑

downloadmanager之大坑

目录

一、5.0新特性

二、6.0新特性

三、7.0新特性

四、8.0新特性

五、9.0新特性

六、 一步步跟着案例进行版本升级踩坑——DownloadManager

1、适配6.0 动态权限

2、适配Android 7.0 解析包时出现问题

3、适配Android 8.0:未知来源的应用权限


本案例下载地址:

https://download.csdn.net/download/csdn_aiyang/10906816

https://github.com/aiyangtianci/DownloadManagerDemo


Android 最近几年发展非常快,自08年智能机时代开始,Android操作系统一路高歌,每年推出升级新的版本。然而,这一转眼都10年过去了,10年的智能机发展之路,使Android系统占全球市场份额近7成(IOS 近3成)。有趣的是谷歌给自己的这个‘亲儿子’起名字可谓绞尽了脑汁,Android1.1开始,就有过给起名Astro(小金刚)、Bender(机器人班亭)本是想从字母A到字母Z,每一代都用首字母排序,可是,由这样的名字太二了,于是从Android1.5的时候,聪明的谷歌就想到了以甜点给‘亲儿子’命名的文化传统了,就在17年8月份谷歌为提高续航和安全发布了Android 8.0,主题是“奥利奥饼干”。但是,由于版本系统推出过快及兼容过多旧版代码,Android 版本碎片化越来越严重了。

谷歌从8.0后推出的一种新的技术框架“Project Treble”,可以改善安卓系统的碎片化问题。将操作系统框架代码与“特定供应商”硬件代码分开,允许手机厂商在无须芯片商参与的情况下推送更新,希望由此减少升级阻碍,加快新系统普及速度。在Android 9.0发布后,谷歌加入了强制性的rollback protection功能。简单来说,这项功能直接杜绝了用户的系统降级操作,虽然早在Android 8.0中就已经加入了这项功能,但是不同的是,原本这项功能是处于用户自选的状态,在更新Android 9之后就变更为强制性了。这也就寓意着,从Android 9开始,之后的安卓系统终于要正式致敬苹果的iOS,系统只提供升级,不允许降级。

其实,谷歌一直以来都在想尽办法试图解决安卓系统碎片化的问题,但是由于覆盖广、适配设备过多等因素,始终没有收获什么成效。碎片化这一问题,也始终被用户所诟病,不过谷歌这次真的下狠手了,通过在系统内置Android Verified Boot 2.0,来阻止用户降级,一旦用户强制输入低于当前版本的安卓系统,被自动检测到后会出现无限重启、无法开机,不得不佩服谷歌的这招真的是做绝了。

 

在正式开篇之前,本人有些心路历程想和大家分享一下,说白了,就是想吐吐这几天苦水,哈哈哈。从收集文章素材、参考文章、动手写demo、写文章等,用了近一个星期。我个人觉得自己写文章的效率很高,这篇文章中的案例在踩坑时出现过很多次奇怪的闪退,着实要费了些心思。例如,在一个手机上开发时遇见崩溃,换个手机就没问题了,我实在搞不懂崩溃原因时候,也会像小白一样重新创建新项目,把旧代码一行行重新复制粘贴过去,重新运行项目去解决问题。然而,就这样我尽然重新创建过四次。手动捂脸哭表情。说这些,是希望同学们能认真对待我的博文,谢谢,因为我很用心在分享技术!

 

一、5.0新特性

  • Material Design
  • 支持多种设备
  • 全新通知中心
  • 支持 64 位 ART 虚拟机
  • 电池续航改进
  • 全新“最近应用程序”
  • 安全性改进
  • 不同数据独立保存
  • 改进搜索
  • 支持蓝牙 4.1、USB Audio、多人分享等

重点注意: 加了很多新控件,如抽屉布局,菜单布局,卡片布局,列表布局新增RecyclerView等。努力改善应用界面吧!

Android Material Design之CoordinatorLayout效果实现

Android ToolBar 基础使用——进阶封装BaseActivity(附源码)

Android 探究onCreateViewHolder和onBindViewHolder两者关系和调用次数

Android 使用RecycleView实现吸附小标题的Demo(附源码)

Android5.0Activity的转场动画、过渡动画、过场动画、跳转动画

Android共享元素转场动画Fragment to Fragment

 

二、6.0新特性

  • 动态权限管理
  • 系统层支持指纹识别
  • APP 关联
  • Android Pay
  • 电源管理
  • TF 卡默认存储

重点注意:动态的权限申请,6.0以下的版本可以直接申请权限直接使用了,以上的版本需要一些敏感权限时需要动态申请。可参考我的文章。

Android6.0版本以上危险权限动态申请及RxPermissions权限库使用

 

三、7.0新特性

  • 分屏多任务
  • 下拉快捷开关
  • 新通知消息
  • 夜间模式
  • 流量保护模式
  • 全新设置样式
  • 改进 Doze 休眠机制
  • 系统级电话黑名单
  • 菜单键快速切换应用

重点注意 7.0对于SDCard的文件URI的访问做了限制,获取文件uri的方式也变了,开发时需要注意。 下面案例会讲到。

鸿洋的:Android 7.0 行为变更 通过FileProvider在应用间共享文件吧

 

四、8.0新特性

  • 画中画
  • 通知标志
  • 自动填充框架
  • 系统优化
  • 后台限制
  • 应用快捷键
  • 语言区域和国际化

重点注意:

8.0限制了后台服务这些,启动后台服务需要设置通知栏,使服务变成前台服务。

8.0对于安装位置来源的应用做了更严格的限制,在app更新安装时需要做些处理。 下面案例会讲到。

Android 8.0 P适配详细指南

 

五、9.0新特性

  • 刘海设计
  • 黑白模式切换
  • 加入长截图
  • 加入护眼模式
  • 通知栏的体验优化
  • Material Design功能更新等等

重点注意:Android 9.0强制使用https,会阻塞http请求,如果app使用的第三方sdk有http,将全部被阻塞。

 

六、10 Q 新特性

Q的最大更新就是用户隐私权限变更。

  • 存储权限:判断当应用运行在Q平台上时,访问自己文件不需申请读写权限,访问音频需要申请新的媒体特定权限。
  • 后台定位权限:如果目标版本targetSDK <= P 请求了ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION权限,Q设备会自动帮你申请ACCESS_BACKGROUND_LOCATION权限。
  • 后台启动 Activity:仅针对与用户毫无交互就启动一个Activity的情况,比如微信登陆授权。
  • minSDK警告:谷歌要求运行在Q设备上的应用targetSDK>=Android 6.0(API 级别 23),不然会向用户发出警告。
  • 设备标识符(DeviceId):TelephonyManager.getDeviceId()方法失效。新权限READ_PRIVILEGED_PHONE_STATE只提供给系统app使用。下面是一个通过硬件信息生产的UUID。设备ID的获取一个版本比一个版本艰难,如有好的方法请指出。
  1. public static String getUUID() {
  2. String serial = null;
  3. String m_szDevIDShort = "35" +
  4. Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +
  5. Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 +
  6. Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +
  7. Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +
  8. Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +
  9. Build.TAGS.length() % 10 + Build.TYPE.length() % 10 +
  10. Build.USER.length() % 10; //13 位
  11. try {
  12. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  13. serial = android.os.Build.getSerial();
  14. } else {
  15. serial = Build.SERIAL;
  16. }
  17. //API>=9 使用serial号
  18. return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
  19. } catch (Exception exception) {
  20. //serial需要一个初始化
  21. serial = "serial"; // 随便一个初始化
  22. }
  23. //使用硬件信息拼凑出来的15位号码
  24. return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
  25. }

 

最后,版本升级踩坑记——DownloadManager

DownloadManager使用介绍参考:https://blog.csdn.net/csdn_aiyang/article/details/64126379

MainActivity.class 代码

  1. public class MainActivity extends AppCompatActivity {
  2. private TextView down;
  3. private TextView progress;
  4. private ProgressBar pb_update;
  5. private DownloadManager downloadManager;
  6. private DownloadManager.Request request;
  7. public static String downloadUrl = "http://www.wanandroid.com/blogimgs/ecb4c318-42f3-454a-a6c4-615ad16f35bd.apk";
  8. private DownloadReceiver completeReceiver;
  9. private final Uri CONTENT_URI = Uri.parse("content://downloads/my_downloads");
  10. private DownloadChangeObserver observer;
  11. long id;
  12. Handler handler = new Handler() {
  13. @Override
  14. public void handleMessage(Message msg) {
  15. super.handleMessage(msg);
  16. Bundle bundle = msg.getData();
  17. int pro = bundle.getInt("pro");
  18. pb_update.setProgress(pro);
  19. progress.setText(String.valueOf(pro) + "%");
  20. }
  21. };
  22. class DownloadChangeObserver extends ContentObserver {
  23. public DownloadChangeObserver(Handler handler) {
  24. super(handler);
  25. }
  26. @Override
  27. public void onChange(boolean selfChange) {
  28. super.onChange(selfChange);
  29. updateView();
  30. }
  31. }
  32. class DownloadReceiver extends BroadcastReceiver {
  33. @Override
  34. public void onReceive(final Context context, final Intent intent) {
  35. Log.i("aaa", "广播监听");
  36. long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
  37. Intent intentInstall = new Intent();
  38. Uri uri = null;
  39. if (completeDownLoadId == id) {
  40. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 兼容6.0以下
  41. uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);
  42. installPackge(context,intentInstall,uri);
  43. }
  44. }
  45. }
  46. }
  47. /**
  48. * 安装APK
  49. * @param context
  50. * @param intentInstall
  51. * @param uri
  52. */
  53. private void installPackge(Context context,Intent intentInstall,Uri uri){
  54. intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  55. intentInstall.setAction(Intent.ACTION_VIEW);
  56. // 安装应用
  57. Log.i("aaa", "app下载完成了,开始安装。。。"+uri);
  58. intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");
  59. context.startActivity(intentInstall);
  60. }
  61. @Override
  62. protected void onCreate(Bundle savedInstanceState) {
  63. super.onCreate(savedInstanceState);
  64. setContentView(R.layout.activity_main);
  65. down = (TextView) findViewById(R.id.down);
  66. progress = (TextView) findViewById(R.id.progress);
  67. pb_update = (ProgressBar) findViewById(R.id.pb_update);
  68. down.setOnClickListener(new View.OnClickListener() {
  69. @Override
  70. public void onClick(View view) {
  71. LoadApp();
  72. //requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,Code_PERMISSION);
  73. }
  74. });
  75. }
  76. private void LoadApp() {
  77. //创建下载对象
  78. downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
  79. request = new DownloadManager.Request(Uri.parse(downloadUrl));
  80. request.setTitle("app-release.apk");
  81. request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
  82. request.setAllowedOverRoaming(false);
  83. request.setMimeType("application/vnd.android.package-archive");
  84. request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
  85. //设置文件存放路径
  86. request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "app-release.apk");
  87. }
  88. private void updateView() {
  89. int[] bytesAndStatus = new int[]{0, 0, 0};
  90. DownloadManager.Query query = new DownloadManager.Query().setFilterById(id);
  91. Cursor c = null;
  92. try {
  93. c = downloadManager.query(query);
  94. if (c != null && c.moveToFirst()) {
  95. //已经下载的字节数
  96. bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
  97. //总需下载的字节数
  98. bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
  99. }
  100. } finally {
  101. if (c != null) {
  102. c.close();
  103. }
  104. }
  105. int pro = (bytesAndStatus[0] * 100) / bytesAndStatus[1];
  106. Message msg = Message.obtain();
  107. Bundle bundle = new Bundle();
  108. bundle.putInt("pro", pro);
  109. msg.setData(bundle);
  110. handler.sendMessage(msg);
  111. Log.i("aaa", "下载进度:" + bytesAndStatus[0] + "/" + bytesAndStatus[1]);
  112. }
  113. //开始下载
  114. private void onDownBegin() {
  115. try {
  116. id = downloadManager.enqueue(request);
  117. } catch (Exception e) {
  118. e.printStackTrace();
  119. } finally {
  120. //更新UI
  121. observer = new DownloadChangeObserver(handler);
  122. getContentResolver().registerContentObserver(CONTENT_URI, true, observer);
  123. //安装
  124. completeReceiver = new DownloadReceiver();
  125. registerReceiver(completeReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
  126. down.setText("正在下载");
  127. down.setClickable(false);
  128. }
  129. }
  130. @Override
  131. protected void onDestroy() {
  132. super.onDestroy();
  133. if (observer != null) {
  134. getContentResolver().unregisterContentObserver(observer);
  135. unregisterReceiver(completeReceiver);
  136. }
  137. }
  138. }

布局文件:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical" android:layout_width="match_parent"
  4. android:gravity="center"
  5. android:layout_height="match_parent">
  6. <TextView
  7. android:layout_width="wrap_content"
  8. android:layout_height="wrap_content"
  9. android:layout_marginBottom="20dp"
  10. android:text="下载进度"/>
  11. <ProgressBar
  12. android:id="@+id/pb_update"
  13. style="?android:attr/progressBarStyleHorizontal"
  14. android:layout_width="match_parent"
  15. android:layout_height="10dp"
  16. android:layout_marginLeft="10dp"
  17. android:layout_marginRight="10dp"
  18. android:layout_gravity="center_horizontal"
  19. android:layout_marginTop="10dp"
  20. android:max="100"
  21. android:progress="10"
  22. android:layout_marginBottom="20dp"
  23. />
  24. <TextView
  25. android:id="@+id/progress"
  26. android:layout_width="wrap_content"
  27. android:layout_height="wrap_content"
  28. android:gravity="center"
  29. android:layout_marginBottom="20dp"
  30. android:text="10%"/>
  31. <TextView
  32. android:id="@+id/down"
  33. android:layout_width="wrap_content"
  34. android:layout_height="wrap_content"
  35. android:gravity="center"
  36. android:paddingTop="10dp"
  37. android:paddingBottom="10dp"
  38. android:paddingLeft="30dp"
  39. android:paddingRight="30dp"
  40. android:background="@color/colorAccent"
  41. android:text="立即下载"/>
  42. </LinearLayout>

开始运行,进行版本适配。

1、适配6.0 动态权限

targetSdkVersion 23

此时,运行报错:

  1. java.lang.SecurityException:
  2. No permission to write to /storage/emulated/0/Download/app-release.apk:
  3. Neither user 10484 nor current process has android.permission.WRITE_EXTERNAL_STORAGE.

没有读写权限。下面进行添加申请权限代码:

AndroidManifest.xml 中:

  1. <uses-permission android:name="android.permission.INTERNET" />;
  2. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>;

MainActivity.class 中添加

  1. /**
  2. * 请求权限
  3. */
  4. int Code_PERMISSION = 0;
  5. /**
  6. * 权限申请
  7. * @param ManifestPermission
  8. * @param CODE
  9. * @return
  10. */
  11. private boolean requestPermission(final String ManifestPermission, final int CODE) {
  12. //1. 检查是否已经有该权限
  13. if (ContextCompat.checkSelfPermission(this,ManifestPermission) != PackageManager.PERMISSION_GRANTED) {
  14. if (ActivityCompat.shouldShowRequestPermissionRationale(this,ManifestPermission)) {
  15. new AlertDialog.Builder(this)
  16. .setTitle("权限申请")
  17. .setMessage("亲,没有权限我会崩溃,请把权限赐予我吧!")
  18. .setPositiveButton("赏给你", new DialogInterface.OnClickListener() {
  19. @Override
  20. public void onClick(DialogInterface dialog, int which) {
  21. dialog.cancel();
  22. // 用户同意 ,再次申请
  23. ActivityCompat.requestPermissions(MainActivity.this, new String[]{ManifestPermission}, CODE);
  24. }
  25. })
  26. .setNegativeButton("就不给", new DialogInterface.OnClickListener() {
  27. @Override
  28. public void onClick(DialogInterface dialog, int which) {
  29. dialog.cancel();
  30. // 用户拒绝 ,如果APP必须有权限否则崩溃,那就继续重复询问弹框~~
  31. }
  32. }).show();
  33. } else {
  34. //2. 权限没有开启,请求权限
  35. ActivityCompat.requestPermissions(this,
  36. new String[]{ManifestPermission}, CODE);
  37. }
  38. } else {
  39. //3. 权限已开,处理逻辑
  40. return true;
  41. }
  42. return false;
  43. }
  44. //4. 接收申请成功或者失败回调
  45. @Override
  46. public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
  47. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  48. if (requestCode == Code_PERMISSION) {
  49. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  50. //权限被用户同意,做相应的事情
  51. onDownBegin();
  52. } else {
  53. //权限被用户拒绝,做相应的事情
  54. Toast.makeText(this,"拒绝了权限",Toast.LENGTH_SHORT);
  55. }
  56. }
  57. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  58. }

在点击下载时候进行权限申请。可以执行下载了,但是最后安装还是报错了:

  1. Caused by: 
  2. android.content.ActivityNotFoundException:
  3. No Activity found to handle Intent {
  4. act=android.intent.action.VIEW typ=application/vnd.android.package-archive flg=0x10000000

原因是在Android6.0以下和Android6.0上,通过DownloadManager 获取到的Uri不一样。区别如下:

  • Android 6.0以下版本:getUriForDownloadedFile得到的为:file:///storage/emulated/0/Android/data/packgeName/files/Download/xxx.apk; 
  •  Android 6.0时:getUriForDownloadedFile得到的值为: content://downloads/my_downloads/10 。
  1. //通过downLoadId查询下载的apk,解决6.0以后安装的问题
  2. public static File queryDownloadedApk(Context context, long downloadId) {
  3. File targetApkFile = null;
  4. DownloadManager downloader = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
  5. if (downloadId != -1) {
  6. DownloadManager.Query query = new DownloadManager.Query();
  7. query.setFilterById(downloadId);
  8. query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
  9. Cursor cur = downloader.query(query);
  10. if (cur != null) {
  11. if (cur.moveToFirst()) {
  12. String uriString = cur.getString(cur.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
  13. if (!TextUtils.isEmpty(uriString)) {
  14. targetApkFile = new File(Uri.parse(uriString).getPath());
  15. }
  16. }
  17. cur.close();
  18. }
  19. }
  20. return targetApkFile;
  21. }

在广播监听安装app处添加6.0-7.0代码:

  1. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 6.0以下
  2. uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);
  3. installPackge(context,intentInstall,uri);
  4. } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 6.0 - 7.0
  5. File apkFile = queryDownloadedApk(context, completeDownLoadId);
  6. uri = Uri.fromFile(apkFile);
  7. installPackge(context,intentInstall,uri);
  8. }

 

2、适配Android 7.0 解析包时出现问题

从Android 7.0开始,不再允许在app中把file:// Uri暴露给其他app,否则应用会抛出FileUriExposedException。异常如下:

  1. E/AndroidRuntime: FATAL EXCEPTION: Timer-0
  2. Process: com.demo.aiyang.downloadmanger, PID: 7985
  3. android.os.FileUriExposedException: file:///storage/emulated/0/Download/app-release.apk exposed beyond app through Intent.getData()

原因在于,Google认为使用file:// Uri存在一定的风险。比如,文件是私有的,其他app无法访问该文件,或者其他app没有申请READ_EXTERNAL_STORAGE运行时权限。使用FileProvider生成content:// Uri来替代file:// Uri,如下图:


使用FileProvider解决上述异常。

(1)声明FileProvider
首先在Manifest.xml文件中申明FileProvider。

  1. //记得替换成你项目包名
  2. <provider
  3. android:name="android.support.v4.content.FileProvider"
  4. android:authorities="你的包名.FileProvider"
  5. android:exported="false"
  6. android:grantUriPermissions="true">
  7. <meta-data
  8. android:name="android.support.FILE_PROVIDER_PATHS"
  9. android:resource="@xml/file_paths" />
  10. </provider>
  • android:name:固定写法;
  • android:authorities:可自定义,是用来标识该provider的唯一标识;
  • android:exported:必须设置成 false,否则运行时报错java.lang.SecurityException: Provider must not be exported ;
  • android:grantUriPermissions:用来控制共享文件的访问权限;
  • <meta-data>节点中的android:resource指定了共享文件的路径。

(2) 添加file_paths.xml文件到res的xml文件下

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <paths xmlns:android="http://schemas.android.com/apk/res/android">
  3. <root-path name="root" path="" />
  4. <files-path name="files" path="" />
  5. <cache-path name="cache" path="" />
  6. <external-path name="external" path="" />
  7. <external-files-path name="name" path="path" />
  8. <external-cache-path name="name" path="path" />
  9. </paths>

根元素<paths>是固定的,属性path表示子目录,内部元素节点如下:

  • <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()

此处,我们apk下载存放在Sdk外部存储区目录下  :

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <paths xmlns:android="http://schemas.android.com/apk/res/android">
  3. <!-- 该方式提供在应用的外部存储区根目录的下的文件。
  4. 它对应Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)返回的路径。
  5. eg:”/storage/emulated/0/Android/data/com.jph.simple/files”
  6. -->
  7. <external-path name="download" path="" />
  8. </paths>

上述代码中path="",是有特殊意义的,它代码根目录。如果你将path设为path="pictures",那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。

(3)在广播监听APP安装代码中添加兼容>7.0

  1. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 兼容6.0以下
  2. uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);
  3. installPackge(context,intentInstall,uri);
  4. } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 兼容6.0-70
  5. File apkFile = queryDownloadedApk(context, completeDownLoadId);
  6. uri = Uri.fromFile(apkFile);
  7. installPackge(context,intentInstall,uri);
  8. } else {//兼容7.0
  9. intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 给目标应用一个临时授权
  10. File file= new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS),"app-release.apk");
  11. Uri uri = FileProvider.getUriForFile(context, getPackageName() + ".fileProvider",file);
  12. installPackge(this,intentInstall,uri);
  13. }

其中:

file路径:/storage/emulated/0/Download/app-release.apk

使用FileProvide得到uri为:content://com.demo.aiyang.demo.fileProvider/download/Download/app-release.apk  

这里,讲一个坑。

在下载成功后,安装时出错,如下图所示

错误获取File路径代码如下: 

  1. //改正前写法
  2. File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app-release.apk");
  3. //file路径:/storage/emulated/0/Android/data/com.demo.aiyang.demo/files/Download/app-release.apk
  4. //改正后写法
  5. File file= new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
  6. "app-release.apk");
  7. //file路径:/storage/emulated/0/Download/app-release.apk

因为FileProvider不支持sdcard目录下的文件共享。Android 7.0 DownloadManager与FileProvider的坑

(一把辛酸泪!这个问题解决了很久,试了很多方法,比较坑的是将文件下载后再写入到私有目录!)

 

3、适配Android 8.0:未知来源的应用权限

Android 8.0的手机会出现在线更新不了新版本,华为荣耀V10手机测试apk下载完成后直接白屏提示“解析包时出现问题”。原因是Android8.0以上未知来源的应用是不可以通过代码来执行安装的(允许手动安装)。Google这么做是为了防止不合法APK安装侵犯了用户权益。适配如下:

(1) 在清单文件中申明权限

<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />


(2)监听apk下载状态的广播中添加代码:

  1. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 兼容6.0以下
  2. uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);
  3. installPackge(context,intentInstall,uri);
  4. } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 兼容6.0-70
  5. File apkFile = queryDownloadedApk(context, completeDownLoadId);
  6. uri = Uri.fromFile(apkFile);
  7. installPackge(context,intentInstall,uri);
  8. } else {
  9. Log.i("aaa", ">7.0");
  10. InstallPackgeAPI28(context);// 兼容Android 8.0
  11. }

兼容Android 8.0 是否有安装权限 

  1. /**
  2. * 兼容 8.0 未知来源应用安装
  3. */
  4. int Code_INSTALLPACKAGES = 1;
  5. @RequiresApi(api = Build.VERSION_CODES.O)
  6. private void startInstallPermissionSettingActivity() {
  7. Uri packageURI = Uri.parse("package:" + getPackageName());
  8. Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);
  9. startActivityForResult(intent, Code_INSTALLPACKAGES);
  10. }
  11. @Override
  12. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  13. super.onActivityResult(requestCode, resultCode, data);
  14. if (requestCode == Code_INSTALLPACKAGES){
  15. InstallPackgeAPI28(this);
  16. }
  17. }
  18. private void InstallPackgeAPI28(Context context){
  19. Intent intentInstall = new Intent();
  20. intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 给目标应用一个临时授权
  21. File file= new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),"app-release.apk");
  22. Uri uri = FileProvider.getUriForFile(context, getPackageName() + ".fileProvider", file);
  23. boolean isInstallPermission = false;//是否有8.0安装权限
  24. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  25. isInstallPermission = getPackageManager().canRequestPackageInstalls();
  26. if (isInstallPermission) {
  27. installPackge(this,intentInstall,uri);
  28. } else {
  29. new AlertDialog.Builder(this)
  30. .setTitle("权限申请")
  31. .setMessage("亲,没有权限我会崩溃,请把权限赐予我吧!")
  32. .setPositiveButton("赏给你", new DialogInterface.OnClickListener() {
  33. @Override
  34. public void onClick(DialogInterface dialog, int which) {
  35. dialog.cancel();
  36. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  37. startInstallPermissionSettingActivity();
  38. }
  39. }
  40. }).setNegativeButton("取消",null ).show();
  41. }
  42. }else{
  43. installPackge(this,intentInstall,uri);
  44. }
  45. }

运行结果:

4、适配Android 9.0:Https网络请求

前面代码基本上都已经算非常完整了,可以在大部分手机上都可以正常安装和使用。但是,我发现依然有很多网友留言说让我兼容9.0。于是乎,我自己也用9.0的测试手机运行了一下,可以下载安装啊!没问题啊!

呵呵。。。终究是我太大意了。 demo 里的  targetSdkVersion  并没有大于  API 28 Android 9.0 。因此,也就不会出现什么问题。审视一下一边代码就明白了,无非就是无法下载apk的问题。

Android P 9.0 的系统上面默认所有Http的请求都被阻止了,就没办法访问了网络了。解决这个问题最好当然是把Http换成Https了。然鹅,并不是每个公司都做到了。还有一个办法,就是通过在AnroidManifest.xml中的application标签下设置如下属性即可。

android:usesCleartextTraffic="true"

完。2019年12月24日16:55:03


欢迎订阅公众号:数据结构、算法、面试经验、每日新闻、闲聊趣文等。欢迎一起学习!

欢迎加入Android QQ交流学习群:

 

参考链接:

https://www.jianshu.com/p/c58d17073e65

https://blog.csdn.net/yq6073025/article/details/52934326

https://www.jianshu.com/p/121bbb07cb07

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/496968
推荐阅读
相关标签
  

闽ICP备14008679号