Android版本差異適配方案(5.0-9.0)

Android版本差異適配方案(5.0-9.0)

一個(gè)好的APP最好支持90%設(shè)備撵孤,由于不同版本系統(tǒng)提供的API可能不同姚淆,所以了解不同版本間系統(tǒng)差異很重要,這樣才能更好的適配更多的智能設(shè)備。你的應(yīng)用足不足夠健壯要看你的應(yīng)用在主流版本運(yùn)行是否流暢媳友。這篇文章記錄開(kāi)發(fā)過(guò)程中遇到的相對(duì)重要以及常用的適配方案,希望對(duì)讀者有所幫助产捞。

Android 版本號(hào)及對(duì)應(yīng)的版本名

版本號(hào) 版本名 中文名
API Q android Q
API 28 android 9.0 Pie 餡餅
API 27 android 8.1 Oreo 奧利奧
API 26 android 8.0 Oreo 奧利奧
API 25 android 7.1 Nougat 牛軋?zhí)?/td>
API 24 android 7.0 Nougat 牛軋?zhí)?/td>
API 23 android 6.0 Marshmallow 棉花糖
API 22 android 5.1 Lollipop 棒棒糖
API 21 android 5.0 Lollipop 棒棒糖
API 20 android 4.4W KitKat 奇巧巧克力棒
API 19 android 4.4 KitKat 奇巧巧克力棒
API 18 android 4.3 Jelly Bean 果凍豆
API 17 android 4.2 Jelly Bean 果凍豆
API 16 android 4.1 Jelly Bean 果凍豆
API 15 android 4.0.3 ~4.0.4 Ice Cream Sandwich 冰淇淋三明治
API 14 android 4.0 ~ 4.0.2 Ice Cream Sandwich 冰淇淋三明治
API 13 android 3.2 Honeycomb 蜂巢
API 12 android 3.1 Honeycomb 蜂巢
API 11 android 3.0 Honeycomb 蜂巢
API 10 android 2.3.3 ~ 2.3.7 Gingerbread 姜餅
API 9 android 2.3~ 2.3.2 Gingerbread 姜餅
API 8 android 2.2~ 2.2.3 Froyo 凍酸奶
API 7 android 2.1 éclair 閃電泡芙
API 6 android 2.0.1 éclair 閃電泡芙
API 5 android2.0 éclair 閃電泡芙
API 4 android 1.6 Donut 甜甜圈
API 3 android 1.5 ICupcake 紙杯蛋糕
API 2 android 1.1
API 1 android 1.0

Android5.0

1醇锚、Android Runtime (ART)

Android運(yùn)行時(shí)由Android核心庫(kù)集和Dalvike虛擬機(jī)改成Android核心庫(kù)集和ART(Android Runtime)模式。兩者的區(qū)別就是Dalvike虛擬機(jī)采用了一種被稱(chēng)為JIT(just-in-time)的解釋器進(jìn)行動(dòng)態(tài)編譯,而ART模式則在用戶安裝App是進(jìn)行預(yù)編譯AOT(Ahead-of-time)坯临,將android5.X的運(yùn)行速度提高了3倍左右焊唬。

