Android版本差異適配方案(5.0-9.0)
一個(gè)好的APP最好支持90%設(shè)備,由于不同版本系統(tǒng)提供的API可能不同,所以了解不同版本間系統(tǒng)差異很重要,這樣才能更好的適配更多的智能設(shè)備。你的應(yīng)用足不足夠健壯要看你的應(yīng)用在主流版本運(yùn)行是否流暢。這篇文章記錄開發(fā)過程中遇到的相對(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核心庫集和Dalvike虛擬機(jī)改成Android核心庫集和ART(Android Runtime)模式。兩者的區(qū)別就是Dalvike虛擬機(jī)采用了一種被稱為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í)。在省去解釋代碼這一過程之后,應(yīng)用的運(yùn)行效率會(huì)更高。
缺點(diǎn):(1) 安裝時(shí)間增加 (2) 安裝后的文件占用更多空間?(外存儲(chǔ)器)
2: 解決垃圾回收 (GC) 問題
在 Dalvik 中,應(yīng)用常常發(fā)現(xiàn)顯式調(diào)用 System.gc() 非常有用,可促進(jìn)垃圾回收 (GC)。對(duì) ART 而言這種做法的必要性低得多,尤其是當(dāng)您需要通過垃圾回收來預(yù)防出現(xiàn) GC_FOR_ALLOC 類型或減少碎片時(shí)。
而且,Android 開源項(xiàng)目 (AOSP) 中正在開發(fā)一種緊湊型垃圾回收器,以改善內(nèi)存管理。
3:預(yù)防 JNI 問題
ART 的 JNI 比 Dalvik 的 JNI 更為嚴(yán)格一些。使用 CheckJNI 模式來捕獲常見問題是一種特別實(shí)用的方法。
1): 檢查 JNI 代碼中的垃圾回收問題
2): 錯(cuò)誤處理 ART 的 JNI 會(huì)在多種情況下引發(fā)錯(cuò)誤,而 Dalvik 則不然。(同樣地,您可以通過使用 CheckJNI 執(zhí)行測試來捕獲大量此種情況)
3): 預(yù)防堆棧大小問題 Dalvik 具有單獨(dú)的原生代碼堆棧和 Java 代碼堆棧,并且默認(rèn)的 Java 堆棧大小為 32KB,默認(rèn)的原生堆棧大小為 1MB。
2、Button將總是位于最上層
從5.0開始,在同一個(gè)layout下,就算你在Button上覆蓋了相應(yīng)的View,Button將總是位于最上層。產(chǎn)生原因:stateListAnimator屬性。谷歌在Material Design中推出,是一個(gè)非常簡單的方法用來實(shí)現(xiàn)在可視狀態(tài)之間平滑過渡。這個(gè)屬性可以通過android:stateListAnimator進(jìn)行設(shè)置,可以使控件在點(diǎn)擊時(shí)產(chǎn)生不同的交互。對(duì)于Button,點(diǎn)擊時(shí)默認(rèn)有個(gè)陰影的效果用于表示按下的狀態(tài)(5.0以前就是簡單的變色)。 解決方法:可以使用 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 最先開始的,也是 Android 系統(tǒng)對(duì)開發(fā)者影響最大的改動(dòng)之一。系統(tǒng)權(quán)限主要分為兩類,正常權(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),但是若沒有請(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 |
| 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列表的話還需要打開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列表的話還需要打開GPS(位置信息)開關(guān)。
if(!isGPSOpen()){
//跳轉(zhuǎn)到手機(jī)原生設(shè)置頁面,打開定位功能
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
this.startActivityForResult(intent,GPS_REQUEST_CODE);
}else{
//你的業(yè)務(wù)邏輯
}
/**
* 檢查有沒打開定位
*/
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í)訪問權(quán)限。進(jìn)行此授權(quán)的最簡單方式是使用 FileProvider類。
使用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 |
|---|---|
| 名稱標(biāo)志字符串,不可以同名 | 文件夾“相對(duì)路徑”,完整路徑取決于當(dāng)前的標(biāo)簽類型 |
| 標(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 是否需要公開出去。
granUriPermissions:是否允許授權(quán)文件的臨時(shí)訪問權(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)
繞過版本限制,刪除Uri的檢測,這樣就可以繞過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)簽名方案來簽署您的應(yīng)用。
<img src="https://upload-images.jianshu.io/upload_images/7912789-da6e85a74c243749.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp"/>
說明
- 只勾選V1簽名就是傳統(tǒng)方案簽署,但是在 Android 7.0 上不會(huì)使用V2安全的驗(yàn)證方式。
- 只勾選V2簽名7.0以下會(huì)顯示未安裝,Android 7.0 上則會(huì)使用了V2安全的驗(yàn)證方式。
- 同時(shí)勾選V1和V2則所有版本都沒問題。
3、org.apache不支持問題
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)聽或發(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)閉的,無法默認(rèn)開啟以及通過程序去主動(dòng)開啟,需要程序員讀取權(quán)限開啟情況,然后提示用戶去開啟。
- 判斷權(quán)限是否開啟
/**
* 判斷通知權(quán)限是否開啟
* @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è)置開啟權(quán)限
/**
* 打開設(shè)置頁面打開權(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ù)渠道來屏蔽一些不想要的通知。
- 創(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("這是一條測試消息")
.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)開始,應(yīng)用程序的圖標(biāo)被分為了兩層:前景層和背景層。也就是說,我們?cè)谠O(shè)計(jì)應(yīng)用圖標(biāo)的時(shí)候,需要將前景和背景部分分離,前景用來展示應(yīng)用圖標(biāo)的Logo,背景用來襯托應(yīng)用圖標(biāo)的Logo。需要注意的是,背景層在設(shè)計(jì)的時(shí)候只允許定義顏色和紋理,但是不能定義形狀。注意圖標(biāo)圖層的大小,兩層的尺寸必須為108x108dp,前景圖層中間的72x72dp圖層就是在手機(jī)界面上展示的應(yīng)用圖標(biāo)范圍。這樣系統(tǒng)在四面各留出18dp以產(chǎn)生有趣的視覺效果,如視差或脈沖(動(dòng)畫視覺效果由受支持的啟動(dòng)器生成,視覺效果可能因發(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去除了“允許未知來源”選項(xiàng),如果我們的App具備安裝App的功能,那么AndroidManifest文件需要包含REQUEST_INSTALL_PACKAGES權(quán)限,未聲明此權(quán)限的應(yīng)用將無法安裝其他應(yīng)用。當(dāng)然,如果你不想添加這個(gè)權(quán)限,也可以通過getPackageManager().canRequestPackageInstalls()查詢是否有此權(quán)限,沒有的話使用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)聽數(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)聽刷新的方法,改為廣播刷新。
6、靜態(tài)廣播無法正常接收
Google官方聲明:從android 8.0(API26)開始,對(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ù)留空間的屏幕缺口。 通過 DisplayCutout類可確定非功能區(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
問題原因: 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過程中會(huì)遇到 Android 9.0 兼容問題導(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 接口訪問限制
在 Android 9.0 版本中,谷歌加入了非 SDK 接口使用限制,無論是通過調(diào)用、反射還是JNI等方式,開發(fā)者都無法對(duì)非 SDK 接口進(jìn)行訪問,此接口的濫用將會(huì)帶來嚴(yán)重的系統(tǒng)兼容性問題。 在開發(fā)過程中,開發(fā)者如果調(diào)用了非 SDK 接口,會(huì)導(dǎo)致應(yīng)用出現(xiàn)crash,無法啟動(dòng);或在運(yùn)行過程中出現(xiàn)崩潰、閃退等現(xiàn)象;也可能導(dǎo)致應(yīng)用功能不可用等嚴(yán)重兼容性問題,其影響范圍波及所有調(diào)用此接口的應(yīng)用。
那么什么是非SDK接口呢,所謂非SDK接口就是所有不能夠在谷歌官網(wǎng)上查詢到的接口,谷歌提供了 查詢接口的網(wǎng)站 。
- 例如我們通過反射修改Dialog窗體的顏色
此方法在安卓9.0版本將不能夠正常運(yùn)行,會(huì)拋出NoSuchFieldException,對(duì)于諸如此類的調(diào)用官方private方法或者@hide方法,都將不能使用。
try {
//通過反射的方式來更改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)類,就會(huì)拋出找不到這些類的錯(cuò)誤。這是因?yàn)楣俜揭呀?jīng)在 Android P 的啟動(dòng)類加載器中將其移除,如果仍然需要使用 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
- 簡書: http://www.itdecent.cn/u/33c3dd2ceaa3
- CSDN: https://blog.csdn.net/yinhaide