Android 兼容性问题汇总



一、引言

都知道 Android 系统兼容性比较差,作为 Android 开发者也是挺苦逼的,从 Android 6.0 开始到现在 Android 9.0,不停地踩坑。最近项目中碰到当前播放音频的服务在手机一灭屏就被 Kill 的,因服务被 Kill,一些参数被置空,这样很容易就 Crash 掉,且只有一台手机 VIVO 21A 手机才有这个问题(Pixel、华为荣耀等都手机就不会),后面通过分析才找到原因,是因为 Android 8.0 不允许多个服务在后台同时运行而导致,后面修改启动服务的方式,问题得以解决。另一个近期遇到的是APP应用升级一直安装失败,原因是 Android 8.0 需要安装权限,这个也是坑啊。因此整理目前遇到的兼容性问题,兼容性真的很考验开发者。

二、Android 6.0 的适配

从 Android 6.0 开始,系统对权限的管理做了一些调整,APP 对权限的申请不仅需要在 Manifest 中声明,还需要在代码中做动态权限申请。当然不是所有的权限都需要动态申请,系统提供了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 -->
    private void initPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS};
            List<String> denyPermissions = new ArrayList<>();
            for (String permission : permissions) {
                if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, permission)) {
                    denyPermissions.add(permission);
                    //进入到这里代表没有权限.
                }
            }

            if (!denyPermissions.isEmpty()) {
                requestPermissions(denyPermissions.toArray(new String[denyPermissions.size()]), 101);
            }
        }
    }

另外,网络上也有人做了封装:RxPermissions

三、Android7.0 的适配

3.1 应用间共享文件

在 targetSdkVersion 大于等于的 24 的 APP 中,但是我们没有去适配 7.0。那么在调用安装页面,或修改用户头像操作时,就会失败。那么就需要你去适配 7.0 或是将targetSdkVersion 改为 24 以下(不推荐)。对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。

官网也给出了解决方案:

要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。如需了解有关权限和共享文件的详细信息,请参阅共享文件。

FileProvider 实际上是 ContentProvider 的一个子类,它的作用也比较明显了,file:///Uri 不给用,那么换个 Uri 为content:// 来替代。

3.2 APK signature scheme v2

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



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

3.3 org.apache 不支持问题

在 build.gradle 里面加上这句话

    defaultConfig {

        useLibrary 'org.apache.http.legacy'
    }

3.4 SharedPreferences闪退

SharedPreferences read = getSharedPreferences(RELEASE_POOL_DATA, MODE_WORLD_READABLE);

//MODE_WORLD_READABLE :7.0 以后不能使用这个获取,会闪退,修改成 MODE_PRIVATE

四、Android 8.0 的适配

4.1 Android 8.0 中 PHONE 权限组新增两个权限

ANSWER_PHONE_CALLS:允许您的应用通过编程方式接听呼入电话。要在您的应用中处理呼入电话,您可以使用 acceptRingingCall() 函数。
READ_PHONE_NUMBERS :权限允许您的应用读取设备中存储的电话号码。

4.2 通知适配

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

4.3 安装 APK

首先在 AndroidManifest 文件中添加安装未知来源应用的权限:

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

这样系统会自动询问用户完成授权。当然你也可以先使用 canRequestPackageInstalls() 查询是否有此权限,如果没有的话使用 Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES 这个 action 将用户引导至安装未知应用权限界面去授权。

这个权限的问题真真实实体验到坑,导致我们的应用都不能实现自身升级,需要全部删除再安装。

4.5 静态广播无法正常接收

Android 8.0 引入了新的广播接收器限制,因此您应该移除所有为隐式广播 Intent 注册的广播接收器

解决方案:使用动态广播代替静态广播

4.6 Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation

问题原因: Android 8.0 非全屏透明页面不允许设置方向(后面 8.1 系统谷歌就去掉了这个限制)

解决方案:

  • android:windowIsTranslucent 设置为 false
  • 如果还是想用的话,就去掉清单文件中 Activity 中 android:screenOrientation="portrait"
  • 就是使用透明的 dialog 或者 PopupWindow 来代替,也可以用 DialogFragment,看自己的需求和喜好

4.7 后台服务限制

如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用 startService() 函数,则该函数将引发一个 IllegalStateException。

新的 Context.startForegroundService() 函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的 startForeground() 函数。同时,调用 startForeground() 需要传入一个通知 notification,注意 notification 也要适配 Android 8.0。

问题:APP 一进后台或灭屏,服务就被 Kill 掉

解决方案:

第一步:使用 startForegroundService 代替 startService 启动服务

    Intent bindIntent = new Intent(this, PlayService.class);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(bindIntent);
    } else {
        startService(bindIntent);
    }

第二步,在服务代码中的 OnStart 作如下修改:

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        logShow("onStart");
        ForegroundService.startForeground(this);
        startService(new Intent(this, ForegroundService.class));
    }

修改为:

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        logShow("onStart");
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            ForegroundService.startForeground(this);
            startService(new Intent(this, ForegroundService.class));
        }
    }

第三步:在 OnCreate 中添加

    @Override
    public void onCreate() {
        super.onCreate();
        logShow("onCreate");
        String channelID = "1";
        String channelName;

        channelName = CSmartApplication.getInstance().getResources().getString(R.string.app_name);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelID, channelName,
                    NotificationManager.IMPORTANCE_HIGH);
            NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            manager.createNotificationChannel(channel);
            Notification notification = new Notification.Builder(getApplicationContext(), channelID).build();
            startForeground(1, notification);
        }
        ......
    }

这里说明下,startForeground() 方法的第一个参数不能为 0,即使调用了,也是启动服务后 5s 程序崩溃,异常说没有在 5s 内调 startForeground(),也不知道什么原因,反正为 0 就是会出错。

五、Android 9.0 的适配

5.1 CLEARTEXT communication to life.115.com not permitted by network security policy

问题原因: Android P 限制了明文流量的网络请求,非加密的流量请求都会被系统禁止掉
解决方案:在资源文件新建 xml 目录,新建文件

<?xml version="1.0" encoding="utf-8"?>

<network-security-config>

    <base-config cleartextTrafficPermitted="true" />

</network-security-config>

清单文件配置:

<application

        android:networkSecurityConfig="@xml/network_security_config">

        <!--9.0加的,哦哦-->
        <uses-library
            android:name="org.apache.http.legacy"
            android:required="false" />

</application>

但还是建议都使用 https 进行传输。

5.2 其他 API 的修改

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

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

六、总结

开发者的路,就是一条踩坑的路,踩多了就成了高手。