ART的特性:
1: 用戶安裝應(yīng)用時(shí)就進(jìn)行預(yù)編譯操作,將原本在程序運(yùn)行中時(shí)的編譯動(dòng)作提前到應(yīng)用安裝時(shí)看靠。在省去解釋代碼這一過(guò)程之后赶促,應(yīng)用的運(yùn)行效率會(huì)更高。
缺點(diǎn):(1) 安裝時(shí)間增加 (2) 安裝后的文件占用更多空間挟炬?(外存儲(chǔ)器)
2: 解決垃圾回收 (GC) 問(wèn)題
在 Dalvik 中鸥滨,應(yīng)用常常發(fā)現(xiàn)顯式調(diào)用 System.gc() 非常有用,可促進(jìn)垃圾回收 (GC)谤祖。對(duì) ART 而言這種做法的必要性低得多婿滓,尤其是當(dāng)您需要通過(guò)垃圾回收來(lái)預(yù)防出現(xiàn) GC_FOR_ALLOC 類(lèi)型或減少碎片時(shí)。
而且粥喜,Android 開(kāi)源項(xiàng)目 (AOSP) 中正在開(kāi)發(fā)一種緊湊型垃圾回收器凸主,以改善內(nèi)存管理。
3:預(yù)防 JNI 問(wèn)題
ART 的 JNI 比 Dalvik 的 JNI 更為嚴(yán)格一些额湘。使用 CheckJNI 模式來(lái)捕獲常見(jiàn)問(wèn)題是一種特別實(shí)用的方法秕铛。
1): 檢查 JNI 代碼中的垃圾回收問(wèn)題
2): 錯(cuò)誤處理 ART 的 JNI 會(huì)在多種情況下引發(fā)錯(cuò)誤,而 Dalvik 則不然缩挑。(同樣地但两,您可以通過(guò)使用 CheckJNI 執(zhí)行測(cè)試來(lái)捕獲大量此種情況)
3): 預(yù)防堆棧大小問(wèn)題  Dalvik 具有單獨(dú)的原生代碼堆棧和 Java 代碼堆棧,并且默認(rèn)的 Java 堆棧大小為 32KB供置,默認(rèn)的原生堆棧大小為 1MB谨湘。

2、Button將總是位于最上層

從5.0開(kāi)始芥丧,在同一個(gè)layout下紧阔,就算你在Button上覆蓋了相應(yīng)的View,Button將總是位于最上層续担。產(chǎn)生原因:stateListAnimator屬性擅耽。谷歌在Material Design中推出,是一個(gè)非常簡(jiǎn)單的方法用來(lái)實(shí)現(xiàn)在可視狀態(tài)之間平滑過(guò)渡。這個(gè)屬性可以通過(guò)android:stateListAnimator進(jìn)行設(shè)置物遇,可以使控件在點(diǎn)擊時(shí)產(chǎn)生不同的交互乖仇。對(duì)于Button憾儒,點(diǎn)擊時(shí)默認(rèn)有個(gè)陰影的效果用于表示按下的狀態(tài)(5.0以前就是簡(jiǎn)單的變色)。 解決方法:可以使用 android:stateListAnimator="@null" 去掉陰影效果而使Button可以被正常的覆蓋乃沙。

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

Android6.0

1起趾、動(dòng)態(tài)權(quán)限

動(dòng)態(tài)權(quán)限適配是 Android 6.0 最先開(kāi)始的,也是 Android 系統(tǒng)對(duì)開(kāi)發(fā)者影響最大的改動(dòng)之一警儒。系統(tǒng)權(quán)限主要分為兩類(lèi)训裆,正常權(quán)限和危險(xiǎn)權(quán)限。不管哪個(gè)版本的android蜀铲,你應(yīng)用中所用到的所有權(quán)限边琉,不管是正常權(quán)限還是危險(xiǎn)權(quán)限,都需要在應(yīng)用Manifest中申明记劝。你的目標(biāo)SDK(targetSdkVersion)是23以及23以上版本:應(yīng)用必須在Manifest中羅列出所有的權(quán)限艺骂,并且在程序運(yùn)行時(shí),它必須請(qǐng)求用戶授予每一個(gè)危險(xiǎn)權(quán)限隆夯,此時(shí)用戶可以授予或者拒絕每一個(gè)權(quán)限钳恕,并且應(yīng)用程序可以繼續(xù)運(yùn)行有限的功能,即使用戶拒絕了權(quán)限請(qǐng)蹄衷。在 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),但是若沒(méi)有請(qǐng)求相應(yīng)的權(quán)限而進(jìn)行操作的話就會(huì)出現(xiàn)應(yīng)用 crash 的情況领舰。

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

權(quán)限組 權(quán)限名稱(chēng)
CALENDAR android.permission.READ_CALENDAR
android.permission.WRITE_CALENDAR
CAMERA android.permission.CAMERA
CALENDAR android.permission.READ_CALENDAR
android.permission.WRITE_CALENDAR
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
android.permission.ANSWER_PHONE_CALLS(8.0新增)
android.permission.READ_PHONE_NUMBERS(8.0新增)
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"/>
<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夫嗓、規(guī)避動(dòng)態(tài)權(quán)限

