Android版本差异适配方案(5.0-11.0)

一个好的APP最好支持90%设备,由于不同版本系统提供的API可能不同,所以了解不同版本间系统差异很重要,这样才能更好的适配更多的智能设备。你的应用足不足够健壮要看你的应用在主流版本运行是否流畅。这篇文章记录开发过程中遇到的相对重要以及常用的适配方案,希望对读者有所帮助。

Android 版本号及对应的版本名

版本号版本名中文名
API Randroid R
API Qandroid Q
API 28android 9.0 Pie馅饼
API 27android 8.1 Oreo奥利奥
API 26android 8.0 Oreo奥利奥
API 25android 7.1 Nougat牛轧糖
API 24android 7.0 Nougat牛轧糖
API 23android 6.0 Marshmallow棉花糖
API 22android 5.1 Lollipop棒棒糖
API 21android 5.0 Lollipop棒棒糖
API 20android 4.4W KitKat奇巧巧克力棒
API 19android 4.4 KitKat奇巧巧克力棒
API 18android 4.3 Jelly Bean果冻豆
API 17android 4.2 Jelly Bean果冻豆
API 16android 4.1 Jelly Bean果冻豆
API 15android 4.0.3 ~4.0.4 Ice Cream Sandwich冰淇淋三明治
API 14android 4.0 ~ 4.0.2 Ice Cream Sandwich冰淇淋三明治
API 13android 3.2 Honeycomb蜂巢
API 12android 3.1 Honeycomb蜂巢
API 11android 3.0 Honeycomb蜂巢
API 10android 2.3.3 ~ 2.3.7 Gingerbread姜饼
API 9android 2.3~ 2.3.2 Gingerbread姜饼
API 8android 2.2~ 2.2.3 Froyo冻酸奶
API 7android 2.1 Éclair闪电泡芙
API 6android 2.0.1 Éclair闪电泡芙
API 5android2.0 Éclair闪电泡芙
API 4android 1.6 Donut甜甜圈
API 3android 1.5 ICupcake纸杯蛋糕
API 2android 1.1
API 1android 1.0

Android5.0

1、Android Runtime (ART)

Android运行时由Android核心库集和Dalvike虚拟机改成Android核心库集和ART(Android Runtime)模式。两者的区别就是Dalvike虚拟机采用了一种被称为JIT(just-in-time)的解释器进行动态编译,而ART模式则在用户安装App是进行预编译AOT(Ahead-of-time),将android5.X的运行速度提高了3倍左右。

ART的特性:
1: 用户安装应用时就进行预编译操作,将原本在程序运行中时的编译动作提前到应用安装时。在省去解释代码这一过程之后,应用的运行效率会更高。
缺点:(1) 安装时间增加 (2) 安装后的文件占用更多空间?(外存储器)
2: 解决垃圾回收 (GC) 问题
在 Dalvik 中,应用常常发现显式调用 System.gc() 非常有用,可促进垃圾回收 (GC)。对 ART 而言这种做法的必要性低得多,尤其是当您需要通过垃圾回收来预防出现 GC_FOR_ALLOC 类型或减少碎片时。
而且,Android 开源项目 (AOSP) 中正在开发一种紧凑型垃圾回收器,以改善内存管理。
3:预防 JNI 问题
ART 的 JNI 比 Dalvik 的 JNI 更为严格一些。使用 CheckJNI 模式来捕获常见问题是一种特别实用的方法。
1): 检查 JNI 代码中的垃圾回收问题
2): 错误处理 ART 的 JNI 会在多种情况下引发错误,而 Dalvik 则不然。(同样地,您可以通过使用 CheckJNI 执行测试来捕获大量此种情况)
3): 预防堆栈大小问题  Dalvik 具有单独的原生代码堆栈和 Java 代码堆栈,并且默认的 Java 堆栈大小为 32KB,默认的原生堆栈大小为 1MB。

2、Button将总是位于最上层

