前言
由于项目要上应用宝,必须设置targetSdk>=26,所以把以前项目的targetSdk=22的改成了26,要开始处理Android 6.0的动态权限,7.0的FileProvider,8.0的通知栏和安装未知应用的权限。对老项目修改最麻烦的是,你得一个一个找哪些地方用到了危险权限,也可以直接设为26后,关闭所有权限,对app每个功能都点点,奔溃就是缺少权限了。在项目中,我是使用RxPermission管理权限。
6.0 Android权限
6.0动态申请危险权限,先在AndroidManifest.xml里声明,再在代码里申请。危险权限分9组,分别是联系人、通话、日历、相机、传感器、定位、存储、音视频/录音、短信权限。
动态权限请求和处理流程
//1.判断是否已经授权
ContextCompat.checkSelfPermission(context,permissions[i]) != PackageManager.PERMISSION_GRANTED)
//2.请求权限
ActivityCompat.requestPermissions(context,permissions,mRequestCode);
//3.请求权限的回调 grantResults值为0允许,1拒绝
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
//4.这个方法的理解是,是否需要告诉用户请求权限的原因,那么用户直接拒绝时,最好给用户一个提示说明该权限用来做什么的,所以返回true。而用户允许了权限 or 点了不再询问,此时没必要再提示用户请求权限的原因,所以返回false。所以在回调中,如果获取权限失败,合理的做法是判断是否需要给用户一个提示说明。
ActivityCompat.shouldShowRequestPermissionRationale(context,permissions[i])
RxPermission简单用法
new RxPermissions(this).request(Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE)
.subscribe(granted -> {
if (granted) {//允许
} else { //拒绝
ToastUtils.showToast("为了功能正常使用,请您开启存储权限");
}
});
7.0 FileProvider
7.0以上的系统,应用间共享文件要通过FileProvider,如果还像以前那样用 [file://URI](file://uri/) 就会触发 FileUriExposeException。其实这个在引用第三方库的时候就看到了,比如腾讯的Bugly。可以全局搜索Uri.fromFile(xxx)定位到要修改的地方,比如app更新下载后的安装、拍照、通知图库更新等地方可能用到。
使用流程
使用流程很简单,分3步。
1)在AndroidMainfest中声明。
<!--
解释:
android:authorities="${applicationId}.fileProvider" 就是 app的包名.fileProvider
android:exported="false" 为false,不允许被另一个Application的组件启动
android:grantUriPermissions="true" 为true,才能获取临时共享权限。
-->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
2)在res文件夹下,创建xml文件夹,在里面创建xml文件,我创建的是 provider_paths.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<!--
对应的关系如下:
<external-path/> Environment.getExternalStorageDirectory()
<files-path/> getFilesDir()
<cache-path/> getCacheDir()
<external-cache-path/> getExternalCacheDir()
<external-files-path/> getExternalFilesDir()
<root-path/> 这个应该是Android设备的根目录
path="xxx"表示你要共享的文件夹
-->
<!--
下面这句代表的目录即为:Environment.getExternalStorageDirectory()/com.sz.dzh.dandroidsummary/download/
-->
<paths>
<external-path name="download_apk_path" path="com.sz.dzh.dandroidsummary/download/"/>
</paths>
3)代码中使用FileProvider,判断是否>=24(也就是Android 7.0),大于调用FileProvider。这里直接放出拉起系统安装页面的代码,关键就是FileProvider.getUriForFile(...)这个方法。
/**
* 判断是否在7.0以上,7.0以上要用FileProvider
*/
private void installApp() {
File file = new File(DownloadIntentService.downLoadPath);
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri apkUri;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
//参数2就是AndroidManifest.xml中provider的authorities
apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileProvider", file);
//临时授权读该Uri代表的文件的权限,不然安装的时候会出现“解析软件包出现问题”。
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}else{
apkUri = Uri.fromFile(file);
}
//注意别设置成setFlags(...)了,不然前面的addFlags就清掉了。
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent);
}
8.0 Andorid的通知栏和未知应用安装权限
NotificationChannel
targetSdk >= 26 时,系统不会默认添加Channel,反之低版本则会默认添加,所以要自己创建Channel。当创建Channel后,以前在Builder设置的如震动、声音等可能都会无效,要在Channel设置,我这里只是简单的显示通知栏,所以没有设置震动、声音等。
private final String NOTIFICATION_CHANNEL_ID = "com.sz.dzh.dandroidsummary";
private final String NOTIFICATION_CHANNEL_NAME = "apk_download_channel";
private NotificationManager mNotifyManager; //通知管理类
private Notification mNotification;
private int downloadId = 101;
/**
* 创建通知栏
* targetSdk >= 26 时,系统不会默认添加Channel,反之低版本则会默认添加;
* @param remoteViews
*/
private void createNotification(RemoteViews remoteViews){
//1.创建通知通道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//参数:通道id、名字、优先级。
NotificationChannel notifyChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT);
notifyChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
mNotifyManager.createNotificationChannel(notifyChannel);
}
//2.创建Builder对象
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID);
} else {
builder = new Notification.Builder(this);
}
builder.setContent(remoteViews)
.setTicker("正在下载")
.setSmallIcon(R.mipmap.ic_launcher);
//3.将Builder对象转变成普通的notification
mNotification = builder.build();
mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotifyManager.notify(downloadId, mNotification);
}
未知应用安装权限
在Android8.0之前,未知应用安装权限是默认开启的。在Android8.0之后,未知应用安装权限默认关闭,且权限入口隐藏。未知应用就是没有在对应的应用市场上线的应用。所以在调安装方法前,要先询问是否已开启了未知应用安装的权限,没开启,引导用户去打开。
//记得在AndroidManifest.xml 中 声明权限
//<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
private final int REQUEST_CODE_APP_INSTALL = 312;
/**
* 因为涉及权限申请,所以把安装app的流程放到Activity里来。
* 也可以考虑在Service中动态获取权限
* 此处Service下载完成后,通过EventBus通知activity可以开始安装
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onDownloadSuccess(EventBean eventBean) {
if(eventBean.getCode() == Constant.EventCode.DOWNLOAD_APK_SUCCESS){
//8.0以上,判断未知应用安装权限是否开启,没开启引导用户去设置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
boolean hasInstallPermission = getPackageManager().canRequestPackageInstalls();
if (!hasInstallPermission) {
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
startActivityForResult(intent,REQUEST_CODE_APP_INSTALL);
return;
}
}
installApp();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == REQUEST_CODE_APP_INSTALL){
//回调再查一次是否开启权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
boolean hasInstallPermission = getPackageManager().canRequestPackageInstalls();
if (hasInstallPermission) {
installApp();
}else{
ToastUtils.showToast("若要安装,请允许未知应用安装权限");
}
}
}
}
/**
* 判断是否在7.0以上,7.0以上要用FileProvider
*/
private void installApp() {
File file = new File(DownloadIntentService.downLoadPath);
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri apkUri;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
//参数2就是AndroidManifest.xml中provider的authorities
apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileProvider", file);
//临时授权读该Uri代表的文件的权限,不然安装的时候会出现“解析软件包出现问题”。
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}else{
apkUri = Uri.fromFile(file);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
startActivity(intent);
}
遇到的问题
线上app的targetSdk是22,在把targetSdk改为26且做了上述修改后,我想试一下app更新下载,就把versionCode改低了,然后下载正常且也拉起了安装页面,但点击安装后,页面就显示了五个字——“应用未安装”,(一加手机测试)除此之外没有没有任何解释。。。
原因:低版本的targetSdkVersion不能覆盖高版本的targetSdkVersion。
参考
FileProvider
https://www.jianshu.com/p/47fcd7873f39
8.0通知栏不显示问题、apk更新安装
https://www.jianshu.com/p/ca88fcbbf6c5
https://www.cnblogs.com/nesger/p/9483582.html
NotificaitionChannel适配填坑指南
https://www.jianshu.com/p/99bc32cd8ad6
网友评论