如果想規(guī)避動(dòng)態(tài)權(quán)限策略也是可以的,配置以下

android {
    ...
    defaultConfig {
        ...
        targetSdkVersion 22 // 不使用api:23以及以上的動(dòng)態(tài)權(quán)限策略
        ...
    }
}

3冲秽、Wifi相關(guān)操作

Android6.0之后舍咖,Wifi的使用更加嚴(yán)格。需要?jiǎng)討B(tài)獲取LOCATION權(quán)限锉桑,如果還想獲取Wifi列表的話還需要打開(kāi)GPS(位置信息)排霉。

  • 首先在AndroidManifest.xml文件中增加以下權(quán)限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
  • 其次還需要?jiǎng)討B(tài)申請(qǐng)定位權(quán)限組
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_CODE_ACCESS_COARSE_LOCATION);
  • 最后如果是用getScanResults()獲取Wifi列表的話還需要打開(kāi)GPS(位置信息)開(kāi)關(guān)。
if(!isGPSOpen()){
    //跳轉(zhuǎn)到手機(jī)原生設(shè)置頁(yè)面,打開(kāi)定位功能
    Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
    this.startActivityForResult(intent,GPS_REQUEST_CODE);
}else{
    //你的業(yè)務(wù)邏輯
}

/**
 * 檢查有沒(méi)打開(kāi)定位
 */
private boolean isGPSOpen() {
    LocationManager locationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
    return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
}

Android7.0

1民轴、FileProvider

在官方7.0的以上的系統(tǒng)中攻柠,嘗試傳遞 file://URI可能會(huì)觸發(fā)FileUriExposedException球订。要應(yīng)用間共享文件,您應(yīng)發(fā)送一項(xiàng) content:// URI辙诞,并授予 URI 臨時(shí)訪問(wèn)權(quán)限辙售。進(jìn)行此授權(quán)的最簡(jiǎn)單方式是使用 FileProvider類(lèi)轻抱。

使用FileProvider授權(quán)

1)飞涂、創(chuàng)建新的FileProvider

當(dāng)你需要以獨(dú)立的模塊分享出去,需要繼承FileProvider祈搜,創(chuàng)建新的FileProvider较店,防止與主工程有沖突

/**
 * 繼承FileProvider,防止沖突
 */
public class RoProvider extends FileProvider {

}

2)容燕、創(chuàng)建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>

各個(gè)標(biāo)簽代表的意義

name path
名稱(chēng)標(biāo)志字符串梁呈,不可以同名 文件夾“相對(duì)路徑”,完整路徑取決于當(dāng)前的標(biāo)簽類(lèi)型
標(biāo)簽 路徑
root-path 代表設(shè)備的根目錄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)、 注冊(cè)FileProvider

authorities:一個(gè)標(biāo)識(shí)醋虏,在當(dāng)前系統(tǒng)內(nèi)必須是唯一值寻咒,一般用包名。
exported:表示該 FileProvider 是否需要公開(kāi)出去颈嚼。
granUriPermissions:是否允許授權(quán)文件的臨時(shí)訪問(wèn)權(quán)限毛秘。這里需要,所以是 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授權(quán)

繞過(guò)版本限制叫挟,刪除Uri的檢測(cè),這樣就可以繞過(guò)7.0的文件共享限制

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

2限煞、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)簽名方案來(lái)簽署您的應(yīng)用适秩。

<img src="https://upload-images.jianshu.io/upload_images/7912789-da6e85a74c243749.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp"/>

說(shuō)明

  • 只勾選V1簽名就是傳統(tǒng)方案簽署,但是在 Android 7.0 上不會(huì)使用V2安全的驗(yàn)證方式硕舆。
  • 只勾選V2簽名7.0以下會(huì)顯示未安裝秽荞,Android 7.0 上則會(huì)使用了V2安全的驗(yàn)證方式。
  • 同時(shí)勾選V1和V2則所有版本都沒(méi)問(wèn)題抚官。