从5.0开始,在同一个layout下,就算你在Button上覆盖了相应的View,Button将总是位于最上层。产生原因:stateListAnimator属性。谷歌在Material Design中推出,是一个非常简单的方法用来实现在可视状态之间平滑过渡。这个属性可以通过android:stateListAnimator进行设置,可以使控件在点击时产生不同的交互。对于Button,点击时默认有个阴影的效果用于表示按下的状态(5.0以前就是简单的变色)。 解决方法:可以使用 android:stateListAnimator="@null" 去掉阴影效果而使Button可以被正常的覆盖。

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:stateListAnimator="@null"/>

Android6.0

1、动态权限

动态权限适配是 Android 6.0 最先开始的,也是 Android 系统对开发者影响最大的改动之一。系统权限主要分为两类,正常权限和危险权限。不管哪个版本的android,你应用中所用到的所有权限,不管是正常权限还是危险权限,都需要在应用Manifest中申明。你的目标SDK(targetSdkVersion)是23以及23以上版本:应用必须在Manifest中罗列出所有的权限,并且在程序运行时,它必须请求用户授予每一个危险权限,此时用户可以授予或者拒绝每一个权限,并且应用程序可以继续运行有限的功能,即使用户拒绝了权限请。在 Android 6.0 ~ Android 8.0中,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用,即对于同一组内的权限,只要有一个被同意,其他的都会被同意。在 Android 8.0 之后,此行为已被纠正。系统只会授予应用明确请求的权限。然而一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准,但是若没有请求相应的权限而进行操作的话就会出现应用 crash 的情况。

危险权限分组说明
权限组权限名称
CALENDARandroid.permission.READ_CALENDAR
android.permission.WRITE_CALENDAR
CAMERAandroid.permission.CAMERA
CALENDARandroid.permission.READ_CALENDAR
android.permission.WRITE_CALENDAR
CONTACTSandroid.permission.READ_CONTACTS
android.permission.WRITE_CONTACTS
android.permission.GET_ACCOUNTS
LOCATIONandroid.permission.ACCESS_FINE_LOCATION
android.permission.ACCESS_COARSE_LOCATION
MICROPHONEandroid.permission.RECORD_AUDIO
PHONEandroid.permission.READ_PHONE_STATE
android.permission.CALL_PHONE
android.permission.READ_CALL_LOG
android.permission.ADD_VOICEMAIL
android.permission.WRITE_CALL_LOG
android.permission.USE_SIP
android.permission.PROCESS_OUTGOING_CALLS
android.permission.ANSWER_PHONE_CALLS(8.0新增)
android.permission.READ_PHONE_NUMBERS(8.0新增)
SENSORSandroid.permission.BODY_SENSORS
SMSandroid.permission.SEND_SMS
android.permission.RECEIVE_SMS
android.permission.READ_SMS
android.permission.RECEIVE_WAP_PUSH
android.permission.RECEIVE_MMS
STORAGEandroid.permission.READ_EXTERNAL_STORAGE
android.permission.WRITE_EXTERNAL_STORAGE
对应在清单文件中的展示
<!--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"/>
<!--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"/>
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
<!--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"/>

2、规避动态权限

如果想规避动态权限策略也是可以的,配置以下

android {
    ...
    defaultConfig {
        ...
        targetSdkVersion 22 // 不使用api:23以及以上的动态权限策略
        ...
    }
}

3、Wifi相关操作

Android6.0之后,Wifi的使用更加严格。需要动态获取LOCATION权限,如果还想获取Wifi列表的话还需要打开GPS(位置信息)。

  • 首先在AndroidManifest.xml文件中增加以下权限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
  • 其次还需要动态申请定位权限组
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_CODE_ACCESS_COARSE_LOCATION);
  • 最后如果是用getScanResults()获取Wifi列表的话还需要打开GPS(位置信息)开关。
if(!isGPSOpen()){
    //跳转到手机原生设置页面,打开定位功能
    Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
    this.startActivityForResult(intent,GPS_REQUEST_CODE);
}else{
    //你的业务逻辑
}

/**
 * 检查有没打开定位
 */
private boolean isGPSOpen() {
    LocationManager locationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
    return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
}

Android7.0

1、FileProvider

在官方7.0的以上的系统中,尝试传递 file://URI可能会触发FileUriExposedException。要应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider类。

