Android版本适配(基于 6.0 ~ 9.0)

前言

新年第一篇总结与分享~
离19年新年就剩一个月左右了,整个公司的项目也不像以往一样紧张、忙碌,周末同事和朋友聊得最多的就是抢票和年终奖了(然而我们公司并没有😭),为自己心疼一分钟。。。
总结一下18年吧,主要负责了4个商业项目,还参与了公司 Android 新框架的封装,整体来说算是稳步提高吧。
先说一下写这篇文章的背景吧,主要就是不太忙了,再加上客户要求适配 Android 9.0 (第一个客户主动提出来的),在看完9.0的适配之后,也对之前的进行了一些整理,废话不多说,请看干货!

Android6.0适配

权限适配

动态权限适配是 Android 6.0 最先开始的,也是 Android 系统对开发者影响最大的改动之一。

权限适配三连问

  • Q: 是否 Android 6.0 所有权限都需要动态申请?
  • A: 不是。只有属于危险权限的才需要申请。
  • Q: 危险权限有哪些?
  • A: 见下面“危险权限分组说明”
  • Q: 危险权限是否需要一个一个申请?
  • A: 在 Android 6.0 ~ Android 8.0,不需要。如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用,即对于同一组内的权限,只要有一个被同意,其他的都会被同意。在 Android 8.0 之后,此行为已被纠正。系统只会授予应用明确请求的权限。然而一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准,但是若没有请求相应的权限而进行操作的话就会出现应用 crash 的情况。

危险权限分组说明

权限组 权限名称
CALENDAR android.permission.READ_CALENDAR
android.permission.WRITE_CALENDAR
CAMERA android.permission.CAMERA
CONTACTS android.permission.READ_CONTACTS
android.permission.WRITE_CONTACTS
android.permission.GET_ACCOUNTS
LOCATION android.permission.ACCESS_FINE_LOCATION
android.permission.ACCESS_COARSE_LOCATION
MICROPHONE android.permission.RECORD_AUDIO
PHONE android.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
SENSORS android.permission.BODY_SENSORS
SMS android.permission.SEND_SMS
android.permission.RECEIVE_SMS
android.permission.READ_SMS
android.permission.RECEIVE_WAP_PUSH
android.permission.RECEIVE_MMS
STORAGE android.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"/>
    <!--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"/>

Android 7.0适配

应用间共享文件

在 targetSdkVersion 大于等于的 24 的 app 中,但是我们没有去适配 Android 7.0。那么在调用安装页面,或修改用户头像操作时,就会失败。那么就需要你去适配 Android 7.0或是将 targetSdkVersion 改为 24 以下(不推荐)。适配的方法这里就不细讲,大家可以看鸿洋大神的 Android 7.0 行为变更 通过FileProvider在应用间共享文件这篇文章。

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 和传统签名方案来签署您的应用。

说明:


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

org.apache不支持问题

// build.gradle里面加上这句话
defaultConfig {
       useLibrary 'org.apache.http.legacy'
}

SharedPreferences闪退

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

Android 8.0适配

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

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

通知适配

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

/**
 * Description: Android 8.0通知的兼容类
 * Author: Jack Zhang
 * create on: 2019/1/2 3:16 PM
 */
public class MyNotification
{

  public static final String CHANNEL_ID_GL = "com.jz.gailun";
  public static final String CHANNEL_NAME_GL = "盖伦";
  public static final String CHANNEL_ID_AX = "com.jz.aixi";
  public static final String CHANNEL_NAME_AX = "艾希";
  public static final String CHANNEL_ID_LL = "com.jz.liulang";
  public static final String CHANNEL_NAME_LL = "流浪";

  public static void setONotifyChannel(NotificationManager manager, String channeId, String channelName)
  {
    setONotifyChannel(manager, null, channeId, channelName);
  }

