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)于我
- Email: 123302687@qq.com
- Github: https://github.com/yinhaide
- 簡(jiǎn)書(shū): http://www.reibang.com/u/33c3dd2ceaa3
- CSDN: https://blog.csdn.net/yinhaide