使用FileProvider授权

1)、创建新的FileProvider

当你需要以独立的模块分享出去,需要继承FileProvider,创建新的FileProvider,防止与主工程有冲突

/**
 * 继承FileProvider,防止冲突
 */
public class RoProvider extends FileProvider {

}

2)、创建file_path.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="external-files" path="" />
    <external-cache-path name="external-cache" path="" />
</paths>

各个标签代表的意义

namepath
名称标志字符串,不可以同名文件夹“相对路径”,完整路径取决于当前的标签类型
标签路径
root-path代表设备的根目录new File("/")
files-path代表context.getFilesDir()
cache-path代表context.getCacheDir()
external-path代表Environment.getExternalStorageDirectory()
external-files-path代表context.getExternalFilesDirs()
external-cache-path代表context.getExternalCacheDirs()

举例

<external-path name=“external” path=“pics” />代表的目录即为:Environment.getExternalStorageDirectory()/pics,其他同理。

3)、 注册FileProvider

authorities:一个标识,在当前系统内必须是唯一值,一般用包名。
exported:表示该 FileProvider 是否需要公开出去。
granUriPermissions:是否允许授权文件的临时访问权限。这里需要,所以是 true。

<provider
    android:name=".RoProvider"
    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>
禁用FileProvider授权

绕过版本限制,删除Uri的检测,这样就可以绕过7.0的文件共享限制

/**
 * 需要在Application中执行
 */
private void detectFileUriExposure() {
    Builder builder = new Builder();
    StrictMode.setVmPolicy(builder.build());
    builder.detectFileUriExposure();
}

2、APK signature scheme v2

Android 7.0 引入一项新的应用签名方案 APK Signature Scheme v2,它能提供更快的应用安装时间和更多针对未授权 APK 文件更改的保护。在默认情况下,Android Studio 2.2 和 Android Plugin for Gradle 2.2 会使用 APK Signature Scheme v2 和传统签名方案来签署您的应用。

说明

  • 只勾选V1签名就是传统方案签署,但是在 Android 7.0 上不会使用V2安全的验证方式。
  • 只勾选V2签名7.0以下会显示未安装,Android 7.0 上则会使用了V2安全的验证方式。
  • 同时勾选V1和V2则所有版本都没问题。

3、org.apache不支持问题

build.gradle里面加上这句话

defaultConfig {
    useLibrary 'org.apache.http.legacy'
}

或者在AndroidManifest.xml添加下面的配置

<uses-library
    android:name="org.apache.http.legacy"
    android:required="false" />

4、SharedPreferences闪退

// MODE_WORLD_READABLE:Android 7.0以后不能使用这个获取,会闪退
// 应修改成MODE_PRIVATE
SharedPreferences read = getSharedPreferences(RELEASE_POOL_DATA, MODE_WORLD_READABLE);

5、三个广播被禁止监听或发送

CONNECTIVITY_CHANGE 广播

在后台时不再能接收到 CONNECTIVITY_CHANGE 广播,前台不影响。

ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO 广播

不能发送或是接收新增图片(ACTION_NEW_PICTURE)和新增视频(ACTION_NEW_VIDEO) 的广播。

Android8.0

1、Notification(通知权限)

Android 8.0之后通知权限默认都是关闭的,无法默认开启以及通过程序去主动开启,需要程序员读取权限开启情况,然后提示用户去开启。

  • 判断权限是否开启
/**
 * 判断通知权限是否开启
 * @param context 上下文
 */
public static boolean isNotificationEnabled(Context context){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        return ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE)).areNotificationsEnabled();
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        ApplicationInfo appInfo = context.getApplicationInfo();
        String pkg = context.getApplicationContext().getPackageName();
        int uid = appInfo.uid;

        try {
            Class<?> appOpsClass = Class.forName(AppOpsManager.class.getName());
            Method checkOpNoThrowMethod = appOpsClass.getMethod("checkOpNoThrow", Integer.TYPE, Integer.TYPE, String.class);
            Field opPostNotificationValue = appOpsClass.getDeclaredField("OP_POST_NOTIFICATION");
            int value = (Integer) opPostNotificationValue.get(Integer.class);
            return (Integer) checkOpNoThrowMethod.invoke(appOps, value, uid, pkg) == 0;
        } catch (NoSuchMethodException | NoSuchFieldException | InvocationTargetException | IllegalAccessException | RuntimeException | ClassNotFoundException ignored) {
            return true;
        }
    } else {
        return true;
    }
}
  • 前往设置开启权限
