1. 程式人生 > >基於Android6.0~9.0的適配

基於Android6.0~9.0的適配

前言

大家還記得Android 6.0許可權適配的淚水嗎?而現在谷歌已經出了Android P的穩定版,而且谷歌粑粑,為了大家能給辛苦熬夜加班,特地的和個大市場合作,要強制推出9.0的適配,而近期在下不才,為了報著多踩坑的心態,做了一下7.0~9.0的適配,臉頰也是老淚兩行

2.安卓6.0的適配

2.1 怎麼適配

● 在6.0所有許可權都需要申請?

曰:當然不是。只有屬於危險許可權的才需要申請。危險許可權看下錶1-2

● 那危險許可權也很多啊,也要一個個申請?

曰:當然不是。你看看下面的表,都分好組了(9組),對於同一組內的許可權,只要有一個被同意,其他的都會被同意。

● 誰最帥

曰:當然是子信。

2.2 列舉許可權的分組

表1-2危險許可權分組

分組 名字 分割線
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  
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  
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  
<!-- 危險許可權 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 -->
複製程式碼

  以上是列出9組需要動態申請的許可權,建議自己程式碼統一封裝成一個工具類,這裡就不細說了, Android6.0許可權工具

3.Android 7.0的適配

3.1 應用間共享檔案

  在targetSdkVersion大於等於的24的App中,但是我們沒有去適配7.0。那麼在呼叫安裝頁面,或修改使用者頭像操作時,就會失敗。那麼就需要你去適配7.0或是將targetSdkVersion改為24以下(不推薦)。適配的方法這裡就不細講,大家可以看鴻洋大神的 Android 7.0 行為變更 通過FileProvider在應用間共享檔案這篇文章

3.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 和傳統簽名方案來簽署您的應用。

1)只勾選v1簽名就是傳統方案簽署,但是在7.0上不會使用V2安全的驗證方式。

2)只勾選V2簽名7.0以下會顯示未安裝,7.0上則會使用了V2安全的驗證方式。

3)同時勾選V1和V2則所有版本都沒問題。

3.3 org.apache不支援問題

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

3.3 SharedPreferences閃退

SharedPreferences read = getSharedPreferences(RELEASE_POOL_DATA, MODE_WORLD_READABLE);
//MODE_WORLD_READABLE :7.0以後不能使用這個獲取,會閃退,修改成MODE_PRIVATE

4.Android 8.0的適配

4.1 安卓8.0中PHONE許可權組新增兩個許可權

ANSWER_PHONE_CALLS:允許您的應用通過程式設計方式接聽呼入電話。要在您的應用中處理呼入電話,您可以使用 acceptRingingCall() 函式。
READ_PHONE_NUMBERS :許可權允許您的應用讀取裝置中儲存的電話號碼。
複製程式碼

4.2 通知適配

  安卓8.0中,為了更好的管制通知的提醒,不想一些不重要的通知打擾使用者,新增了通知渠道,使用者可以根據渠道來遮蔽一些不想要的通知

相容的程式碼

/**
* 安卓8。0通知的相容類哦,
* NotifyCompatYc   yc : 是雨辰的簡寫,謝謝哦,嘿嘿 ----高貴的子信
*/
public class NotifyCompatYc {

   public static final String QFMD_CHANNEL_ID = "com.oms.mingdeng";
   public static final String QFMD_CHANNEL_NAME = "祈福明燈";
   public static final String LJMS_DEFAULT_CHANNEL_NAME = "靈機妙算";
   public static final String LJMS_CHANNEL_ID = "com.oms.mmcnotity";
   public static final String XYS_CHANNEL_ID = "com.oms.xuyuanshu";
   public static final String XYS_CHANNEL_NAME = "許願樹";

