Android 5.0、6.0、7.0、8.0、9.0 新特性,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的获取一个版本比一个版本艰难,如有好的方法请指出。
public static String getUUID() {
String serial = null;String m_szDevIDShort = "35" +Build.BOARD.length() % 10 + Build.BRAND.length() % 10 +Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 +Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 +Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 +Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 +Build.TAGS.length() % 10 + Build.TYPE.length() % 10 +Build.USER.length() % 10; //13 位try {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {serial = android.os.Build.getSerial();} else {serial = Build.SERIAL;}//API>=9 使用serial号return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
} catch (Exception exception) {//serial需要一个初始化serial = "serial"; // 随便一个初始化
}//使用硬件信息拼凑出来的15位号码return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
}
- 无线和蓝牙扫描权限:使用 WLAN API 和 Bluetooth API 时的权限,这两种权限的变更影响较少。
最后,版本升级踩坑记——DownloadManager
DownloadManager使用介绍参考:https://blog.csdn.net/csdn_aiyang/article/details/64126379
MainActivity.class 代码
public class MainActivity extends AppCompatActivity {private TextView down;private TextView progress;private ProgressBar pb_update;private DownloadManager downloadManager;private DownloadManager.Request request;public static String downloadUrl = "http://www.wanandroid.com/blogimgs/ecb4c318-42f3-454a-a6c4-615ad16f35bd.apk";private DownloadReceiver completeReceiver;private final Uri CONTENT_URI = Uri.parse("content://downloads/my_downloads");private DownloadChangeObserver observer;long id;Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Bundle bundle = msg.getData();int pro = bundle.getInt("pro");pb_update.setProgress(pro);progress.setText(String.valueOf(pro) + "%");}};class DownloadChangeObserver extends ContentObserver {public DownloadChangeObserver(Handler handler) {super(handler);}@Overridepublic void onChange(boolean selfChange) {super.onChange(selfChange);updateView();}}class DownloadReceiver extends BroadcastReceiver {@Overridepublic void onReceive(final Context context, final Intent intent) {Log.i("aaa", "广播监听");long completeDownLoadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);Intent intentInstall = new Intent();Uri uri = null;if (completeDownLoadId == id) {if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 兼容6.0以下uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);installPackge(context,intentInstall,uri);} }}}/*** 安装APK* @param context* @param intentInstall* @param uri*/private void installPackge(Context context,Intent intentInstall,Uri uri){intentInstall.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intentInstall.setAction(Intent.ACTION_VIEW);// 安装应用Log.i("aaa", "app下载完成了,开始安装。。。"+uri);intentInstall.setDataAndType(uri, "application/vnd.android.package-archive");context.startActivity(intentInstall);}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);down = (TextView) findViewById(R.id.down);progress = (TextView) findViewById(R.id.progress);pb_update = (ProgressBar) findViewById(R.id.pb_update);down.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {LoadApp();//requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,Code_PERMISSION);}});}private void LoadApp() {//创建下载对象downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);request = new DownloadManager.Request(Uri.parse(downloadUrl));request.setTitle("app-release.apk");request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);request.setAllowedOverRoaming(false);request.setMimeType("application/vnd.android.package-archive");request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);//设置文件存放路径request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "app-release.apk");}private void updateView() {int[] bytesAndStatus = new int[]{0, 0, 0};DownloadManager.Query query = new DownloadManager.Query().setFilterById(id);Cursor c = null;try {c = downloadManager.query(query);if (c != null && c.moveToFirst()) {//已经下载的字节数bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));//总需下载的字节数bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));}} finally {if (c != null) {c.close();}}int pro = (bytesAndStatus[0] * 100) / bytesAndStatus[1];Message msg = Message.obtain();Bundle bundle = new Bundle();bundle.putInt("pro", pro);msg.setData(bundle);handler.sendMessage(msg);Log.i("aaa", "下载进度:" + bytesAndStatus[0] + "/" + bytesAndStatus[1]);}//开始下载private void onDownBegin() {try {id = downloadManager.enqueue(request);} catch (Exception e) {e.printStackTrace();} finally {//更新UIobserver = new DownloadChangeObserver(handler);getContentResolver().registerContentObserver(CONTENT_URI, true, observer);//安装completeReceiver = new DownloadReceiver();registerReceiver(completeReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));down.setText("正在下载");down.setClickable(false);}}@Overrideprotected void onDestroy() {super.onDestroy();if (observer != null) {getContentResolver().unregisterContentObserver(observer);unregisterReceiver(completeReceiver);}}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:gravity="center"android:layout_height="match_parent"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginBottom="20dp"android:text="下载进度"/><ProgressBarandroid:id="@+id/pb_update"style="?android:attr/progressBarStyleHorizontal"android:layout_width="match_parent"android:layout_height="10dp"android:layout_marginLeft="10dp"android:layout_marginRight="10dp"android:layout_gravity="center_horizontal"android:layout_marginTop="10dp"android:max="100"android:progress="10"android:layout_marginBottom="20dp"/><TextViewandroid:id="@+id/progress"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:layout_marginBottom="20dp"android:text="10%"/><TextViewandroid:id="@+id/down"android:layout_width="wrap_content"android:layout_height="wrap_content"android:gravity="center"android:paddingTop="10dp"android:paddingBottom="10dp"android:paddingLeft="30dp"android:paddingRight="30dp"android:background="@color/colorAccent"android:text="立即下载"/>
</LinearLayout>
开始运行,进行版本适配。
1、适配6.0 动态权限
targetSdkVersion 23
此时,运行报错:
java.lang.SecurityException:
No permission to write to /storage/emulated/0/Download/app-release.apk:
Neither user 10484 nor current process has android.permission.WRITE_EXTERNAL_STORAGE.
没有读写权限。下面进行添加申请权限代码:
AndroidManifest.xml 中:
<uses-permission android:name="android.permission.INTERNET" />;<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>;
MainActivity.class 中添加
/*** 请求权限*/int Code_PERMISSION = 0;/*** 权限申请* @param ManifestPermission* @param CODE* @return*/private boolean requestPermission(final String ManifestPermission, final int CODE) {//1. 检查是否已经有该权限if (ContextCompat.checkSelfPermission(this,ManifestPermission) != PackageManager.PERMISSION_GRANTED) {if (ActivityCompat.shouldShowRequestPermissionRationale(this,ManifestPermission)) {new AlertDialog.Builder(this).setTitle("权限申请").setMessage("亲,没有权限我会崩溃,请把权限赐予我吧!").setPositiveButton("赏给你", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {dialog.cancel();// 用户同意 ,再次申请ActivityCompat.requestPermissions(MainActivity.this, new String[]{ManifestPermission}, CODE);}}).setNegativeButton("就不给", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {dialog.cancel();// 用户拒绝 ,如果APP必须有权限否则崩溃,那就继续重复询问弹框~~}}).show();} else {//2. 权限没有开启,请求权限ActivityCompat.requestPermissions(this,new String[]{ManifestPermission}, CODE);}} else {//3. 权限已开,处理逻辑return true;}return false;}//4. 接收申请成功或者失败回调@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == Code_PERMISSION) {if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {//权限被用户同意,做相应的事情onDownBegin();} else {//权限被用户拒绝,做相应的事情Toast.makeText(this,"拒绝了权限",Toast.LENGTH_SHORT);}}super.onRequestPermissionsResult(requestCode, permissions, grantResults);}
在点击下载时候进行权限申请。可以执行下载了,但是最后安装还是报错了:
Caused by:
android.content.ActivityNotFoundException:
No Activity found to handle Intent {
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 。
//通过downLoadId查询下载的apk,解决6.0以后安装的问题public static File queryDownloadedApk(Context context, long downloadId) {File targetApkFile = null;DownloadManager downloader = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);if (downloadId != -1) {DownloadManager.Query query = new DownloadManager.Query();query.setFilterById(downloadId);query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);Cursor cur = downloader.query(query);if (cur != null) {if (cur.moveToFirst()) {String uriString = cur.getString(cur.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));if (!TextUtils.isEmpty(uriString)) {targetApkFile = new File(Uri.parse(uriString).getPath());}}cur.close();}}return targetApkFile;}
在广播监听安装app处添加6.0-7.0代码:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 6.0以下uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);installPackge(context,intentInstall,uri);} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 6.0 - 7.0File apkFile = queryDownloadedApk(context, completeDownLoadId);uri = Uri.fromFile(apkFile);installPackge(context,intentInstall,uri);}
2、适配Android 7.0 解析包时出现问题
从Android 7.0开始,不再允许在app中把file:// Uri暴露给其他app,否则应用会抛出FileUriExposedException。异常如下:
E/AndroidRuntime: FATAL EXCEPTION: Timer-0Process: com.demo.aiyang.downloadmanger, PID: 7985android.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。
//记得替换成你项目包名<providerandroid:name="android.support.v4.content.FileProvider"android:authorities="你的包名.FileProvider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths" /></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文件下
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><root-path name="root" path="" /><files-path name="files" path="" /><cache-path name="cache" path="" /><external-path name="external" path="" /><external-files-path name="name" path="path" /><external-cache-path name="name" path="path" />
</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外部存储区目录下 :
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><!-- 该方式提供在应用的外部存储区根目录的下的文件。它对应Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)返回的路径。eg:”/storage/emulated/0/Android/data/com.jph.simple/files”--><external-path name="download" path="" /></paths>
上述代码中path="",是有特殊意义的,它代码根目录。如果你将path设为path="pictures",那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。
(3)在广播监听APP安装代码中添加兼容>7.0
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 兼容6.0以下uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);installPackge(context,intentInstall,uri);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 兼容6.0-70File apkFile = queryDownloadedApk(context, completeDownLoadId);uri = Uri.fromFile(apkFile);installPackge(context,intentInstall,uri);
} else {//兼容7.0intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 给目标应用一个临时授权File file= new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS),"app-release.apk");Uri uri = FileProvider.getUriForFile(context, getPackageName() + ".fileProvider",file);installPackge(this,intentInstall,uri);
}
其中:
file路径:/storage/emulated/0/Download/app-release.apk
使用FileProvide得到uri为:content://com.demo.aiyang.demo.fileProvider/download/Download/app-release.apk
这里,讲一个坑。
在下载成功后,安装时出错,如下图所示
错误获取File路径代码如下:
//改正前写法File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "app-release.apk");
//file路径:/storage/emulated/0/Android/data/com.demo.aiyang.demo/files/Download/app-release.apk//改正后写法
File file= new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
"app-release.apk");
//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下载状态的广播中添加代码:
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 兼容6.0以下uri = downloadManager.getUriForDownloadedFile(completeDownLoadId);installPackge(context,intentInstall,uri);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // 兼容6.0-70File apkFile = queryDownloadedApk(context, completeDownLoadId);uri = Uri.fromFile(apkFile);installPackge(context,intentInstall,uri);
} else {Log.i("aaa", ">7.0");InstallPackgeAPI28(context);// 兼容Android 8.0
}
兼容Android 8.0 是否有安装权限
/*** 兼容 8.0 未知来源应用安装*/int Code_INSTALLPACKAGES = 1;@RequiresApi(api = Build.VERSION_CODES.O)private void startInstallPermissionSettingActivity() {Uri packageURI = Uri.parse("package:" + getPackageName());Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI);startActivityForResult(intent, Code_INSTALLPACKAGES);}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == Code_INSTALLPACKAGES){InstallPackgeAPI28(this);}}private void InstallPackgeAPI28(Context context){Intent intentInstall = new Intent();intentInstall.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 给目标应用一个临时授权File file= new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),"app-release.apk");Uri uri = FileProvider.getUriForFile(context, getPackageName() + ".fileProvider", file);boolean isInstallPermission = false;//是否有8.0安装权限if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {isInstallPermission = getPackageManager().canRequestPackageInstalls();if (isInstallPermission) {installPackge(this,intentInstall,uri);} else {new AlertDialog.Builder(this).setTitle("权限申请").setMessage("亲,没有权限我会崩溃,请把权限赐予我吧!").setPositiveButton("赏给你", new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {dialog.cancel();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {startInstallPermissionSettingActivity();}}}).setNegativeButton("取消",null ).show();}}else{installPackge(this,intentInstall,uri);}}
运行结果:
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
Android 5.0、6.0、7.0、8.0、9.0 新特性,DownloadManager踩坑记相关推荐
- 东八区转为0时区_踩坑记 | Flink 天级别窗口中存在的时区问题
❝ 本系列每篇文章都是从一些实际的 case 出发,分析一些生产环境中经常会遇到的问题,抛砖引玉,以帮助小伙伴们解决一些实际问题.本文介绍 Flink 时间以及时区问题,分析了在天级别的窗口时会遇到的 ...
- Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验
Android群英传笔记--第十二章:Android5.X 新特性详解,Material Design UI的新体验 第十一章为什么不写,因为我很早之前就已经写过了,有需要的可以去看 Android高 ...
- android 重新编译内核,[原创]华为内核重新编译踩坑记
华为内核重新编译踩坑记 前几天有朋友找我帮他跟一个加密,由于js太乱了,想从app入手,于是打算用frida脱壳,结果发现报错 Failed to attach: remote_write PTRAC ...
- android小程序_小程序踩坑记
小程序踩坑记 希望这个文章能尽量记录下小程序的那些坑,避免开发者们浪费自己的生命来定位到底是自己代码导致的还是啥神秘的字节跳变原因. 前记 小程序大多数坑是同一套代码在不同平台上表现不一致导致的,微信 ...
- android p preview_细数 Android P 开发者预览版中最不能错过的新特性
原标题:细数 Android P 开发者预览版中最不能错过的新特性 2018年安卓巴士全球开发者论坛-重庆站 [线下活动]春天到了 跟小编一起去重庆嗨皮吧~ Android P 应用适配新特性1.Pr ...
- CentOS 8.0 今天已正式发布!一起看看有哪些新特性
CentOS 8发行了吗?CentOS 8出来没?CentOS 8发行进度最近大家非常关心! 好消息:CentOS 8.0 官方终于在今天(2019.9.25)正式发布了!!! CentOS 8 下载 ...
- OpenCV4.0.1/4.0.0/3.4.2 + Contrib + Qt5.9 + CMake3.12.1编译及踩坑笔记、Qt5+OpenCV配置、代码验证、效果图、福利彩蛋
Table of Contents 前言 Windows 10, OpenCV4.0.1, Qt5.9.3, CMake3.12.1, MinGW5.3.0 Windows 10, OpenCV4.0 ...
- Android 实现全屏截图、剪裁图片、分享图片至其他应用进程功能(踩坑记录)
Android 实现全屏截图.剪裁.分享功能 项目中需要用到 截图分享 的功能,特此写下查询资料过程中的踩坑记录. android 26以上,google 官方文档支持 PixelCopy 实现截图效 ...
- Notadd 2.0 全新 Node.js 版本~ (开发中) [从 PHP 到 node 的踩坑记]
对于 Notadd 我们本来期望它实现更多... 尽管我们也尝试做了很多努力,但是由于 PHP 本身的局限,以及考虑到开发环境配置的复杂程度,最终使用了折中方案. 接下来,我们谈谈整个技术选型历程, ...
- React-Native android在windows下的踩坑记
坑很多,跳之前做好准备.没有VPN的同学请浏览完本文后慎行. 你需要先安装最新版本的node.js(我最后使用的是v4.1.2),前往官网下载>> 注:我win7已经安装过Visual S ...
最新文章
- PHP判断变量内容是什么编码(gbk?utf-8) mb_detect_encoding
- linux 扫描mipi设备,VS-RK3399 在linux系统下面调试Mipi camera接口介绍
- 产品总监基本功:从零到壹
- IOS开发学习----给表视图设置缩进级别
- 让天之痕窗口化运行!
- 电子/自动化专业常用软件介绍
- Laravel和Vue提供支持的VueFileManager v2.0.2私有云盘多用户网盘程序源码
- Leetcode Hot-100
- springboot注解实现自动插入创建时间和更新时间到数据库
- python实现文本审核_百度AI文本审核API使用说明
- react引入svg图片
- SSM毕设项目校园书蜀黍易购平台xk9g6(java+VUE+Mybatis+Maven+Mysql)
- Android视频播放器横竖屏切换时遇到的问题记录
- oracle有dba角色用户,ORACLE管理-查看拥有DBA角色的用户
- 2021年茶艺师(中级)考试报名及茶艺师(中级)新版试题
- decode_audio.c 解读/decode_video.c 解读
- 望(dream-coastline 3.0)
- 华大半导体HC32F4A0笔记(三),RS485通信,使用串口USART1,DMA接收
- QQ JS省市区三级联动
- 雷达遥感原理;侧视雷达成像系统;雷达回波强度的影响因素;雷达遥感及雷达图像的特征