  public static void setONotifyChannel(NotificationManager manager, NotificationCompat.Builder builder, String channeId, String channelName)
  {
    if (TextUtils.isEmpty(channeId) || TextUtils.isEmpty(channelName))
      Logger.e("Android 8.0 Notification的channeId与channelName不能为空");

    if (Build.VERSION.SDK_INT >= 26)
    {
      // 第三个参数设置通知的优先级别
      NotificationChannel channel = new NotificationChannel(channeId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
      // 是否可以绕过请勿打扰模式
      channel.canBypassDnd();
      // 是否可以显示icon角标
      channel.canShowBadge();
      // 是否显示通知闪灯
      channel.enableLights(true);
      // 收到消息时震动提示
      channel.enableVibration(true);
      // 设置绕过免打扰
      channel.setBypassDnd(true);
      channel.setLockscreenVisibility(NotificationCompat.VISIBILITY_SECRET);
      // 设置闪光灯颜色
      channel.setLightColor(Color.RED);
      // 获取设置铃声设置
      channel.getAudioAttributes();
      // 设置震动模式
      channel.setVibrationPattern(new long[]{100, 200, 100});
      // 是否会闪光
      channel.shouldShowLights();
      if (manager != null)
        manager.createNotificationChannel(channel);
      if (builder != null)
        builder.setChannelId(channeId);//这个id参数要与上面channel构建的第一个参数对应
    }
  }

  public static Notification getNotification(Context context, String channelId)
  {
    NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, channelId);
    Notification notification = notificationBuilder.setOngoing(true)
            .setSmallIcon(R.mipmap.ic_logo)
            .setPriority(NotificationManager.IMPORTANCE_MIN)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build();
    return notification;
  }
}
/**
 * Description: 通知管理类
 * Author: Jack Zhang
 * create on: 2019/1/2 3:23 PM
 */
public class NotifyManager
{
  private volatile static NotifyManager INSTANCE;

  private NotifyManager(Context context)
  {
    initNotifyManager(context);
  }

  public static NotifyManager getInstance(Context context)
  {
    if (INSTANCE == null)
      synchronized (NotifyManager.class)
      {
        if (INSTANCE == null)
          INSTANCE = new NotifyManager(context);
      }
    return INSTANCE;
  }

  private NotificationManager manager;
  // NotificationManagerCompat
  private NotificationCompat.Builder builder;

  // 初始化通知栏配置
  private void initNotifyManager(Context context)
  {
    context = context.getApplicationContext();
    manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
//    // 如果存在则清除上一个消息
//    manager.cancel(lastNotificationId);
    builder = new NotificationCompat.Builder(context, MyNotification.CHANNEL_ID_GL);

    MyNotification.setONotifyChannel(manager, builder, MyNotification.CHANNEL_ID_GL, MyNotification.CHANNEL_NAME_GL);

    // 设置标题
    builder.setContentTitle("Title");
    // 状态栏的动画提醒语句
    builder.setTicker("Ticker");
    // 什么时候提醒
    builder.setWhen(System.currentTimeMillis());
    // 设置通知栏的优先级
    builder.setPriority(Notification.PRIORITY_DEFAULT);
    // 设置点击可消失
    builder.setAutoCancel(true);
    // 设置是否震动等
    builder.setDefaults(Notification.DEFAULT_VIBRATE);
    // 设置icon
    builder.setSmallIcon(R.mipmap.ic_logo);
    // 设置点击意图
    Intent intent = new Intent(context, MainActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    builder.setContentIntent(pendingIntent);
  }

  /**
   * 显示盖伦通知栏
   *
   * @author Jack Zhang
   * create at 2019/1/2 3:28 PM
   */
  public void showGLNotify(Context context)
  {
    // 设置内容
    builder.setContentText("盖伦");
    manager.notify(1, builder.build());
  }

  /**
   * 显示艾希通知栏
   *
   * @author Jack Zhang
   * create at 2019/1/2 3:28 PM
   */
  public void showAXNotify(Context context)
  {
    builder.setContentText("艾希");
    manager.notify(2, builder.build());
  }

  /**
   * 显示流浪通知栏
   *
   * @author Jack Zhang
   * create at 2019/1/2 3:28 PM
   */
  public void showLLNotify(Context context)
  {
    builder.setContentText("流浪");
    manager.notify(3, builder.build());
  }
}

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"/>
  1. 去掉这个监听刷新的方法,改为广播刷新

静态广播无法正常接收

问题原因:Android 8.0 引入了新的广播接收器限制,因此您应该移除所有为隐式广播 Intent 注册的广播接收器。
解决方案:使用动态广播代替静态广播。

Only fullscreen opaque activities can request orientation

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

问题原因:Android 8.0 非全屏透明页面不允许设置方向(后面8.1系统谷歌就去掉了这个限制,可能是真的没必要)
解决方案:

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

Android 9.0适配

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

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

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

  1. 在资源文件新建xml目录,新建文件network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
  1. 清单文件配置:
<application
    android:networkSecurityConfig="@xml/network_security_config">
    <!--Android 9.0加的-->
    <uses-library
        android:name="org.apache.http.legacy"
        android:required="false" />
</application>

其他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);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容