Android适配全面总结(二)----版本适配
版权声明:本文为博主原创文章(部分引用他人博文,已加上引用说明),未经博主允许不得转载。https://www.jianshu.com/p/49fa8ebc0105
转载请标明出处:
https://www.jianshu.com/p/49fa8ebc0105
本文出自 AWeiLoveAndroid的博客
上一篇文章讲了 屏幕适配 http://www.jianshu.com/p/7aa34434ad4d
这一篇文章讲一下 版本适配 https://www.jianshu.com/p/49fa8ebc0105
下一篇文章讲一下 ROM适配 https://www.jianshu.com/p/f9c67a4b908e
在我们的开发中,会对不同安卓版本做适配,比如我之前做过的项目中最低兼容到4.4,最高兼容是最新的系统7.1,由于不同版本的系统中部分API版本也不同,我就要对这些API做特殊处理。新的平台有一些API不能使用旧的API,旧的平台也使用不了新的API。所以这就要考验我们开发人员的能力了。我这里简单给出几点我开发中使用过的一些方式,仅供参考:
一、同一个api在不同版本都存在,只是api的一些接口方法有变更。
这种情况是最好处理的,只要对版本号做判断,对应的系统版本用相应的api方法就好了。为了好维护,建议做一个简单的封装。
举例说明如下:
比如Notification在不同版本的兼容,举例如下:
首先打开谷歌官方文档,看看文档里面的一些说明:
1.Notification这个类是added in API level 1,一直都有,只是具体某些方法有变更。继续往下看。
2.这个类有个说明,意思是Notification.Builder是新增的一个内部类,用它创建通知更方便。接着往下看。
A class that represents how a persistent notification is to
be presented to the user using the NotificationManager.
The Notification.Builder has been added to make it easier
to construct Notifications.
3.Public constructors公共的构造方法,其中有3个参数的这个在api 11过时,它被Notification.Builder替代了。
Notification(int icon, CharSequence tickerText, long when)
This constructor was deprecated in API level 11.
Use Notification.Builder instead.
4.常量
-
EXTRA_LARGE_ICON
This constant was deprecated in API level 26. Use getLargeIcon(), which supports a wider variety of icon sources.(在API级别26中已弃用。使用getLargeIcon(),它支持更多种图标源。) -
EXTRA_SMALL_ICON
This constant was deprecated in API level 26. Use getSmallIcon(), which supports a wider variety of icon sources.(在API级别26中已弃用。使用getSmallIcon(),它支持更多种图标源。) -
FLAG_HIGH_PRIORITY
This constant was deprecated in API level 16. Use priority with a positive value.(在api16被弃用,请使用正数priority值替代) -
FLAG_SHOW_LIGHTS
This constant was deprecated in API level 26. use shouldShowLights().(在API级别26中已弃用。请使用shouldShowLights()
替代) -
PRIORITY_DEFAULT
This constant was deprecated in API level 26. use IMPORTANCE_DEFAULT instead.(在API级别26中已弃用。请使用IMPORTANCE_DEFAULT
替代) -
PRIORITY_HIGH
This constant was deprecated in API level 26. use IMPORTANCE_HIGH instead.(在API级别26中已弃用。请使用IMPORTANCE_HIGH
替代) -
PRIORITY_LOW
This constant was deprecated in API level 26. use IMPORTANCE_LOW instead.(在API级别26中已弃用。请使用IMPORTANCE_LOW
替代) -
PRIORITY_MAX
This constant was deprecated in API level 26. use IMPORTANCE_HIGH instead.(在API级别26中已弃用。请使用IMPORTANCE_HIGH
替代) -
PRIORITY_MIN
This constant was deprecated in API level 26. use IMPORTANCE_MIN instead.(在API级别26中已弃用。请使用IMPORTANCE_MIN
替代) -
STREAM_DEFAULT
This constant was deprecated in API level 21. Use getAudioAttributes() instead.(在API级别21中已弃用。请使用getAudioAttributes()
替代)
5.字段Fields
-
audioAttributes
在api 26弃用. 使用getAudioAttributes()
替代. -
audioStreamType
在api 21弃用. 使用audioAttributes
替代. -
defaults
此字段在API 26弃用。使用getSound()
和shouldShowLights()
和shouldVibrate()
。 -
icon
此字段已在API级别26中弃用。使用setSmallIcon(Icon)
替代。 -
largeIcon
This field was deprecated in API level 23. Use `setLargeIcon(Icon) instead. -
ledARGB
This field was deprecated in API level 26. use `shouldShowLights(). -
ledOffMS
This field was deprecated in API level 26. use `shouldShowLights(). -
ledOnMS
This field was deprecated in API level 26. useshouldShowLights().
-
priority
This field was deprecated in API level 26. usegetImportance()
instead. -
sound
This field was deprecated in API level 26. usegetSound()
instead. -
vibrate
This field was deprecated in API level 26. usegetVibrationPattern()
.
二、Android6.0的动态权限介绍
因为Android6.0(API23)开始需要动态申请权限,需要手动申请的权限有8组(短信、电话、联系人、存储、位置、麦克风、日历、相机
),共24个,如下所示:
所属权限组 | 权限 |
---|---|
短信 | SEND_SMS |
短信 | RECEIVE_SMS |
短信 | READ_SMS |
短信 | RECEIVE_WAP_PUSH |
短信 | RECEIVE_MMS |
电话 | READ_PHONE_STATE |
电话 | CALL_PHONE |
电话 | READ_CALL_LOG |
电话 | WRITE_CALL_LOG |
电话 | ADD_VOICEMAIL |
电话 | USE_SIP |
电话 | PROCESS_OUTGOING_CALLS |
联系人 | READ_CONTACTS |
联系人 | WRITE_CONTACTS |
联系人 | GET_ACCOUNTS |
存储 | READ_EXTERNAL_STORAGE |
存储 | WRITE_EXTERNAL_STORAGE |
位置 | ACCESS_FINE_LOCATION |
位置 | ACCESS_COARSE_LOCATION |
麦克风 | RECORD_AUDIO |
日历 | READ_CALENDAR |
日历 | WRITE_CALENDAR |
相机 | CAMERA |
传感器 | BODY_SENSORS |
注意:如果应用程序请求在AndroidManifest中列出的危险权限,并且应用程序已经在同一权限组中具有另一个危险权限,系统会立即授予权限,而不会与用户进行任何交互。
例如,如果一个应用程序先前已经请求并被授予READ_CONTACTS权限,然后它请求WRITE_CONTACTS(同属于联系人一组),系统会立即授予该权限,不会再弹出权限授予询问的对话框。
三、Android6.0如何申请动态权限
开发中经常会遇到拍照的权限申请,这里就讲一下如何动态设置拍照权限:
//别忘记在清单文件也加上CAMERA权限
//<uses-permission android:name="android.permission.CAMERA" />
// 定义识别码
public static final int CAMERA_OK = 1;
//动态申请拍照权限
if (Build.VERSION.SDK_INT>22){
if (ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED){
//先判断有没有权限 ,没有就在这里进行权限的申请
requestPermissions(new String[]{Manifest.permission.CAMERA}, CAMERA_OK);
}else {
//说明已经获取到摄像头权限了,可以去选择照片或者拍照了。
toSelectPhotoOrOpenCamera();
}
}else {
//这个说明系统版本在6.0之下,不需要动态获取权限,直接去选择照片或者拍照。
toSelectPhotoOrOpenCamera();
}
//在Activity中重写权限获取方法:
/**
* 权限操作结果处理
*/
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
switch (requestCode) {
case CAMERA_OK:
if (grantResults.length > 0 && grantResults[0]
== PackageManager.PERMISSION_GRANTED) {
//用户已授权
toSelectPhotoOrOpenCamera();
} else {
//用户拒绝权限
ToastUtils.show(this,
"缺少相机权限,暂时无法提供扫描功能,请尝试在设置中打开相机权限!",
Toast.LENGTH_LONG);
}
break;
}
}
}
四、Android7.0对文件权限进一步升级,提出了新的类FileProvider来获取文件。所以适配的时候一定要注意这一点api的变化。
FileProvider
是ContentProvider
的子类,把原来文件共享的 file://uri
换成了 content://uri
。一个Uri允许你获取临时权限去读写文件,当使用含有Uri的Intent,可以使用Intent.setFlags来添加临时权限。
下面来看看调用系统相机拍摄照片有如何变化,大致步骤如下所示:
(一)在manifest中添加Provider
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.lzw.demo.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
...
</provider>
...
</application>
</manifest>
(二)配置你要获取的文件所在的文件夹 --> 创建一个xml文件,比如file_demo.xml,文件内容如下:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/"/>
...
</paths>
路径说明:
<files-path name="name" path="path/" />
<!--等同于Context.getFilesDir()下面的path文件夹的所有文件-->
<cache-path name="name" path="path/" />
<!--等同于Context.getCacheDir()下面的path文件夹-->
<external-path name="name" path="path/" />
<!--等同于Environment.getExternalStorageDirectory()下面的path文件夹-->
<external-files-path name="name" path="path/" />
<!--等同于 Context#getExternalFilesDir(String)下面子文件path文件夹-->
<external-cache-path name="name" path="path/" />
<!--相当于 Context.getExternalCacheDir()下边的path文件夹-->
(三)添加路径信息到provier
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.lzw.demo.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_demo" />
</provider>
(四)现在可以去拍照了。(由于Android6.0开始要动态申请权限,所以别忘了,这里就不写了,主要讲FileProvider的使用)
//适配7.0的fileprovider,imgfile是图片文件路径
public void TakePhotoAdaption(File imgFile){
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//适配android7.0 手机拍照取uri的处理
if(Build.VERSION.SDK_INT<24){
//7.0如果用会Uri.fromFile(XXX)会闪退,所以这里要特别做一个判断。
//imgfile是图片文件路径
uri = Uri.fromFile(imgFile);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
}else{
//7.0+使用FileProvider.getUriForFile这个api
uri=FileProvider.getUriForFile(DemoActivity.this,
"com.lzw.demo.fileprovider",imgFile);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
//添加这一句表示对目标应用临时授权该Uri所代表的文件
cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION );
}
startActivityForResult(cameraIntent, FLAG_CHOOSE_CAMERA);
}
想看到拍照、选择照片、裁剪等完整流程的描述,可以参考这篇博客 解决安卓7.0拍照,相册选择崩溃的问题(包括压缩图片在内)
五、关于Android7.0相机闪退以及相册获取不到图片问题
- 1、没有动态申请权限,按照上述思路去做就好了。
- 2、华为手机的一些特殊处理方式,详情参见 ROM适配 https://www.jianshu.com/p/f9c67a4b908e
六、Android 8.0适配报错:Only fullscreen opaque activities can request orientation解决方案:
出现的原因:绝大多数都是因为我们为了提高用户体验,手动取消App启动白屏或者黑屏的时候,将Splash界面设为了透明,然后这个时候又设置了方向为垂直,从而导致了这个问题。
解决方案:
-
1.找到你设置透明的Activity,然后在他的theme中将android:windowIsTranslucent改为false
<item name="android:windowIsTranslucent">false</item>
-
2.再加入下面这行代码就搞定了。
<item name="android:windowDisablePreview">true</item>
这个坑来自于博客: https://www.jianshu.com/p/d0d907754603
七、Android8.0版本更新相关api适配
- 创建通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel mChannel = new NotificationChannel("channel_01",
"消息推送", NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(mChannel);
}
- 创建Notification
Context context = DJApplication.getInstance();
Notification.Builder builder = new Notification.Builder(context);
builder.setTicker("开始下载");
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodeResource(DJApplication.getInstance().getResources(),
R.mipmap.ic_launcher));
builder.setAutoCancel(true);
PendingIntent pIntent = PendingIntent.getActivity(context, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentTitle("下载中");
builder.setContentIntent(pIntent);
builder.setContentText(text);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId("channel_01");//设置有效的通知渠道 ID,这个ID要和之前创建时候的Channel_ID相同
}
manager.notify(1, builder.build());
- 安装apk权限
在 Android 8.0 中,安装未知应用权限提高了安装未知来源应用时的安全性。此权限与其他运行时权限一样,会与应用绑定,在安装时进行提示,确保用户授予使用安装来源的权限后,此权限才会提示用户安装应用。在运行 Android 8.0 或更高版本的设备上使用此权限时,恶意下载程序将无法骗取用户安装未获得预先授权的应用,所以我们需要加入安装apk文件的权限。
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
这个坑来自微信公众号“代码集中营”。