/**
 * 打开设置页面打开权限
 *
 * @param activity activity
 * @param requestCode 这里的requestCode和onActivityResult中requestCode要一致
 */
public static void startSettingActivity(@NonNull Activity activity, int requestCode) {
    try {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + activity.getPackageName()));
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        activity.startActivityForResult(intent, requestCode);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

2、Notification(通知适配)

Android 8.0中,为了更好的管制通知的提醒,不想一些不重要的通知打扰用户,新增了通知渠道,用户可以根据渠道来屏蔽一些不想要的通知。

  • 创建通知
private void createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationManager notificationManager = (NotificationManager)
                getSystemService(Context.NOTIFICATION_SERVICE);
        //分组(可选)
        //groupId要唯一
        String groupId = "group_001";
        NotificationChannelGroup group = new NotificationChannelGroup(groupId, "广告");
        //创建group
        notificationManager.createNotificationChannelGroup(group);
        //channelId要唯一
        String channelId = "channel_001";
        NotificationChannel adChannel = new NotificationChannel(channelId,
                "推广信息", NotificationManager.IMPORTANCE_DEFAULT);
        //补充channel的含义(可选)
        adChannel.setDescription("推广信息");
        //将渠道添加进组(先创建组才能添加)
        adChannel.setGroup(groupId);
        //创建channel
        notificationManager.createNotificationChannel(adChannel);
        //创建通知时,标记你的渠道id
        Notification notification = new Notification.Builder(MainActivity.this, channelId)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setContentTitle("一条新通知")
                .setContentText("这是一条测试消息")
                .setAutoCancel(true)
                .build();
        notificationManager.notify(1, notification);
    }
}

3、自适应启动图标

从Android 8.0系统开始,应用程序的图标被分为了两层:前景层和背景层。也就是说,我们在设计应用图标的时候,需要将前景和背景部分分离,前景用来展示应用图标的Logo,背景用来衬托应用图标的Logo。需要注意的是,背景层在设计的时候只允许定义颜色和纹理,但是不能定义形状。注意图标图层的大小,两层的尺寸必须为108x108dp,前景图层中间的72x72dp图层就是在手机界面上展示的应用图标范围。这样系统在四面各留出18dp以产生有趣的视觉效果,如视差或脉冲(动画视觉效果由受支持的启动器生成,视觉效果可能因发射器而异)。

  • mipmap-anydpi-v26文件夹
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background" />
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

4、安装APK

Android 8.0去除了“允许未知来源”选项,如果我们的App具备安装App的功能,那么AndroidManifest文件需要包含REQUEST_INSTALL_PACKAGES权限,未声明此权限的应用将无法安装其他应用。当然,如果你不想添加这个权限,也可以通过getPackageManager().canRequestPackageInstalls()查询是否有此权限,没有的话使用Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES这个action将用户引导至安装未知应用权限界面去授权。

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

5、SecurityException的闪退

项目使用了ActiveAndroid,在 8.0 或 8.1 系统上使用 26 或以上的版本的 SDK 时,调用 ContentResolver 的 notifyChange 方法通知数据更新,或者调用 ContentResolver 的 registerContentObserver 方法监听数据变化时,会出现上述异常。解决方案:

  • 方案1、在清单文件配置。
<provider
   android:name="com.activeandroid.content.ContentProvider"
   android:authorities="com.jz.androidclient"
   android:enabled="true"
   android:exported="false"/>
  • 方案2、去掉这个监听刷新的方法,改为广播刷新。

6、静态广播无法正常接收

Google官方声明:从android 8.0(API26)开始,对清单文件中静态注册广播接收者增加了限制,建议大家不要在清单文件中静态注册广播接收者,改为动态注册。当然,如果你还是想用静态注册的方式也是有方法的,Intent里添加Component参数可实现。

  • 发送静态广播的特殊处理
