一個好的APP最好支持90%設備,由於不同版本系統提供的API可能不同,所以瞭解不同版本間系統差異很重要,這樣才能更好的適配更多的智能設備。你的應用足不足夠健壯要看你的應用在主流版本運行是否流暢。這篇文章記錄開發過程中遇到的相對重要以及常用的適配方案,希望對讀者有所幫助。
Android 版本號及對應的版本名
版本號 | 版本名 | 中文名 |
---|---|---|
API R | android R | |
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 | 牛軋糖 |
API 24 | android 7.0 Nougat | 牛軋糖 |
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運行時由Android核心庫集和Dalvike虛擬機改成Android核心庫集和ART(Android Runtime)模式。兩者的區別就是Dalvike虛擬機採用了一種被稱爲JIT(just-in-time)的解釋器進行動態編譯,而ART模式則在用戶安裝App是進行預編譯AOT(Ahead-of-time),將android5.X的運行速度提高了3倍左右。
ART的特性:
1: 用戶安裝應用時就進行預編譯操作,將原本在程序運行中時的編譯動作提前到應用安裝時。在省去解釋代碼這一過程之後,應用的運行效率會更高。
缺點:(1) 安裝時間增加 (2) 安裝後的文件佔用更多空間?(外存儲器)
2: 解決垃圾回收 (GC) 問題
在 Dalvik 中,應用常常發現顯式調用 System.gc() 非常有用,可促進垃圾回收 (GC)。對 ART 而言這種做法的必要性低得多,尤其是當您需要通過垃圾回收來預防出現 GC_FOR_ALLOC 類型或減少碎片時。
而且,Android 開源項目 (AOSP) 中正在開發一種緊湊型垃圾回收器,以改善內存管理。
3:預防 JNI 問題
ART 的 JNI 比 Dalvik 的 JNI 更爲嚴格一些。使用 CheckJNI 模式來捕獲常見問題是一種特別實用的方法。
1): 檢查 JNI 代碼中的垃圾回收問題
2): 錯誤處理 ART 的 JNI 會在多種情況下引發錯誤,而 Dalvik 則不然。(同樣地,您可以通過使用 CheckJNI 執行測試來捕獲大量此種情況)
3): 預防堆棧大小問題 Dalvik 具有單獨的原生代碼堆棧和 Java 代碼堆棧,並且默認的 Java 堆棧大小爲 32KB,默認的原生堆棧大小爲 1MB。
2、Button將總是位於最上層
從5.0開始,在同一個layout下,就算你在Button上覆蓋了相應的View,Button將總是位於最上層。產生原因:stateListAnimator屬性。谷歌在Material Design中推出,是一個非常簡單的方法用來實現在可視狀態之間平滑過渡。這個屬性可以通過android:stateListAnimator進行設置,可以使控件在點擊時產生不同的交互。對於Button,點擊時默認有個陰影的效果用於表示按下的狀態(5.0以前就是簡單的變色)。 解決方法:可以使用 android:stateListAnimator="@null" 去掉陰影效果而使Button可以被正常的覆蓋。
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:stateListAnimator="@null"/>
Android6.0
1、動態權限
動態權限適配是 Android 6.0 最先開始的,也是 Android 系統對開發者影響最大的改動之一。系統權限主要分爲兩類,正常權限和危險權限。不管哪個版本的android,你應用中所用到的所有權限,不管是正常權限還是危險權限,都需要在應用Manifest中申明。你的目標SDK(targetSdkVersion)是23以及23以上版本:應用必須在Manifest中羅列出所有的權限,並且在程序運行時,它必須請求用戶授予每一個危險權限,此時用戶可以授予或者拒絕每一個權限,並且應用程序可以繼續運行有限的功能,即使用戶拒絕了權限請。在 Android 6.0 ~ Android 8.0中,如果應用在運行時請求權限並且被授予該權限,系統會錯誤地將屬於同一權限組並且在清單中註冊的其他權限也一起授予應用,即對於同一組內的權限,只要有一個被同意,其他的都會被同意。在 Android 8.0 之後,此行爲已被糾正。系統只會授予應用明確請求的權限。然而一旦用戶爲應用授予某個權限,則所有後續對該權限組中權限的請求都將被自動批准,但是若沒有請求相應的權限而進行操作的話就會出現應用 crash 的情況。
危險權限分組說明
權限組 | 權限名稱 |
---|---|
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 |
對應在清單文件中的展示
<!--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、規避動態權限
如果想規避動態權限策略也是可以的,配置以下
android {
...
defaultConfig {
...
targetSdkVersion 22 // 不使用api:23以及以上的動態權限策略
...
}
}
3、Wifi相關操作
Android6.0之後,Wifi的使用更加嚴格。需要動態獲取LOCATION權限,如果還想獲取Wifi列表的話還需要打開GPS(位置信息)。
- 首先在AndroidManifest.xml文件中增加以下權限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
- 其次還需要動態申請定位權限組
ActivityCompat.requestPermissions(getActivity(),new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_CODE_ACCESS_COARSE_LOCATION);
- 最後如果是用getScanResults()獲取Wifi列表的話還需要打開GPS(位置信息)開關。
if(!isGPSOpen()){
//跳轉到手機原生設置頁面,打開定位功能
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
this.startActivityForResult(intent,GPS_REQUEST_CODE);
}else{
//你的業務邏輯
}
/**
* 檢查有沒打開定位
*/
private boolean isGPSOpen() {
LocationManager locationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
}
Android7.0
1、FileProvider
在官方7.0的以上的系統中,嘗試傳遞 file://URI可能會觸發FileUriExposedException。要應用間共享文件,您應發送一項 content:// URI,並授予 URI 臨時訪問權限。進行此授權的最簡單方式是使用 FileProvider類。
使用FileProvider授權
1)、創建新的FileProvider
當你需要以獨立的模塊分享出去,需要繼承FileProvider,創建新的FileProvider,防止與主工程有衝突
/**
* 繼承FileProvider,防止衝突
*/
public class RoProvider extends FileProvider {
}
2)、創建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>
各個標籤代表的意義
name | path |
---|---|
名稱標誌字符串,不可以同名 | 文件夾“相對路徑”,完整路徑取決於當前的標籤類型 |
標籤 | 路徑 |
---|---|
root-path | 代表設備的根目錄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)、 註冊FileProvider
authorities:一個標識,在當前系統內必須是唯一值,一般用包名。
exported:表示該 FileProvider 是否需要公開出去。
granUriPermissions:是否允許授權文件的臨時訪問權限。這裏需要,所以是 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授權
繞過版本限制,刪除Uri的檢測,這樣就可以繞過7.0的文件共享限制
/**
* 需要在Application中執行
*/
private void detectFileUriExposure() {
Builder builder = new Builder();
StrictMode.setVmPolicy(builder.build());
builder.detectFileUriExposure();
}
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 和傳統簽名方案來簽署您的應用。
說明
- 只勾選V1簽名就是傳統方案簽署,但是在 Android 7.0 上不會使用V2安全的驗證方式。
- 只勾選V2簽名7.0以下會顯示未安裝,Android 7.0 上則會使用了V2安全的驗證方式。
- 同時勾選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以後不能使用這個獲取,會閃退
// 應修改成MODE_PRIVATE
SharedPreferences read = getSharedPreferences(RELEASE_POOL_DATA, MODE_WORLD_READABLE);
5、三個廣播被禁止監聽或發送
CONNECTIVITY_CHANGE 廣播
在後臺時不再能接收到 CONNECTIVITY_CHANGE 廣播,前臺不影響。
ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO 廣播
不能發送或是接收新增圖片(ACTION_NEW_PICTURE)和新增視頻(ACTION_NEW_VIDEO) 的廣播。
Android8.0
1、Notification(通知權限)
Android 8.0之後通知權限默認都是關閉的,無法默認開啓以及通過程序去主動開啓,需要程序員讀取權限開啓情況,然後提示用戶去開啓。
- 判斷權限是否開啓
/**
* 判斷通知權限是否開啓
* @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;
}
}
- 前往設置開啓權限
/**
* 打開設置頁面打開權限
*
* @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中,爲了更好的管制通知的提醒,不想一些不重要的通知打擾用戶,新增了通知渠道,用戶可以根據渠道來屏蔽一些不想要的通知。
- 創建通知
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, "廣告");
//創建group
notificationManager.createNotificationChannelGroup(group);
//channelId要唯一
String channelId = "channel_001";
NotificationChannel adChannel = new NotificationChannel(channelId,
"推廣信息", NotificationManager.IMPORTANCE_DEFAULT);
//補充channel的含義(可選)
adChannel.setDescription("推廣信息");
//將渠道添加進組(先創建組才能添加)
adChannel.setGroup(groupId);
//創建channel
notificationManager.createNotificationChannel(adChannel);
//創建通知時,標記你的渠道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);
}
}
3、自適應啓動圖標
從Android 8.0系統開始,應用程序的圖標被分爲了兩層:前景層和背景層。也就是說,我們在設計應用圖標的時候,需要將前景和背景部分分離,前景用來展示應用圖標的Logo,背景用來襯托應用圖標的Logo。需要注意的是,背景層在設計的時候只允許定義顏色和紋理,但是不能定義形狀。注意圖標圖層的大小,兩層的尺寸必須爲108x108dp,前景圖層中間的72x72dp圖層就是在手機界面上展示的應用圖標範圍。這樣系統在四面各留出18dp以產生有趣的視覺效果,如視差或脈衝(動畫視覺效果由受支持的啓動器生成,視覺效果可能因發射器而異)。
- 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去除了“允許未知來源”選項,如果我們的App具備安裝App的功能,那麼AndroidManifest文件需要包含REQUEST_INSTALL_PACKAGES權限,未聲明此權限的應用將無法安裝其他應用。當然,如果你不想添加這個權限,也可以通過getPackageManager().canRequestPackageInstalls()查詢是否有此權限,沒有的話使用Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES這個action將用戶引導至安裝未知應用權限界面去授權。
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
5、SecurityException的閃退
項目使用了ActiveAndroid,在 8.0 或 8.1 系統上使用 26 或以上的版本的 SDK 時,調用 ContentResolver 的 notifyChange 方法通知數據更新,或者調用 ContentResolver 的 registerContentObserver 方法監聽數據變化時,會出現上述異常。解決方案:
- 方案1、在清單文件配置。
<provider
android:name="com.activeandroid.content.ContentProvider"
android:authorities="com.jz.androidclient"
android:enabled="true"
android:exported="false"/>
- 方案2、去掉這個監聽刷新的方法,改爲廣播刷新。
6、靜態廣播無法正常接收
Google官方聲明:從android 8.0(API26)開始,對清單文件中靜態註冊廣播接收者增加了限制,建議大家不要在清單文件中靜態註冊廣播接收者,改爲動態註冊。當然,如果你還是想用靜態註冊的方式也是有方法的,Intent裏添加Component參數可實現。
- 發送靜態廣播的特殊處理
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 支持最新的全面屏,其中包含爲攝像頭和揚聲器預留空間的屏幕缺口。 通過 DisplayCutout類可確定非功能區域的位置和形狀,這些區域不應顯示內容。 要確定這些屏幕缺口區域是否存在及其位置,使用 getDisplayCutout() 函數。
- 取區域位置及位置
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);
}
}
}
}
- 新窗口布局模式,允許應用程序請求是否在挖孔區域佈局
class WindowManager.LayoutParams {
//佈局參數
int layoutInDisplayCutoutMode;
//默認情況下,全屏窗口不會使用到挖孔區域,非全屏窗口可正常使用挖孔區域。
final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
//窗口聲明使用挖孔區域
final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
//窗口聲明不使用挖孔區域
final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
}
- 設置代碼
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 限制了明文流量的網絡請求,非加密的流量請求(http)都會被系統禁止掉。解決方案:
- 方案一:將http請求改爲https
- 方案二:添加usesCleartextTraffic屬性
<application
android:usesCleartextTraffic="true">
</application>
- 方案三:添加資源文件(複雜)
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過程中會遇到 Android 9.0 兼容問題導致的Crash,解決方案:
if (Build.VERSION.SDK_INT >= 26){
canvas.clipPath(mPath);
} else {
canvas.clipPath(mPath, Region.Op.REPLACE);
}
4、前臺服務需要添加權限
在安卓9.0版本之後,必須要授予FOREGROUND_SERVICE權限,才能夠使用前臺服務,否則會拋出異常。對此,我們只需要在AndroidManifest添加對應的權限即可,這個權限是普通權限,不需要動態申請。
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
5、全面限制靜態廣播的接收
升級安卓9.0之後,隱式廣播將會被全面禁止,在AndroidManifest中註冊的Receiver將不能夠生效,你需要在應用中進行動態註冊。
MyReceiver myReceiver = new MyReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(MY_ACTION);
registerReceiver(myReceiver, intentFilter);
6、非 SDK 接口訪問限制
在 Android 9.0 版本中,谷歌加入了非 SDK 接口使用限制,無論是通過調用、反射還是JNI等方式,開發者都無法對非 SDK 接口進行訪問,此接口的濫用將會帶來嚴重的系統兼容性問題。 在開發過程中,開發者如果調用了非 SDK 接口,會導致應用出現crash,無法啓動;或在運行過程中出現崩潰、閃退等現象;也可能導致應用功能不可用等嚴重兼容性問題,其影響範圍波及所有調用此接口的應用。
那麼什麼是非SDK接口呢,所謂非SDK接口就是所有不能夠在谷歌官網上查詢到的接口,谷歌提供了 查詢接口的網站 。
- 例如我們通過反射修改Dialog窗體的顏色
此方法在安卓9.0版本將不能夠正常運行,會拋出NoSuchFieldException,對於諸如此類的調用官方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 升級到 28 之後,如果在項目中用到了 Apache HTTP client 的相關類,就會拋出找不到這些類的錯誤。這是因爲官方已經在 Android P 的啓動類加載器中將其移除,如果仍然需要使用 Apache HTTP client.在 Manifest 文件中加入:
<uses-library
android:name="org.apache.http.legacy"
android:required="false"/>
8、Calandar(日曆)
Android 9.0日曆的時間戳小於0
- Android 9.0
long timeMil = Calendar.getInstance().getTimeInMillis();//timeMil < 0
- Android 9.0以前
long timeMil = Calendar.getInstance().getTimeInMillis();//timeMil > 0
Android10.0
這裏講的不錯,先佔坑吧。大概總結一下:
Scoped Storage(分區存儲)
- 特定目錄(
App-specific
),使用getExternalFilesDir(String type)
或getExternalCacheDir()
方法訪問。無需權限,且卸載應用時會自動刪除。 - 照片、視頻、音頻這類媒體文件。使用
MediaStore
訪問,訪問其他應用的媒體文件時需要READ_EXTERNAL_STORAGE
權限。 - 其他目錄,使用存儲訪問框架
SAF
(Storage Access Framwork
)
權限變化
- 在後臺運行時訪問設備位置信息需要權限
Android 10
引入了ACCESS_BACKGROUND_LOCATION
權限(危險權限)。該權限允許應用程序在後臺訪問位置。如果請求此權限,則還必須請求ACCESS_FINE_LOCATION
或ACCESS_COARSE_LOCATION
權限。只請求此權限無效果。官方推薦使用前臺服務來實現,在前臺服務中獲取位置信息。<service android:name="MyNavigationService" android:foregroundServiceType="location"> </service>
- 電話、藍牙和WLAN的API需要精確位置權限
上述部分類和方法中必須具有ACCESS_FINE_LOCATION
權限才能使用。 - 新增ACCESS_MEDIA_LOCATION權限
如果你要獲取圖片中的地理位置信息,需要申請ACCESS_MEDIA_LOCATION
權限,並使用MediaStore.setRequireOriginal()
獲取。 - 廢棄PROCESS_OUTGOING_CALLS權限
呼出電話的監聽
後臺啓動 Activity 的限制
簡單解釋就是應用處於後臺時,無法啓動Activity
。因爲此項行爲變更適用於在 Android 10
上運行的所有應用,所以這一限制導致最明顯的問題就是點擊推送信息時,有些應用無法進行正常的跳轉(具體的實現問題導致)。所以針對這類問題,全屏 intent
,注意設置最高優先級和添加USE_FULL_SCREEN_INTENT
權限,這是一個普通權限。比如微信來語音或者視頻通話時,彈出的接聽頁面就是使用這一功能。
深色主題
Android 10 新增了一個系統級的深色主題(在系統設置中開啓)。雖然深色主題並不是強制適配項,但是它可以帶給用戶更好的體驗:
- 可大幅減少耗電量。
OLED
屏幕中每個像素都是自主發光,所以在顯示深色元素時像素所消耗的電流更低,尤其在純黑顏色時像素點可以完全關閉來達到省電的效果。 - 爲弱視以及對強光敏感的用戶提高可視性。深色可以降低屏幕的整體視覺亮度,減少對眼睛的視覺壓力。
- 讓所有人都可以在光線較暗的環境中更輕鬆地使用設備
標識符和數據
- 對不可重置的設備標識符實施了限制
Build.getSerial()
、TelephonyManager.getImei()/getXXId()/getXXNumber()
等唯一標識符方法應用必須具有READ_PRIVILEGED_PHONE_STATE
特許權限才能正常使用。 - 限制了對剪貼板數據的訪問權限
除非您的應用是默認輸入法 (IME
) 或是目前處於焦點的應用,否則它無法訪問Android 10
或更高版本平臺上的剪貼板數據。 - 對啓用和停用 WLAN 實施了限制
以Android 10
或更高版本爲目標平臺的應用無法啓用或停用WLAN
。WifiManager.setWifiEnabled()
方法始終返回false
。如果您需要提示用戶啓用或停用WLAN
,請使用設置面板。
Android11.0
存儲機制更新
- 強制執行分區存儲
爲了給開發者更多時間進行測試,以Android 10
(API
級別29
)爲目標平臺的應用仍可請求requestLegacyExternalStorage
屬性。應用可以利用此標記暫時停用與分區存儲相關的變更,例如授予對不同目錄和不同類型的媒體文件的訪問權限。當您將應用更新爲以Android 11
爲目標平臺後,將無法使用requestLegacyExternalStorage
來停用分區存儲。 - 管理設備存儲空間
不能再刪除其他應用的緩存文件,即使您的應用具有所有文件訪問權限也是如此。通過調用ACTION_MANAGE_STORAGE
intent
操作來檢查可用空間以及提示用戶同意讓您的應用調用ACTION_CLEAR_APP_CACHE
intent
清除所有緩存。 - 權限變更
WRITE_EXTERNAL_STORAGE
權限和WRITE_MEDIA_STORAGE
特許權限將不再提供任何其他訪問權限。 - 所有文件訪問權限
某些應用的核心用例需要訪問大量的文件,如文件管理操作或備份和恢復操作。聲明MANAGE_EXTERNAL_STORAGE
權限,使用ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
intent
操作將用戶引導至一個系統設置頁面開啓。
權限更新
- 一次性權限
在Android 11
中,每當應用請求與位置信息、麥克風或攝像頭相關的權限時,面向用戶的權限對話框就會包含僅限這一次選項。如果用戶在對話框中選擇此選項,系統會向您的應用授予臨時的一次性權限。當您應用的Activity
可見時,您的應用可以繼續訪問相關數據。如果用戶隨後將您的應用轉到後臺,則您的應用對相關數據的訪問權限會在一小段時間後被撤消。 - 數據訪問審覈
提供了支持服務來幫助開發者審覈數據訪問,並將數據訪問與應用中的特定功能相關聯。
位置信息更新
Android 11
進一步強調了用戶對位置信息的控制,方法是添加了一次性權限,並去除了用戶通過應用內提示授予 ACCESS_BACKGROUND_LOCATION
權限的能力,可以創建自定義界面,向用戶解釋您的應用爲什麼需要 ACCESS_BACKGROUND_LOCATION
權限。
軟件包可見性
Android 11
更改了應用查詢同一設備上的其他已安裝應用及與之交互的方式。您可能需要在應用的清單文件中添加 <queries>
元素,以便系統知道要向您的應用顯示哪些其他應用。<queries>
元素可用於描述您的應用可能需要與哪些其他應用交互。
前臺服務類型
如果您的應用以 Android 11
爲目標平臺並且在某項前臺服務中訪問這些類型的數據,則您需要在該前臺服務的聲明的 foregroundServiceType
屬性中添加新的 camera
和 microphone
類型。
<manifest>
<service android:foregroundServiceType="location|camera|microphone"/>
</manifest>
消息框的更新
出於安全方面的考慮,同時也爲了保持良好的用戶體驗,如果包含自定義視圖的消息框是以 Android 11
爲目標平臺的應用從後臺發送的,則系統會屏蔽這些消息框。請注意,仍允許使用文本消息框;此類消息框是使用 Toast.makeText()
創建的,並不調用 setView()
。
關於我
- Email: [email protected]
- Github: https://github.com/yinhaide
- 簡書: https://www.jianshu.com/u/33c3dd2ceaa3
- CSDN: https://blog.csdn.net/yinhaide