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

前言

新年第一篇總結(jié)與分享~
離19年新年就剩一個(gè)月左右了扔涧,整個(gè)公司的項(xiàng)目也不像以往一樣緊張、忙碌缀台,周末同事和朋友聊得最多的就是搶票和年終獎(jiǎng)了(然而我們公司并沒有??)幔翰,為自己心疼一分鐘京髓。烈和。。
總結(jié)一下18年吧叫惊,主要負(fù)責(zé)了4個(gè)商業(yè)項(xiàng)目帝洪,還參與了公司 Android 新框架的封裝似舵,整體來說算是穩(wěn)步提高吧。
先說一下寫這篇文章的背景吧葱峡,主要就是不太忙了砚哗,再加上客戶要求適配 Android 9.0 (第一個(gè)客戶主動(dòng)提出來的),在看完9.0的適配之后砰奕,也對(duì)之前的進(jìn)行了一些整理蛛芥,廢話不多說提鸟,請(qǐng)看干貨!

Android6.0適配

權(quán)限適配

動(dòng)態(tài)權(quán)限適配是 Android 6.0 最先開始的仅淑,也是 Android 系統(tǒng)對(duì)開發(fā)者影響最大的改動(dòng)之一称勋。

權(quán)限適配三連問

  • Q: 是否 Android 6.0 所有權(quán)限都需要?jiǎng)討B(tài)申請(qǐng)?
  • A: 不是涯竟。只有屬于危險(xiǎn)權(quán)限的才需要申請(qǐng)赡鲜。
  • Q: 危險(xiǎn)權(quán)限有哪些?
  • A: 見下面“危險(xiǎn)權(quán)限分組說明”
  • Q: 危險(xiǎn)權(quán)限是否需要一個(gè)一個(gè)申請(qǐng)庐船?
  • A: 在 Android 6.0 ~ Android 8.0银酬,不需要。如果應(yīng)用在運(yùn)行時(shí)請(qǐng)求權(quán)限并且被授予該權(quán)限醉鳖,系統(tǒng)會(huì)錯(cuò)誤地將屬于同一權(quán)限組并且在清單中注冊(cè)的其他權(quán)限也一起授予應(yīng)用捡硅,即對(duì)于同一組內(nèi)的權(quán)限,只要有一個(gè)被同意盗棵,其他的都會(huì)被同意壮韭。在 Android 8.0 之后,此行為已被糾正纹因。系統(tǒng)只會(huì)授予應(yīng)用明確請(qǐng)求的權(quán)限喷屋。然而一旦用戶為應(yīng)用授予某個(gè)權(quán)限,則所有后續(xù)對(duì)該權(quán)限組中權(quán)限的請(qǐng)求都將被自動(dòng)批準(zhǔn)瞭恰,但是若沒有請(qǐng)求相應(yīng)的權(quán)限而進(jìn)行操作的話就會(huì)出現(xiàn)應(yīng)用 crash 的情況屯曹。

危險(xiǎn)權(quán)限分組說明

權(quán)限組 權(quán)限名稱
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

對(duì)應(yīng)在清單文件中的展示如下:

    <!--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適配

應(yīng)用間共享文件

在 targetSdkVersion 大于等于的 24 的 app 中,但是我們沒有去適配 Android 7.0惊畏。那么在調(diào)用安裝頁面恶耽,或修改用戶頭像操作時(shí),就會(huì)失敗颜启。那么就需要你去適配 Android 7.0或是將 targetSdkVersion 改為 24 以下(不推薦)偷俭。適配的方法這里就不細(xì)講,大家可以看鴻洋大神的 Android 7.0 行為變更 通過FileProvider在應(yīng)用間共享文件這篇文章缰盏。

APK signature scheme v2

Android 7.0 引入一項(xiàng)新的應(yīng)用簽名方案 APK Signature Scheme v2涌萤,它能提供更快的應(yīng)用安裝時(shí)間和更多針對(duì)未授權(quán) APK 文件更改的保護(hù)。在默認(rèn)情況下口猜,Android Studio 2.2 和 Android Plugin for Gradle 2.2 會(huì)使用 APK Signature Scheme v2 和傳統(tǒng)簽名方案來簽署您的應(yīng)用负溪。

說明:


示例圖示
  1. 只勾選V1簽名就是傳統(tǒng)方案簽署,但是在 Android 7.0 上不會(huì)使用V2安全的驗(yàn)證方式济炎。
  2. 只勾選V2簽名7.0以下會(huì)顯示未安裝川抡,Android 7.0 上則會(huì)使用了V2安全的驗(yàn)證方式。
  3. 同時(shí)勾選V1和V2則所有版本都沒問題冻辩。

org.apache不支持問題

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

SharedPreferences閃退

// MODE_WORLD_READABLE:Android 7.0以后不能使用這個(gè)獲取猖腕,會(huì)閃退
// 應(yīng)修改成MODE_PRIVATE
SharedPreferences read = getSharedPreferences(RELEASE_POOL_DATA, MODE_WORLD_READABLE);

Android 8.0適配

Android 8.0中PHONE權(quán)限組新增兩個(gè)權(quán)限

ANSWER_PHONE_CALLS:允許您的應(yīng)用通過編程方式接聽呼入電話拆祈。要在您的應(yīng)用中處理呼入電話,您可以使用 acceptRingingCall() 函數(shù)倘感。
READ_PHONE_NUMBERS:權(quán)限允許您的應(yīng)用讀取設(shè)備中存儲(chǔ)的電話號(hào)碼放坏。

通知適配

Android 8.0中,為了更好的管制通知的提醒老玛,不想一些不重要的通知打擾用戶淤年,新增了通知渠道,用戶可以根據(jù)渠道來屏蔽一些不想要的通知蜡豹。
代碼示例如下:

/**
 * 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)
    {
      // 第三個(gè)參數(shù)設(shè)置通知的優(yōu)先級(jí)別
      NotificationChannel channel = new NotificationChannel(channeId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
      // 是否可以繞過請(qǐng)勿打擾模式
      channel.canBypassDnd();
      // 是否可以顯示icon角標(biāo)
      channel.canShowBadge();
      // 是否顯示通知閃燈
      channel.enableLights(true);
      // 收到消息時(shí)震動(dòng)提示
      channel.enableVibration(true);
      // 設(shè)置繞過免打擾
      channel.setBypassDnd(true);
      channel.setLockscreenVisibility(NotificationCompat.VISIBILITY_SECRET);
      // 設(shè)置閃光燈顏色
      channel.setLightColor(Color.RED);
      // 獲取設(shè)置鈴聲設(shè)置
      channel.getAudioAttributes();
      // 設(shè)置震動(dòng)模式
      channel.setVibrationPattern(new long[]{100, 200, 100});
      // 是否會(huì)閃光
      channel.shouldShowLights();
      if (manager != null)
        manager.createNotificationChannel(channel);
      if (builder != null)
        builder.setChannelId(channeId);//這個(gè)id參數(shù)要與上面channel構(gòu)建的第一個(gè)參數(shù)對(duì)應(yīng)
    }
  }

  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);
//    // 如果存在則清除上一個(gè)消息
//    manager.cancel(lastNotificationId);
    builder = new NotificationCompat.Builder(context, MyNotification.CHANNEL_ID_GL);

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

    // 設(shè)置標(biāo)題
    builder.setContentTitle("Title");
    // 狀態(tài)欄的動(dòng)畫提醒語句
    builder.setTicker("Ticker");
    // 什么時(shí)候提醒
    builder.setWhen(System.currentTimeMillis());
    // 設(shè)置通知欄的優(yōu)先級(jí)
    builder.setPriority(Notification.PRIORITY_DEFAULT);
    // 設(shè)置點(diǎn)擊可消失
    builder.setAutoCancel(true);
    // 設(shè)置是否震動(dòng)等
    builder.setDefaults(Notification.DEFAULT_VIBRATE);
    // 設(shè)置icon
    builder.setSmallIcon(R.mipmap.ic_logo);
    // 設(shè)置點(diǎn)擊意圖
    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)
  {
    // 設(shè)置內(nèi)容
    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的閃退

問題原因:項(xiàng)目使用了ActiveAndroid麸粮,在 8.0 或 8.1 系統(tǒng)上使用 26 或以上的版本的 SDK 時(shí),調(diào)用 ContentResolver 的 notifyChange 方法通知數(shù)據(jù)更新镜廉,或者調(diào)用 ContentResolver 的 registerContentObserver 方法監(jiān)聽數(shù)據(jù)變化時(shí)弄诲,會(huì)出現(xiàn)上述異常。
解決方案:

  1. 在清單文件配置:
<provider
       android:name="com.activeandroid.content.ContentProvider"
       android:authorities="com.jz.androidclient"
       android:enabled="true"
       android:exported="false"/>
  1. 去掉這個(gè)監(jiān)聽刷新的方法娇唯,改為廣播刷新

靜態(tài)廣播無法正常接收

問題原因:Android 8.0 引入了新的廣播接收器限制齐遵,因此您應(yīng)該移除所有為隱式廣播 Intent 注冊(cè)的廣播接收器。
解決方案:使用動(dòng)態(tài)廣播代替靜態(tài)廣播塔插。

Only fullscreen opaque activities can request orientation

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

問題原因:Android 8.0 非全屏透明頁面不允許設(shè)置方向(后面8.1系統(tǒng)谷歌就去掉了這個(gè)限制梗摇,可能是真的沒必要)
解決方案:

  1. android:windowIsTranslucent設(shè)置為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 限制了明文流量的網(wǎng)絡(luò)請(qǐng)求,非加密的流量請(qǐng)求都會(huì)被系統(tǒng)禁止掉
解決方案:

  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);
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末景图,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碉哑,更是在濱河造成了極大的恐慌,老刑警劉巖亮蒋,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扣典,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡慎玖,警方通過查閱死者的電腦和手機(jī)贮尖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趁怔,“玉大人湿硝,你說我怎么就攤上這事薪前。” “怎么了关斜?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵示括,是天一觀的道長。 經(jīng)常有香客問我痢畜,道長垛膝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任丁稀,我火速辦了婚禮吼拥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘线衫。我一直安慰自己凿可,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布授账。 她就那樣靜靜地躺著枯跑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪矗积。 梳的紋絲不亂的頭發(fā)上全肮,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音棘捣,去河邊找鬼辜腺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛乍恐,可吹牛的內(nèi)容都是我干的评疗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼茵烈,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼百匆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起呜投,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤加匈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后仑荐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雕拼,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年粘招,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了啥寇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辑甜,靈堂內(nèi)的尸體忽然破棺而出衰絮,到底是詐尸還是另有隱情,我是刑警寧澤磷醋,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布猫牡,位于F島的核電站,受9級(jí)特大地震影響子檀,放射性物質(zhì)發(fā)生泄漏镊掖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一褂痰、第九天 我趴在偏房一處隱蔽的房頂上張望亩进。 院中可真熱鬧,春花似錦缩歪、人聲如沸归薛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽主籍。三九已至,卻和暖如春逛球,著一層夾襖步出監(jiān)牢的瞬間千元,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國打工颤绕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留幸海,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓奥务,卻偏偏與公主長得像物独,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子氯葬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容