Intent intent = new Intent( "广播的action" );
intent.setComponent( new ComponentName( "包名(如:com.yhd.rocket)","接收器的完整路径(如:com.yhd.rocket.receiver.RoReceiver)" ) );
sendBroadcast(intent);

Android9.0

1、刘海屏API支持

Android 9 支持最新的全面屏,其中包含为摄像头和扬声器预留空间的屏幕缺口。 通过 DisplayCutout类可确定非功能区域的位置和形状,这些区域不应显示内容。 要确定这些屏幕缺口区域是否存在及其位置,使用 getDisplayCutout() 函数。

  • 取区域位置及位置
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    View decorView = getWindow().getDecorView();
    WindowInsets rootWindowInsets = decorView.getRootWindowInsets();
    if (rootWindowInsets != null) {
        DisplayCutout cutout = rootWindowInsets.getDisplayCutout();
        List<Rect> boundingRects = cutout.getBoundingRects();
        if (boundingRects != null && boundingRects.size() > 0) {
            String msg = "";
            for (Rect rect : boundingRects) {
                msg = msg +"left-" + rect.left;
                Log.d(TAG, msg);
            }
         }
    }
}
  • 新窗口布局模式,允许应用程序请求是否在挖孔区域布局
class WindowManager.LayoutParams {
    //布局参数
    int layoutInDisplayCutoutMode;
    //默认情况下,全屏窗口不会使用到挖孔区域,非全屏窗口可正常使用挖孔区域。
    final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
    //窗口声明使用挖孔区域
    final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
    //窗口声明不使用挖孔区域
    final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
}
  • 设置代码
WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
getWindow().setAttributes(lp);

2、CLEARTEXT communication to http://xxx not permitted by network security policy

问题原因: Android P 限制了明文流量的网络请求,非加密的流量请求(http)都会被系统禁止掉。解决方案:

  • 方案一:将http请求改为https
  • 方案二:添加usesCleartextTraffic属性
<application
    android:usesCleartextTraffic="true">
</application>        
  • 方案三:添加资源文件(复杂)

1、在资源文件新建xml目录,新建文件network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

2、清单文件配置:

<application
    android:networkSecurityConfig="@xml/network_security_config">
</application>

3、java.lang.IllegalArgumentException: Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed

在自定义绘制View过程中会遇到 Android 9.0 兼容问题导致的Crash,解决方案:

if (Build.VERSION.SDK_INT >= 26){
  canvas.clipPath(mPath); 
} else {
  canvas.clipPath(mPath, Region.Op.REPLACE);
}

4、前台服务需要添加权限

在安卓9.0版本之后,必须要授予FOREGROUND_SERVICE权限,才能够使用前台服务,否则会抛出异常。对此,我们只需要在AndroidManifest添加对应的权限即可,这个权限是普通权限,不需要动态申请。

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

5、全面限制静态广播的接收

升级安卓9.0之后,隐式广播将会被全面禁止,在AndroidManifest中注册的Receiver将不能够生效,你需要在应用中进行动态注册。

MyReceiver myReceiver = new MyReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(MY_ACTION);
registerReceiver(myReceiver, intentFilter);

6、非 SDK 接口访问限制

在 Android 9.0 版本中,谷歌加入了非 SDK 接口使用限制,无论是通过调用、反射还是JNI等方式,开发者都无法对非 SDK 接口进行访问,此接口的滥用将会带来严重的系统兼容性问题。 在开发过程中,开发者如果调用了非 SDK 接口,会导致应用出现crash,无法启动;或在运行过程中出现崩溃、闪退等现象;也可能导致应用功能不可用等严重兼容性问题,其影响范围波及所有调用此接口的应用。

那么什么是非SDK接口呢,所谓非SDK接口就是所有不能够在谷歌官网上查询到的接口,谷歌提供了 查询接口的网站

  • 例如我们通过反射修改Dialog窗体的颜色

此方法在安卓9.0版本将不能够正常运行,会抛出NoSuchFieldException,对于诸如此类的调用官方private方法或者@hide方法,都将不能使用。