   public static void setONotifyChannel(NotificationManager manager, NotificationCompat.Builder builder, String channeId, String channelName) {
       if (TextUtils.isEmpty(channeId)||TextUtils.isEmpty(channelName)){
           L.e("NotifyCompatYc:  ".concat("安卓8.0的通知相容庫中 channeId 與 channelName 不能為empty"));
       }
       if (Build.VERSION.SDK_INT >= 26) {
           //第三個引數設定通知的優先級別
           NotificationChannel channel =
                   new NotificationChannel(channeId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
           channel.canBypassDnd();//是否可以繞過請勿打擾模式
           channel.canShowBadge();//是否可以顯示icon角標
           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 void setONotifyChannel(NotificationManager manager,  String channeId, String channelName) {
       setONotifyChannel(manager,null,channeId,channelName);
   }

   public static Notification getNotification(Context context, String channelId) {
       NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, channelId);
       Notification notification = notificationBuilder.setOngoing(true)
               .setSmallIcon(R.drawable.ic_launcher)
               .setPriority(NotificationManager.IMPORTANCE_MIN)
               .setCategory(Notification.CATEGORY_SERVICE)
               .build();
       return notification;
   }
}
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(news_flag);
       builder = new NotificationCompat.Builder(context,NotifyCompatYc.QFMD_CHANNEL_ID);

       NotifyCompatYc.setONotifyChannel(manager,builder,NotifyCompatYc.QFMD_CHANNEL_ID,NotifyCompatYc.QFMD_CHANNEL_NAME);

       // 設定標題
       builder.setContentTitle(context.getResources().getString(R.string.qfmd_notify_title1));
       // 狀態列的動畫提醒語句
       builder.setTicker(context.getResources().getString(R.string.qfmd_notify_ticker));
       // 什麼時候提醒的
       builder.setWhen(System.currentTimeMillis());
       // 設定通知欄的優先順序
       builder.setPriority(Notification.PRIORITY_DEFAULT);
       // 設定點選可消失
       builder.setAutoCancel(true);
       // 設定是否震動等
       builder.setDefaults(Notification.DEFAULT_VIBRATE);
       // 設定icon
       builder.setSmallIcon(R.drawable.lingji_icon);
       // 設定點選意圖
       Intent intent = new Intent(context, GongdenggeActivity.class);
       Bundle bundle = new Bundle();
       bundle.putBoolean(Contants.INTENT_GOTO_MYLMAP, true);
       intent.putExtras(bundle);
       intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       PendingIntent pendingIntent = PendingIntent.getActivity(context, 230, intent, PendingIntent.FLAG_UPDATE_CURRENT);
       builder.setContentIntent(pendingIntent);
   }

   /**
    * 顯示祈福明燈過期通知
    */
   public void showQiFuLampOutOfDateNotify(Context context) {
       // 設定內容
       builder.setContentText(context.getResources().getString(R.string.qfmd_notify_content1));
       manager.notify(13251, builder.build());
   }

   public void showQiFuLampBlessNotify(Context context) {
       builder.setContentText(context.getResources().getString(R.string.qfmd_notify_content2));
       manager.notify(13255, builder.build());
   }
}

4.3 安裝APK

  首先在AndroidManifest檔案中新增安裝未知來源應用的許可權:

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

  這樣系統會自動詢問使用者完成授權。當然你也可以先使用 canRequestPackageInstalls()查詢是否有此許可權,如果沒有的話使用Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES這個action將使用者引導至安裝未知應用許可權介面去授權。

private static final int REQUEST_CODE_UNKNOWN_APP = 100;

   private void installAPK(){

       if (Build.VERSION.SDK_INT >= 26) {
           boolean hasInstallPermission = getPackageManager().canRequestPackageInstalls();
           if (hasInstallPermission) {
               //安裝應用
           } else {
               //跳轉至“安裝未知應用”許可權介面,引導使用者開啟許可權
               Uri selfPackageUri = Uri.parse("package:" + this.getPackageName());
               Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, selfPackageUri);
               startActivityForResult(intent, REQUEST_CODE_UNKNOWN_APP);
           }
       }else {
           //安裝應用
       }

   }

   //接收“安裝未知應用”許可權的開啟結果
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
       super.onActivityResult(requestCode, resultCode, data);
       if (requestCode == REQUEST_CODE_UNKNOWN_APP) {
           installAPK();
       }
   }

4.4 SecurityException的閃退

  問題原因:專案使用了ActiveAndroid,在 8.0 或 8.1 系統上使用 26 或以上的版本的 SDK 時,呼叫 ContentResolver 的 notifyChange 方法通知資料更新,或者呼叫 ContentResolver 的 registerContentObserver 方法監聽資料變化時,會出現上述異常。

解決方案:

(1)在清單檔案配置

<provider
       android:name="com.activeandroid.content.ContentProvider"
       android:authorities="com.ylmf.androidclient"
       android:enabled="true"
       android:exported="false">
</provider>

(2)去掉這個監聽重新整理的方法,改為廣播重新整理

4.5 靜態廣播無法正常接收

  問題原因: Android 8.0 引入了新的廣播接收器限制,因此您應該移除所有為隱式廣播 Intent 註冊的廣播接收器

解決方案: 使用動態廣播代替靜態廣播

4.6 Caused by:

4.6 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的適配

9.1 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 限制了明文流量的網路請求,非加密的流量請求都會被系統禁止掉

解決方案:

  在資原始檔新建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進行傳輸

9.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);
               }
複製程式碼

總結

  經過幾天的踩坑,終於把targetSdkVersion升級到28,對於以上的經驗,也許還存在某些疏漏的,也希望大家可以指正,補充,告訴,希望對你有一定的幫助,鄙人也很開心