3扬跋、org.apache不支持問(wèn)題

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以后不能使用這個(gè)獲取凌节,會(huì)閃退
// 應(yīng)修改成MODE_PRIVATE
SharedPreferences read = getSharedPreferences(RELEASE_POOL_DATA, MODE_WORLD_READABLE);

5钦听、三個(gè)廣播被禁止監(jiān)聽(tīng)或發(fā)送

CONNECTIVITY_CHANGE 廣播

在后臺(tái)時(shí)不再能接收到 CONNECTIVITY_CHANGE 廣播洒试,前臺(tái)不影響。

ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO 廣播

不能發(fā)送或是接收新增圖片(ACTION_NEW_PICTURE)和新增視頻(ACTION_NEW_VIDEO) 的廣播朴上。

Android8.0

1垒棋、Notification(通知權(quán)限)

Android 8.0之后通知權(quán)限默認(rèn)都是關(guān)閉的,無(wú)法默認(rèn)開(kāi)啟以及通過(guò)程序去主動(dòng)開(kāi)啟痪宰,需要程序員讀取權(quán)限開(kāi)啟情況叼架,然后提示用戶去開(kāi)啟。

  • 判斷權(quán)限是否開(kāi)啟
/**
 * 判斷通知權(quán)限是否開(kāi)啟
 * @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;
    }
}
  • 前往設(shè)置開(kāi)啟權(quán)限
/**
 * 打開(kāi)設(shè)置頁(yè)面打開(kāi)權(quán)限
 *
 * @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中乖订,為了更好的管制通知的提醒,不想一些不重要的通知打擾用戶具练,新增了通知渠道乍构,用戶可以根據(jù)渠道來(lái)屏蔽一些不想要的通知。

  • 創(chuàng)建通知
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, "廣告");
        //創(chuàng)建group
        notificationManager.createNotificationChannelGroup(group);
        //channelId要唯一
        String channelId = "channel_001";
        NotificationChannel adChannel = new NotificationChannel(channelId,
                "推廣信息", NotificationManager.IMPORTANCE_DEFAULT);
        //補(bǔ)充channel的含義(可選)
        adChannel.setDescription("推廣信息");
        //將渠道添加進(jìn)組(先創(chuàng)建組才能添加)
        adChannel.setGroup(groupId);
        //創(chuàng)建channel
        notificationManager.createNotificationChannel(adChannel);
        //創(chuàng)建通知時(shí)扛点,標(biāo)記你的渠道id
        Notification notification = new Notification.Builder(MainActivity.this, channelId)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .setContentTitle("一條新通知")
                .setContentText("這是一條測(cè)試消息")
                .setAutoCancel(true)
                .build();
        notificationManager.notify(1, notification);
    }
}

<img src="https://upload-images.jianshu.io/upload_images/3018964-006db30436c92ddb.png?imageMogr2/auto-orient/strip|imageView2/2/w/635/format/webp"/>

3哥遮、自適應(yīng)啟動(dòng)圖標(biāo)

從Android 8.0系統(tǒng)開(kāi)始,應(yīng)用程序的圖標(biāo)被分為了兩層:前景層和背景層陵究。也就是說(shuō)眠饮,我們?cè)谠O(shè)計(jì)應(yīng)用圖標(biāo)的時(shí)候,需要將前景和背景部分分離畔乙,前景用來(lái)展示應(yīng)用圖標(biāo)的Logo君仆,背景用來(lái)襯托應(yīng)用圖標(biāo)的Logo。需要注意的是牲距,背景層在設(shè)計(jì)的時(shí)候只允許定義顏色和紋理返咱,但是不能定義形狀。注意圖標(biāo)圖層的大小牍鞠,兩層的尺寸必須為108x108dp咖摹,前景圖層中間的72x72dp圖層就是在手機(jī)界面上展示的應(yīng)用圖標(biāo)范圍。這樣系統(tǒng)在四面各留出18dp以產(chǎn)生有趣的視覺(jué)效果难述,如視差或脈沖(動(dòng)畫(huà)視覺(jué)效果由受支持的啟動(dòng)器生成萤晴,視覺(jué)效果可能因發(fā)射器而異)。

  • 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去除了“允許未知來(lái)源”選項(xiàng)店读,如果我們的App具備安裝App的功能,那么AndroidManifest文件需要包含REQUEST_INSTALL_PACKAGES權(quán)限攀芯,未聲明此權(quán)限的應(yīng)用將無(wú)法安裝其他應(yīng)用屯断。當(dāng)然,如果你不想添加這個(gè)權(quán)限,也可以通過(guò)getPackageManager().canRequestPackageInstalls()查詢是否有此權(quán)限殖演,沒(méi)有的話使用Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES這個(gè)action將用戶引導(dǎo)至安裝未知應(yīng)用權(quán)限界面去授權(quán)氧秘。

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

5、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)聽(tīng)數(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"/>
  • 方案2更舞、去掉這個(gè)監(jiān)聽(tīng)刷新的方法畦幢,改為廣播刷新坎吻。

6、靜態(tài)廣播無(wú)法正常接收

Google官方聲明:從android 8.0(API26)開(kāi)始宇葱,對(duì)清單文件中靜態(tài)注冊(cè)廣播接收者增加了限制瘦真,建議大家不要在清單文件中靜態(tài)注冊(cè)廣播接收者,改為動(dòng)態(tài)注冊(cè)黍瞧。當(dāng)然诸尽,如果你還是想用靜態(tài)注冊(cè)的方式也是有方法的,Intent里添加Component參數(shù)可實(shí)現(xiàn)印颤。

  • 發(fā)送靜態(tài)廣播的特殊處理
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 支持最新的全面屏,其中包含為攝像頭和揚(yáng)聲器預(yù)留空間的屏幕缺口年局。 通過(guò) DisplayCutout類(lèi)可確定非功能區(qū)域的位置和形狀际看,這些區(qū)域不應(yīng)顯示內(nèi)容。 要確定這些屏幕缺口區(qū)域是否存在及其位置矢否,使用 getDisplayCutout() 函數(shù)仲闽。

  • 取區(qū)域位置及位置
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);
            }
         }
    }
}
  • 新窗口布局模式,允許應(yīng)用程序請(qǐng)求是否在挖孔區(qū)域布局
class WindowManager.LayoutParams {
    //布局參數(shù)
    int layoutInDisplayCutoutMode;
    //默認(rèn)情況下僵朗,全屏窗口不會(huì)使用到挖孔區(qū)域赖欣,非全屏窗口可正常使用挖孔區(qū)域。
    final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
    //窗口聲明使用挖孔區(qū)域
    final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
    //窗口聲明不使用挖孔區(qū)域
    final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
}
  • 設(shè)置代碼
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

問(wèn)題原因: Android P 限制了明文流量的網(wǎng)絡(luò)請(qǐng)求顶吮,非加密的流量請(qǐng)求(http)都會(huì)被系統(tǒng)禁止掉。解決方案:

  • 方案一:將http請(qǐng)求改為https
  • 方案二:添加usesCleartextTraffic屬性
<application
    android:usesCleartextTraffic="true">
</application>        
  • 方案三:添加資源文件(復(fù)雜)

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過(guò)程中會(huì)遇到 Android 9.0 兼容問(wèn)題導(dǎo)致的Crash让禀,解決方案:

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

4挑社、前臺(tái)服務(wù)需要添加權(quán)限

在安卓9.0版本之后,必須要授予FOREGROUND_SERVICE權(quán)限巡揍,才能夠使用前臺(tái)服務(wù)痛阻,否則會(huì)拋出異常。對(duì)此腮敌,我們只需要在AndroidManifest添加對(duì)應(yīng)的權(quán)限即可阱当,這個(gè)權(quán)限是普通權(quán)限,不需要?jiǎng)討B(tài)申請(qǐng)糜工。

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

5弊添、全面限制靜態(tài)廣播的接收

升級(jí)安卓9.0之后,隱式廣播將會(huì)被全面禁止,在AndroidManifest中注冊(cè)的Receiver將不能夠生效,你需要在應(yīng)用中進(jìn)行動(dòng)態(tài)注冊(cè)淑际。

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

6招拙、非 SDK 接口訪問(wèn)限制

在 Android 9.0 版本中,谷歌加入了非 SDK 接口使用限制,無(wú)論是通過(guò)調(diào)用、反射還是JNI等方式,開(kāi)發(fā)者都無(wú)法對(duì)非 SDK 接口進(jìn)行訪問(wèn)瞬女,此接口的濫用將會(huì)帶來(lái)嚴(yán)重的系統(tǒng)兼容性問(wèn)題。 在開(kāi)發(fā)過(guò)程中努潘,開(kāi)發(fā)者如果調(diào)用了非 SDK 接口诽偷,會(huì)導(dǎo)致應(yīng)用出現(xiàn)crash,無(wú)法啟動(dòng)疯坤;或在運(yùn)行過(guò)程中出現(xiàn)崩潰报慕、閃退等現(xiàn)象;也可能導(dǎo)致應(yīng)用功能不可用等嚴(yán)重兼容性問(wèn)題贴膘,其影響范圍波及所有調(diào)用此接口的應(yīng)用卖子。

那么什么是非SDK接口呢,所謂非SDK接口就是所有不能夠在谷歌官網(wǎng)上查詢到的接口刑峡,谷歌提供了 查詢接口的網(wǎng)站 洋闽。

  • 例如我們通過(guò)反射修改Dialog窗體的顏色

此方法在安卓9.0版本將不能夠正常運(yùn)行,會(huì)拋出NoSuchFieldException突梦,對(duì)于諸如此類(lèi)的調(diào)用官方private方法或者@hide方法诫舅,都將不能使用。

try {
    //通過(guò)反射的方式來(lái)更改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 升級(jí)到 28 之后,如果在項(xiàng)目中用到了 Apache HTTP client 的相關(guān)類(lèi),就會(huì)拋出找不到這些類(lèi)的錯(cuò)誤虚汛。這是因?yàn)楣俜揭呀?jīng)在 Android P 的啟動(dòng)類(lèi)加載器中將其移除匾浪,如果仍然需要使用 Apache HTTP client.在 Manifest 文件中加入:

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

8、Calandar(日歷)

Android 9.0日歷的時(shí)間戳小于0

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

關(guān)于我

持續(xù)更新中...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末卷哩,一起剝皮案震驚了整個(gè)濱河市蛋辈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌将谊,老刑警劉巖冷溶,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異尊浓,居然都是意外死亡逞频,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)栋齿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)苗胀,“玉大人,你說(shuō)我怎么就攤上這事褒颈∑馕祝” “怎么了励堡?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵谷丸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我应结,道長(zhǎng)刨疼,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任鹅龄,我火速辦了婚禮揩慕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扮休。我一直安慰自己迎卤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布玷坠。 她就那樣靜靜地躺著蜗搔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪八堡。 梳的紋絲不亂的頭發(fā)上樟凄,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音兄渺,去河邊找鬼缝龄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的叔壤。 我是一名探鬼主播瞎饲,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼炼绘!你這毒婦竟也來(lái)了企软?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤饭望,失蹤者是張志新(化名)和其女友劉穎仗哨,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體铅辞,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡厌漂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斟珊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苇倡。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖囤踩,靈堂內(nèi)的尸體忽然破棺而出旨椒,到底是詐尸還是另有隱情,我是刑警寧澤堵漱,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布综慎,位于F島的核電站,受9級(jí)特大地震影響勤庐,放射性物質(zhì)發(fā)生泄漏示惊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一愉镰、第九天 我趴在偏房一處隱蔽的房頂上張望米罚。 院中可真熱鬧,春花似錦丈探、人聲如沸录择。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)隘竭。三九已至,卻和暖如春遗锣,著一層夾襖步出監(jiān)牢的瞬間货裹,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工精偿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弧圆,地道東北人赋兵。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像搔预,于是被迫代替她去往敵國(guó)和親霹期。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345