try {
    //通过反射的方式来更改dialog中文字大小、颜色
    Field mAlert = AlertDialog.class.getDeclaredField("mAlert");
    mAlert.setAccessible(true);
    Object mAlertController = mAlert.get(normalDialog);
    Field mMessage = mAlertController.getClass().getDeclaredField("mMessageView");
    mMessage.setAccessible(true);
    TextView mMessageView = (TextView) mMessage.get(mAlertController);
    mMessageView.setTextSize(23);
    mMessageView.setTextColor(Color.RED);
    Field mTitle = mAlertController.getClass().getDeclaredField("mTitleView");
    mTitle.setAccessible(true);
    TextView mTitleView = (TextView) mTitle.get(mAlertController);
    mTitleView.setTextSize(20);
    mTitleView.setTextColor(Color.RED);
} catch (Exception e){
    Toast.makeText(NotSDKInterfaceActivity.this,e.getLocalizedMessage(),Toast.LENGTH_LONG).show();
}

7、Apache HTTP 客户端弃用

将 compileSdkVersion 升级到 28 之后,如果在项目中用到了 Apache HTTP client 的相关类,就会抛出找不到这些类的错误。这是因为官方已经在 Android P 的启动类加载器中将其移除,如果仍然需要使用 Apache HTTP client.在 Manifest 文件中加入:

<uses-library 
    android:name="org.apache.http.legacy" 
    android:required="false"/>

8、Calandar(日历)

Android 9.0日历的时间戳小于0

  • Android 9.0
long timeMil = Calendar.getInstance().getTimeInMillis();//timeMil < 0
  • Android 9.0以前
long timeMil = Calendar.getInstance().getTimeInMillis();//timeMil > 0

Android10.0

这里讲的不错,先占坑吧。大概总结一下:

Scoped Storage(分区存储)

  • 特定目录(App-specific),使用getExternalFilesDir(String type)getExternalCacheDir()方法访问。无需权限,且卸载应用时会自动删除。
  • 照片、视频、音频这类媒体文件。使用MediaStore 访问,访问其他应用的媒体文件时需要READ_EXTERNAL_STORAGE权限。
  • 其他目录,使用存储访问框架SAFStorage Access Framwork

权限变化

  • 在后台运行时访问设备位置信息需要权限
    Android 10 引入了 ACCESS_BACKGROUND_LOCATION 权限(危险权限)。该权限允许应用程序在后台访问位置。如果请求此权限,则还必须请求ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION权限。只请求此权限无效果。官方推荐使用前台服务来实现,在前台服务中获取位置信息。
    <service
        android:name="MyNavigationService"
        android:foregroundServiceType="location">
    </service>
    
  • 电话、蓝牙和WLAN的API需要精确位置权限
    上述部分类和方法中必须具有 ACCESS_FINE_LOCATION 权限才能使用。
  • 新增ACCESS_MEDIA_LOCATION权限
    如果你要获取图片中的地理位置信息,需要申请ACCESS_MEDIA_LOCATION权限,并使用MediaStore.setRequireOriginal()获取。
  • 废弃PROCESS_OUTGOING_CALLS权限
    呼出电话的监听

后台启动 Activity 的限制

简单解释就是应用处于后台时,无法启动Activity。因为此项行为变更适用于在 Android 10 上运行的所有应用,所以这一限制导致最明显的问题就是点击推送信息时,有些应用无法进行正常的跳转(具体的实现问题导致)。所以针对这类问题,全屏 intent,注意设置最高优先级和添加USE_FULL_SCREEN_INTENT权限,这是一个普通权限。比如微信来语音或者视频通话时,弹出的接听页面就是使用这一功能。

深色主题

Android 10 新增了一个系统级的深色主题(在系统设置中开启)。虽然深色主题并不是强制适配项,但是它可以带给用户更好的体验:

  • 可大幅减少耗电量。 OLED 屏幕中每个像素都是自主发光,所以在显示深色元素时像素所消耗的电流更低,尤其在纯黑颜色时像素点可以完全关闭来达到省电的效果。
  • 为弱视以及对强光敏感的用户提高可视性。深色可以降低屏幕的整体视觉亮度,减少对眼睛的视觉压力。
  • 让所有人都可以在光线较暗的环境中更轻松地使用设备

