Android版本适配(一)
前面做项目用到了Notification涉及到Android版本适配问题,然后总结了下Android6.0—9.0的变化及其适配。下面介绍Android6.0的适配。
Android6.0适配
在6.0之前的版本,我们使用某项权限只需要在Manifest中声明就可以使用了,但是在6.0之后,某些权限(例如:通讯录、位置、相机)不仅需要在Manifest中声明,还要在app运行的时候动态地申请并被用户允许才能正常使用,这类权限称为危险权限(Dangerous Permission)。与其对应的是正常权限(Normal Permissions),正常权限(例如:蓝牙,网络)只需要在清单文件声明即可。
注意:只有危险权限需要动态申请。危险权限很多被分为9组,不需要一个个申请,对于同一组内的权限,只要一个被同意,其他的都会被同意。
<!-- 危险权限 start -->
<!--PHONE-->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.ADD_VOICEMAIL"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<!--CALENDAR-->
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<!--CAMERA-->
<uses-permission android:name="android.permission.CAMERA"/>
<!--CONTACTS-->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!--LOCATION-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--MICROPHONE-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--SENSORS-->
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<!--SMS-->
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<!--STORAGE-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 危险权限 Permissions end -->
适配需要用到的方法
- ContextCompat.checkSelfPermission
检查应用是否具有某个危险权限。
如果应用具有此权限,方法将返回 PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。
如果应用不具有此权限,返回PackageManager.PERMISSION_DENIED,且应用必须明确向用户要求权限。
- ActivityCompat.requestPermissions
应用可以通过这个方法动态申请权限,调用后会弹出一个对话框提示用户授权所申请的权限。
- ActivityCompat.shouldShowRequestPermissionRationale
如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。
如果用户在过去拒绝了权限请求,并在权限请求系统对话框中选择了 Don't ask again 选项,此方法将返回 false。
如果设备规范禁止应用具有该权限,此方法也会返回 false。
- onRequestPermissionsResult
当应用请求权限时,系统将向用户显示一个对话框。
当用户响应时,系统将调用应用的 onRequestPermissionsResult() 方法,向其传递用户响应,处理对应的场景
适配流程 (下面以相机权限为例说明,分为以下几个步骤)
-
在Manifest中声明所需权限:
<uses-permission android:name="android.permission.CAMERA"/>
-
调用ContextCompat.checkSelfPermission()检查权限:
//checkSelfPermission()方法接受两个参数。 //第一个参数为Context对象, //第二个参数为需要进行检查的权限,类型为String。 //返回值是一个int常量,返回PackageManager.PERMISSION_GRANTED表示权限已经被授予, //返回PackageManager.PERMISSION_DENIED表示权限未被授予。 if(ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { //权限未授予,调用requestPermission()申请权限 }else{ //权限已授予 }
-
如果权限未被授予,调用ActivityCompat.requestPermissions()申请权限:
//requestPermissions()接受三个参数, //第一个参数是Context对象, //第二个参数是一个String数组,可以同时申请多个权限, //第三个参数是请求码。 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, MY_REQUEST_CODE);
-
重写Activity的onRequestPermissionsResult()处理申请回调:
//requestCode是请求码,在这里就是上面的MY_REQUEST_CODE, //permissions就是申请的权限, //grantResults是请求的结果,数组大小与permissions对应。 @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case MY_REQUEST_CODE: { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { //用户允许该权限 } else { //用户拒绝该权限 } return; } } }
Android7.0适配
(1)Android7.0新增了多窗口支持,用户可以一次在屏幕上打开两个应用。
分屏模式具有以下几个特点:
- 分屏模式不会更改 Activity 生命周期
- 在分屏模式模式下,只有一个Activity会获得焦点,其他Activity处于Paused状态
- 在分屏模式下调整窗口大小会回调onConfigurationChanged
- 如果根Activity允许多窗口模式,那么与它在同一个栈种的Activity都被允许多窗口模式(即使没有设置)。
- 某些系统 UI 自定义选项将被禁用;例如,在非全屏模式中,应用无法隐藏状态栏。
- 系统将忽略对android:screenOrientation 属性所作的更改。
适配流程
-
如果布局外层是ScrollView、RecycleView、ListView的界面,那么在分屏模式下是比较正常,可以尝试让这些界面支持分屏模式。
-
如果是固定宽高的界面,设置android:minimalHeight、android:minimalWidth来设置分屏模式下的最小宽高。
<activity android:name=".MyActivity">
<layout
android:minimalHeight="450dp"
android:minimalWidth="300dp" />
</activity> -
如果不想适配分屏模式,可以在Manifest的activity标签下设置属性android:resizeableActivity="false"
(2)Android7.0应用间共享文件被限制
在7.0版本之前,我们可以通过File://这一类Uri访问其他应用的私有文件或者让其他应用访问自己的私有文件。
在Android 7.0系统上,Android框架强制执行了StrictMode API政策,禁止向应用外公开file://URI,如果尝试传递 file:// Uri来访问其他应用的私有文件会触发 FileUriExposedException异常,如调用系统相机拍照、裁切照片、打开APK安装界面等。 Android7.0如果要在应用间共享文件,可以发送content://URI类型的Uri,并授予URI临时访问权限,进行此授权的最简单方式是使用FileProvider类。
适配流程 (下面以打开下载完的APK的实例说明,分为以下几个步骤)
-
导入v4包,在主module的build.gradle下的dependencies节点下添加依赖,版本号视情况而定。
implementation 'com.android.support:support-v4:27.1.1'
-
在 res/xml 目录下新建一个 xml 文件,用于存放应用需要共享的文件目录
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="captured_media" path="captured_media/" /> <external-path name="data" path="Android" /> <cache-path name="cache" path="appCache" /> <external-path name="external" path="" /> </paths>
name是自定义的一个别名,path是这个共享的目录,这里的path值表示共享外部私有目录file/images下的文件。如果是输入的是"."则表示共享外部私有目录下的file/目录下的所有文件。
其中<paths>元素有以下几个子节点:
<files-path>:内部存储空间应用私有目录下的 files/ 目录,等同于 Context.getFilesDir() 所获取的目录路径
<cache-path>:内部存储空间应用私有目录下的 cache/ 目录,同于 Context.getCacheDir() 所获取的目录路径;
<external-path>:外部存储空间根目录,等同于 Environment.getExternalStorageDirectory() 所获取的目录路径;
<external-files-path>外部存储空间应用私有目录下的 files/ 目录,等同于 Context.getExternalFilesDir(null) 所获取的目录路径;
<external-cache-path>:外部存储空间应用私有目录下的 cache/ 目录,等同于 Context.getExternalCacheDir();
-
在AndroidManifest中声明FileProvide
<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/file_paths"/> </provider>
exported为false,grantUriPermissions表示授予URI临时访问权限。
authorities的名字可自定义,一般为包名+FileProvide,resource就是刚刚新建的共享文件。 -
通过FileProvider,打开下载完的APK
public static Intent getOpenFileIntent(Context context, DownloadResponse downloadResponse) { File file = new File(downloadResponse.getParentPath(), downloadResponse.getFileName()); if (!file.exists()) { return null; } Intent intent = new Intent(); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setAction(Intent.ACTION_VIEW); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); Uri contentUri = FileProvider.getUriForFile(context, "com.demo.lizejun.provider", file); intent.setDataAndType(contentUri, downloadResponse.getMimeType()); } else { intent.setDataAndType(Uri.fromFile(file), downloadResponse.getMimeType()); } if (!(context instanceof Activity)) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } return intent; }
(3)Android7.0关闭了三项系统广播:网络状态变更广播、拍照广播及录像广播。
网络状态变更广播、拍照广播及录像广播
只有在通过 动态注册 的方式才能收到网络变化的广播,
在AndroidManifest.xml中静态的注册的无法收到。