Android R(Android 11 API 30)于2020年9月9日正式釋出,随國内各終端廠商在售Android裝置的版本更新更新,應用軟體對Android R 版本的相容适配已迫在眉睫。
對于Android R的新特性,這裡按照以下幾個方面進行了歸納:
分區存儲、權限、隐私、性能、安全
。
官方文檔描述:
https://developer.android.google.cn/about/versions/11## 一、分區存儲
從Android 10(API 29)開始,Android
預設開啟分區存儲
功能,不過Android 10 可通過增加 android:requestLegacyExternalStorage="true"
配置 停用分區存儲
;
從Android 11(API 30)開始,
強制執行分區存儲
,對于Android 11及以上裝置, android:requestLegacyExternalStorage="true"
配置将不再有效。
Android 11 分區存儲官方描述:
https://developer.android.google.cn/training/data-storage#scoped-storage Android 10 預設開啟分區存儲: https://xiaxl.blog.csdn.net/article/details/1031251171.1、通路目錄
開啟分區存儲後,應用預設情況下隻能通路
應用專屬目錄(内部存儲、外部存儲應用專屬目錄)
,以及
本應用所建立的特定類型的媒體檔案
- 應用專屬目錄
包括
内部存儲
、
外部存儲專屬目錄
(若應用包名com.xiaxl.demo):
/data/data/com.xiaxl.demo/files,
/sdcard/Android/data/com.xiaxl.demo/files
分别采用以下API進行通路:
File appFile = new File(context.getFilesDir(), filename);
File appExternalFile = new File(context.getExternalFilesDir(), filename);
- 共享存儲目錄
包括媒體、文檔和其他檔案。例如DCIM、Pictures、Movies、Download等目錄;
注:
Android 10(Android Q)中共享存儲目錄使用MediaStore API通路;
Android 11(Android R)中共享存儲目錄支援MediaStore API與File API通路。
為保證應用在Android 10、Android 11裝置中,使用
File API對共享存儲目錄具有相同的檔案通路權限
。建議在應用 AndroidManifest配置檔案中,增加
requestLegacyExternalStorage="true"
辨別,以
關閉Android 10裝置上的分區存儲功能
,使
分區存儲隻對Android 11以上裝置生效
:
1.2、通路所需權限
應用專屬目錄(
内部存儲
外部存儲專屬目錄
)的讀寫,Android 4.4以上裝置不需要任何權限;
共享存儲路徑的讀寫,需要
READ_EXTERNAL_STORAGE
與
WRITE_EXTERNAL_STORAGE
權限;
Android 11以上裝置中,如果您的應用再次請求
READ_EXTERNAL_STORAGE
權限時,動态權限申請彈窗将變化為
“您的應用正在請求通路照片和媒體”
檔案媒體通路 官方描述:
1.3、共享檔案
如果需要與其他應用共享單個檔案或應用資料,可以使用API:
-
(分享自己的一個或多個檔案)FileProvider
如果應用需要将自己的一個或多個檔案提供給其他應用,安全的做法是向接收方應用發送檔案的内容 URI,并授予對該 URI 的臨時通路權限。
Android
FileProvider
元件提供了
getUriForFile()
方法,用于生成檔案的内容
URI
-
ContentProvider
(擷取替他應用提供的資料)
如果您需要向其他應用提供資料,可以使用
ContentProvider
是一種标準接口,可将一個程序中的資料與另一個程序中運作的代碼進行連。ContentProvider
Android 11 共享檔案官方描述:
1.4、所有檔案的通路權限
有一些應用需要擷取所有檔案的通路權限,例如:檔案管理器軟體。
擷取所有檔案的通路權限,可申請
MANAGE_EXTERNAL_STORAGE
權限。
// 權限配置
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
// 是否擁有MANAGE_EXTERNAL_STORAGE權限判斷
Environment.isExternalStorageManager();
// 跳轉到設定頁,請求使用者授權
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivity(intent);
MANAGE_EXTERNAL_STORAGE
相關官方描述:
https://developer.android.google.cn/training/data-storage/manage-all-files二、權限
Android 11 中對權限進行了如下更改:
- 新增
權限,擷取手機号碼;READ_PHONE_NUMBERS
-
權限調整;背景通路位置
- 使用者
點多次針對某項特定的權限請求
,表示使用者希望拒絕
不再詢問
- 應用
,系統會長時間未使用
自動重置使用者已授予敏感權限
- 針對
授權彈窗新增位置、麥克風、攝像頭
授權按鈕;僅限這一次
-
權限授權方式改變為系統自動授權;SYSTEM_ALERT_WINDOW
參考 Android 11 權限更新官方文檔:
https://developer.android.google.cn/about/versions/11/privacy/permissions#one-time2.1、新增 READ_PHONE_NUMBERS 權限
當應用的
targetSdkVersion>=30
時,使用以下API
擷取手機号碼
時,需要申請
READ_PHONE_NUMBERS
權限,而不再是
READ_PHONE_STATE
-
類和TelephonyManager
類中的TelecomManager
方法。getLine1Number()
-
類中不受支援的TelephonyManager
getMsisdn()
在Android 10及之前的裝置,可以繼續使用
READ_PHONE_STATE
擷取手機号;
對Android11及以上裝置,需擷取
READ_PHONE_NUMBERS
權限,才能擷取手機号;
<manifest>
<!-- 僅在Android 10及以下裝置擷取READ_PHONE_STATE權限,以擷取終端手機号碼-->
<uses-permission android:name="READ_PHONE_STATE"
android:maxSdkVersion="29" />
<!-- Android 11及以上裝置擷取READ_PHONE_NUMBERS權限,以擷取終端手機号碼-->
<uses-permission android:name="READ_PHONE_NUMBERS" />
</manifest>
對于
READ_PHONE_STATE
權限
- Android 10 開始
已經不能再普通應用
資訊;讀取裝置的硬體ID
相關資訊參考
- Android 11 開始
相關API更換為擷取手機号
權限;READ_PHONE_NUMBERS
READ_PHONE_NUMBERS
權限官方API描述:
https://developer.android.google.cn/reference/android/Manifest.permission#READ_PHONE_NUMBERS2.2、背景通路位置權限調整
- 在Android10裝置上,同時
時,并在使用者選擇申請前台、背景位置權限
後,才能獲得背景位置權限。始終允許
- 在Android11裝置上,對于
的應用,同時targetSdkVersion<=29(Android 10)
時,對話框不再提示始終允許字樣,而是提供了位置權限的設定入口,需要申請前台、背景位置權限
才能獲得背景位置權限。使用者在設定頁面選擇始終允許
-
targetSdkVersion=30(Android 11)
時,系統會忽略該請求,無任何響應(申請前台、背景位置權限
)。需首先擷取前台位置權限,再次申請背景位置權限
-
的應用,targetSdkVersion=30(Android 11)
先申請前台位置權限,後申請背景位置權限
背景通路位置權限 官方描述:
https://developer.android.google.cn/training/location/backgrounda、Android10裝置
申請前台、背景位置權限
始終允許
// 在Android10裝置上,同時 申請前台、背景位置權限
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 101);
b、Android11裝置 targetSdkVersion<=29
targetSdkVersion<=29(Android 10)
申請前台、背景位置權限
使用者在設定頁面選擇始終允許
// 在Android11裝置上,targetSdkVersion<=29的應用,同時 申請前台、背景位置權限
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 101);
c、Android11裝置 targetSdkVersion=30 同時申請前台、背景位置權限
-
targetSdkVersion=30(Android 11)
申請前台、背景位置權限
需首先擷取前台位置權限,再次申請背景位置權限
// 在Android11裝置上,targetSdkVersion=30的應用,同時 申請前台、背景位置權限
// 請求無反應,此為錯誤寫法
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 101);
d、Android11裝置 targetSdkVersion=30 依次申請前台、背景位置權限
targetSdkVersion=30(Android 11)
先申請前台位置權限,後申請背景位置權限
// 在Android11裝置上,targetSdkVersion=30的應用,申請前台位置權限
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION}, 101);
Android11裝置上,targetSdkVersion=30的應用,申請背景位置權限,直接跳轉到設定頁面。
// 在Android11裝置上,targetSdkVersion=30的應用,申請背景位置權限
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 101);
2.3、使用者 多次針對某項特定的權限請求
拒絕
多次針對某項特定的權限請求
拒絕
在 Android 11 中,使用者
多次針對某項特定的權限請求
點選了
拒絕
,那麼應用再次請求該項權限時,使用者将不會看到系統權限彈窗,該操作表示使用者希望
不再詢問
2.4、長時間未使用,自動重置已授予敏感權限
在 Android 11 中,當targetSdkVersion>=30時,
應用在一段時間内未使用
,系統會通過
自動重置使用者已授予應用的運作時敏感權限
來保護使用者資料;
2.5、新增“僅限這一次”授權按鈕
從 Android 11(API 級别 30)開始,當應用請求與
位置、麥克風、攝像頭
相關權限時,面向使用者的授權對話框會包含
僅限這一次
選項;如果使用者在對話框中選擇
僅限這一次
,系統會向應用授予臨時的單次授權。
權限申請API使用方式不變:
private void showCameraPreview() {
// 判斷是否擁有Camera權限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED) {
// 進入Camera頁面
// startCamera();
} else {
// 請求Camera權限
requestCameraPermission();
}
}
private void requestCameraPermission() {
// 判斷Camera權限,之前是否已被使用者"拒絕"
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
// 彈窗告訴使用者,為什麼需要Camera權限
Snackbar.make(mLayout, R.string.camera_access_required,
Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
// 請求Camera權限
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.CAMERA},
PERMISSION_REQUEST_CAMERA);
}
}).show();
} else {
// 請求Camera權限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CAMERA);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
if (requestCode == PERMISSION_REQUEST_CAMERA) {
// 使用者授權Camera(使用者選擇"使用使用時允許"、"僅這一次允許")
if (grantResults.length == 1
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission has been granted. Start camera preview Activity.
Snackbar.make(mLayout, R.string.camera_permission_granted,
Snackbar.LENGTH_SHORT)
.show();
startCamera();
}
// 使用者選擇"拒絕"
else {
// Permission request was denied.
Snackbar.make(mLayout, R.string.camera_permission_denied,
Snackbar.LENGTH_SHORT)
.show();
}
}
}
源碼參考:
https://github.com/android/permissions-samples/tree/main/RuntimePermissionsBasic2.6、SYSTEM_ALERT_WINDOW 權限授權方式
在 Android 11 中,
SYSTEM_ALERT_WINDOW
權限授權方式更改為:
根據請求自動向某些應用授予 SYSTEM_ALERT_WINDOW 權限
- 系統會自動向具有
且請求ROLE_CALL_SCREENING
的所有應用授予該權限。如果應用失去SYSTEM_ALERT_WINDOW
,就會失去該權限。ROLE_CALL_SCREENING
為ROLE_CALL_SCREENING
中的常量類,多用于通知使用者将我們的應用替換掉手機自帶的預搭載應用(短信、電話撥号);RoleManager
- 系統會自動向通過
截取螢幕且請求MediaProjection
的所有應用授予該權限,除非使用者已明确拒絕向應用授予該權限。當應用停止截取螢幕時,就會失去該權限。此用例主要用于遊戲直播應用。SYSTEM_ALERT_WINDOW
SYSTEM_ALERT_WINDOW權限 官方描述:
https://developer.android.google.cn/about/versions/11/privacy/permissions#system-alert三、隐私保護
主要更改涉及以下幾個方面:
- 軟體包可見性:擷取其他應用資訊需在
中增加AndroidManifest
标簽;<queries>
- 前台服務:通路位置資訊、攝像頭、麥克風限制;
- 永久 SIM 卡辨別符 ICCID 擷取受限;
-
監聽危險權限的調用,進而保護使用者的私密資料;AppOpsManager.OnOpNotedCallback
這樣對于第三方依賴庫的權限使用申請可以做一個監控
3.1、軟體包可見性
- 在 Android 11 及更高版本裝置中,當應用的
時,如果應用希望擷取其他應用的資訊(比如:包名、軟體名稱),原有方式将無法擷取到。targetSdkVersion>=30
- 如需擷取其他應用資訊,需要在
AndroidManifest
元素标簽,告知系統希望擷取哪些應用的資訊或者哪一類應用的資訊。<queries>
- 如果需要擷取所有應用的資訊(比如:Launcher應用、裝置管理器應用):這種情況隻需要在
中添加AndroidManifest
權限即可。QUERY_ALL_PACKAGES
QUERY_ALL_PACKAGES
權限為普通權限,不需要進行動态申請。但送出應用市場後,應用市場可能會進行稽核
軟體包可見性 官方描述:
<manifest package="com.xiaxl.myapp">
// 1、若知道具體應用的包名
<queries>
<package android:name="com.xiaxl.otherapp01" />
<package android:name="com.xiaxl.otherapp01" />
</queries>
// 2、不知道包名,但想知道某一類App的應用資訊
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="image/jpeg" />
</intent>
</queries>
</manifest>
3.2、前台服務:通路位置資訊、攝像頭、麥克風限制
targetSdkVersion>=30
時,
前台服務
通路
位置資訊、攝像頭、麥克風
時,需添加
foregroundServiceType
<manifest>
// 前台服務通路:位置資訊、攝像頭、麥克風
<service
android:foregroundServiceType="location|camera|microphone" />
</manifest>
前台服務 官方描述:
https://developer.android.google.cn/about/versions/11/privacy/foreground-services3.3、永久 SIM 卡辨別符 ICCID 擷取受限
在 Android 11 及更高版本中,使用
SubscriptionInfo.getIccId()
方法通路不可重置的 ICCID 受到限制。
SubscriptionInfo.getIccId()
方法會傳回一個
非null的空字元串
如需唯一辨別裝置上安裝的 SIM 卡,請改用
getSubscriptionId()
SubscriptionId
會提供一個索引值,用于唯一識别已安裝的 SIM 卡(包括實體 SIM 卡和電子 SIM 卡),除非裝置恢複出廠設定,否則此辨別符的值對于給定 SIM 卡是保持不變的。
3.4、監聽危險權限的調用
Android 11新增
AppOpsManager.OnOpNotedCallback
為開發者提供
對應用危險權限的使用監聽,進而保護使用者的私密資料
當應用以及應用的依賴包中,申請某項危險權限時,
AppOpsManager.OnOpNotedCallback
的對應回調方法将會被調用,進而
列印申請的權限
對應的API調用棧
舉例:
使用位置權限擷取位置資訊
時,将會回調
AppOpsManager.OnOpNotedCallback
中的
onNoted
方法,并列印
使用的權限
對應的API調用棧
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//
AppOpsManager.OnOpNotedCallback appOpsCallback =
new AppOpsManager.OnOpNotedCallback() {
private void logPrivateDataAccess(String opCode, String trace) {
Log.i("xiaxl: ", "opCode: " + opCode + "\n trace: " + trace);
}
@Override
public void onNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
Log.i("xiaxl: ", "---onNoted---");
logPrivateDataAccess(syncNotedAppOp.getOp(),
Arrays.toString(new Throwable().getStackTrace()));
}
@Override
public void onSelfNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
Log.i("xiaxl: ", "---onSelfNoted---");
logPrivateDataAccess(syncNotedAppOp.getOp(),
Arrays.toString(new Throwable().getStackTrace()));
}
@Override
public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncNotedAppOp) {
Log.i("xiaxl: ", "---onAsyncNoted---");
logPrivateDataAccess(asyncNotedAppOp.getOp(),
asyncNotedAppOp.getMessage());
}
};
AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
if (appOpsManager != null) {
appOpsManager.setOnOpNotedCallback(getMainExecutor(), appOpsCallback);
}
}
public void getLocation() {
// 建立歸因
Context attributionContext = createAttributionContext("shareLocation");
// 擷取位置資訊
LocationManager locationManager =
attributionContext.getSystemService(LocationManager.class);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
Location lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
列印日志如下:
---onNoted---
opCode: android:coarse_location
trace:
[com.xiaxl.android_test.MainActivity$1.onNoted(MainActivity.java:42),
android.app.AppOpsManager.readAndLogNotedAppops(AppOpsManager.java:8204),
android.os.Parcel.readExceptionCode(Parcel.java:2304),
android.os.Parcel.readException(Parcel.java:2279),
android.location.ILocationManager$Stub$Proxy.getLastLocation(ILocationManager.java:1225),
android.location.LocationManager.getLastKnownLocation(LocationManager.java:648),
com.xiaxl.android_test.MainActivity.getLocation(MainActivity.java:87),
com.xiaxl.android_test.MainActivity$2.onClick(MainActivity.java:70),
android.view.View.performClick(View.java:7448),
com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:967),
android.view.View.performClickInternal(View.java:7425),
android.view.View.access$3600(View.java:810),
android.view.View$PerformClick.run(View.java:28305),
android.os.Handler.handleCallback(Handler.java:938),
android.os.Handler.dispatchMessage(Handler.java:99),
android.os.Looper.loop(Looper.java:223),
android.app.ActivityThread.main(ActivityThread.java:7656),
java.lang.reflect.Method.invoke(Native Method),
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592),
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)]
從以上日志可以看出,當應用申請
ACCESS_COARSE_LOCATION
權限并
擷取位置資訊時
,列印了應用
申請的權限
對應的API調用棧
AppOpsManager 相關官方描述:
https://developer.android.google.cn/guide/topics/data/audit-access#audit-by-attribution-tag四、性能
- JobScheduler使用頻率進行限制
4.1、JobScheduler使用頻率進行限制
Android 11 為對
JobScheduler
使用頻率進行一定限制。
對于 debuggable 清單屬性設定為 true 的應用,過多的調用
JobScheduler
API 将傳回
RESULT_FAILURE
JobScheduler
主要用于在未來某個時間下滿足一定條件時觸發執行某項任務,例如:
當裝置在空閑狀态, 并且使用wifi時, 自動下載下傳Apk
JobScheduler
典型的使用舉例如下:
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName jobService = new ComponentName(this, MyJobService.class);
//任務Id等于123
JobInfo jobInfo = new JobInfo.Builder(123, jobService)
// 任務最少延遲時間
.setMinimumLatency(5000)
// 任務deadline,當到期沒達到指定條件也會開始執行
.setOverrideDeadline(60000)
// 網絡條件,網絡無需付費時執行
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
// 是否充電
.setRequiresCharging(true)
// 是否在空閑時執行
.setRequiresDeviceIdle(true)
// 裝置重新開機後是否繼續執行
.setPersisted(true)
// 設定退避/重試政策
.setBackoffCriteria(3000,JobInfo.BACKOFF_POLICY_LINEAR)
.build();
scheduler.schedule(jobInfo);
官方描述參考:
https://developer.android.google.cn/about/versions/11/behavior-changes-all官方Demo參考:
https://github.com/googlearchive/android-JobScheduler## 五、安全
- 非 SDK 接口限制
5.1、非 SDK 接口限制
官方從 Android 9(API 級别 28)開始,對應用使用的非 SDK 接口實施了限制。
如果你的APP通過引用
非 SDK 接口
或嘗試
使用反射或 JNI 來擷取句柄
,這些限制就會起作用。官方給出的解釋是為了
提升使用者體驗、降低應用崩潰風險
a、非SDK接口檢測工具
官方給出了一個檢測工具,下載下傳位址:
veridexveridex使用方法:
appcompat.sh --dex-file=apk.apk
b、blacklist、greylist、greylist-max-o、greylist-max-p含義
以上截圖中,blacklist、greylist、greylist-max-o、greylist-max-p含義如下:
- blacklist 黑名單:禁止使用的非SDK接口,運作時直接Crash(是以必須解決)
- greylist 灰名單:即目前版本仍能使用的非SDK接口,但在下一版本中可能變成被限制的非SDK接口
- greylist-max-o: 在targetSDK<=O中能使用,但是在targetSDK>=P中被禁止使用的非SDK接口
- greylist-max-p: 在targetSDK<=P中能使用,但是在targetSDK>=Q中被禁止使用的非SDK接口
非SDK接口限制 官方描述:
https://developer.android.google.cn/about/versions/11/non-sdk-11========== THE END ==========
文章首發于公衆号”CODING技術小館“,如果文章對您有幫助,可關注我的公衆号。