标识符和数据

  • 对不可重置的设备标识符实施了限制
    Build.getSerial()TelephonyManager.getImei()/getXXId()/getXXNumber()等唯一标识符方法应用必须具有 READ_PRIVILEGED_PHONE_STATE 特许权限才能正常使用。
  • 限制了对剪贴板数据的访问权限
    除非您的应用是默认输入法 (IME) 或是目前处于焦点的应用,否则它无法访问 Android 10 或更高版本平台上的剪贴板数据。
  • 对启用和停用 WLAN 实施了限制
    Android 10 或更高版本为目标平台的应用无法启用或停用 WLANWifiManager.setWifiEnabled()方法始终返回 false。如果您需要提示用户启用或停用 WLAN,请使用设置面板。

Android11.0

存储机制更新

  • 强制执行分区存储
    为了给开发者更多时间进行测试,以 Android 10API 级别 29)为目标平台的应用仍可请求 requestLegacyExternalStorage 属性。应用可以利用此标记暂时停用与分区存储相关的变更,例如授予对不同目录和不同类型的媒体文件的访问权限。当您将应用更新为以 Android 11 为目标平台后,将无法使用 requestLegacyExternalStorage 来停用分区存储。
  • 管理设备存储空间
    不能再删除其他应用的缓存文件,即使您的应用具有所有文件访问权限也是如此。通过调用 ACTION_MANAGE_STORAGE intent 操作来检查可用空间以及提示用户同意让您的应用调用 ACTION_CLEAR_APP_CACHE intent清除所有缓存。
  • 权限变更
    WRITE_EXTERNAL_STORAGE 权限和 WRITE_MEDIA_STORAGE 特许权限将不再提供任何其他访问权限。
  • 所有文件访问权限
    某些应用的核心用例需要访问大量的文件,如文件管理操作或备份和恢复操作。声明 MANAGE_EXTERNAL_STORAGE 权限,使用 ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent 操作将用户引导至一个系统设置页面开启。

权限更新

  • 一次性权限
    Android 11 中,每当应用请求与位置信息、麦克风或摄像头相关的权限时,面向用户的权限对话框就会包含仅限这一次选项。如果用户在对话框中选择此选项,系统会向您的应用授予临时的一次性权限。当您应用的 Activity 可见时,您的应用可以继续访问相关数据。如果用户随后将您的应用转到后台,则您的应用对相关数据的访问权限会在一小段时间后被撤消。
  • 数据访问审核
    提供了支持服务来帮助开发者审核数据访问,并将数据访问与应用中的特定功能相关联。

位置信息更新

Android 11 进一步强调了用户对位置信息的控制,方法是添加了一次性权限,并去除了用户通过应用内提示授予 ACCESS_BACKGROUND_LOCATION 权限的能力,可以创建自定义界面,向用户解释您的应用为什么需要 ACCESS_BACKGROUND_LOCATION 权限。

软件包可见性

Android 11 更改了应用查询同一设备上的其他已安装应用及与之交互的方式。您可能需要在应用的清单文件中添加 <queries> 元素,以便系统知道要向您的应用显示哪些其他应用。<queries> 元素可用于描述您的应用可能需要与哪些其他应用交互。

前台服务类型

如果您的应用以 Android 11 为目标平台并且在某项前台服务中访问这些类型的数据,则您需要在该前台服务的声明的 foregroundServiceType 属性中添加新的 cameramicrophone 类型。

<manifest>
    <service android:foregroundServiceType="location|camera|microphone"/>
</manifest>

消息框的更新

出于安全方面的考虑,同时也为了保持良好的用户体验,如果包含自定义视图的消息框是以 Android 11 为目标平台的应用从后台发送的,则系统会屏蔽这些消息框。请注意,仍允许使用文本消息框;此类消息框是使用 Toast.makeText() 创建的,并不调用 setView()

关于我

持续更新中…

  • 11
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值