八、Android 11(API Level:30)
1.行为变更(所有应用)
1.1 数据访问审核
为了让应用及其依赖项访问用户私密数据的过程更加透明,Android 11 引入了数据访问审核功能。借助此流程得出的见解,您可以更好地识别和纠正可能出现的意外数据访问。
用户私密数据就是危险权限,这个功能提供了危险权限调用的监听,无论是应用本身还是依赖库中的代码,只要访问到私密数据(危险权限),都会回调给我们。
build.gradle
targetSdkVersion 30
AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private Context attributionContext;
@RequiresApi(api = 30)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, onCreate, Target30");
setContentView(R.layout.activity_main);
//创建归因(attribute)
attributionContext = createAttributionContext("visitLocation");
//监听事件
AppOpsManager.OnOpNotedCallback appOpsCallback =
new AppOpsManager.OnOpNotedCallback() {
private void logPrivateDataAccess(String opCode, String attributionTag, String trace) {
Log.d(TAG, "zwm, logPrivateDataAccess, opCode: " + opCode);
Log.d(TAG, "zwm, logPrivateDataAccess, attributionTag: " + attributionTag);
Log.d(TAG, "zwm, logPrivateDataAccess, trace: " + trace);
}
@Override
public void onNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
logPrivateDataAccess(syncNotedAppOp.getOp(), syncNotedAppOp.getAttributionTag(),
Arrays.toString(new Throwable().getStackTrace()));
}
@Override
public void onSelfNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
logPrivateDataAccess(syncNotedAppOp.getOp(), syncNotedAppOp.getAttributionTag(),
Arrays.toString(new Throwable().getStackTrace()));
}
@Override
public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncNotedAppOp) {
logPrivateDataAccess(asyncNotedAppOp.getOp(), asyncNotedAppOp.getAttributionTag(),
asyncNotedAppOp.getMessage());
}
};
//开启私密数据监听
AppOpsManager appOpsManager = (AppOpsManager) getSystemService(APP_OPS_SERVICE);
if (appOpsManager != null) {
appOpsManager.setOnOpNotedCallback(getMainExecutor(), appOpsCallback);
}
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
getLocation();
}
}, 5000);
}
@RequiresApi(api = Build.VERSION_CODES.M)
public void getLocation() {
LocationManager locationManager = (LocationManager) attributionContext.getSystemService(LOCATION_SERVICE);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "zwm, getLocation");
locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
}
}
日志打印:
2020-09-15 15:41:11.020 23954-23954/com.tomorrow.target30 D/MainActivity: zwm, onCreate, Target30
2020-09-15 15:41:16.172 23954-23954/com.tomorrow.target30 D/MainActivity: zwm, getLocation
2020-09-15 15:41:16.173 23954-23954/com.tomorrow.target30 D/MainActivity: zwm, logPrivateDataAccess, opCode: android:fine_location
2020-09-15 15:41:16.173 23954-23954/com.tomorrow.target30 D/MainActivity: zwm, logPrivateDataAccess, attributionTag: visitLocation
2020-09-15 15:41:16.173 23954-23954/com.tomorrow.target30 D/MainActivity: zwm, logPrivateDataAccess, trace: [com.tomorrow.target30.MainActivity$1.onNoted(MainActivity.java:47), android.app.AppOpsManager.readAndLogNotedAppops(AppOpsManager.java:8322), android.os.Parcel.readExceptionCode(Parcel.java:2316), android.os.Parcel.readException(Parcel.java:2291), android.location.ILocationManager$Stub$Proxy.getLastLocation(ILocationManager.java:1356), android.location.LocationManager.getLastKnownLocation(LocationManager.java:671), com.tomorrow.target30.MainActivity.getLocation(MainActivity.java:84), com.tomorrow.target30.MainActivity$2.run(MainActivity.java:73), android.os.Handler.handleCallback(Handler.java:938), android.os.Handler.dispatchMessage(Handler.java:99), android.os.Looper.loop(Looper.java:246), android.app.ActivityThread.main(ActivityThread.java:8309), java.lang.reflect.Method.invoke(Native Method), com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:593), com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)]
1.2 单次授权
在 Android 11 中,每当应用请求与位置信息、麦克风或摄像头相关的权限时,面向用户的权限对话框会包含仅限这一次选项。如果用户在对话框中选择此选项,系统会向应用授予临时的单次授权。
1.3 权限对话框的可见性
Android 11 建议不要请求用户已选择拒绝的权限。在应用安装到设备上后,如果用户在使用过程中屡次针对某项特定的权限点按拒绝,此操作表示其希望“不再询问”。
1.4 应用使用情况统计信息
为了更好地保护用户,Android 11 将每个用户的应用使用情况统计信息存储在凭据加密存储空间中。因此,系统和任何应用都无法访问该数据,除非 isUserUnlocked() 返回 true,这发生在出现以下某种情况之后:
- 用户在系统启动后首次解锁其设备。
- 用户在设备上切换到自己的帐号。
如果您的应用已绑定到 UsageStatsManager 的实例,请检查您是否是在用户解锁其设备后在此对象上调用方法。如果并非如此,该 API 现在会返回 null 或空值。
2.行为变更(以 Android 11 为目标平台的应用)
2.1 分区存储强制执行
对外部存储目录的访问仅限于应用专属目录,以及应用已创建的特定类型的媒体。
关于分区存储,在 Android10 就已经推行了,简单的说,就是应用对于文件的读写只能在沙盒环境,也就是属于自己应用的目录里面读写。其他媒体文件可以通过 MediaStore 进行访问。在 targetSdkVersion = 29 的应用中,设置 android:requestLegacyExternalStorage="true",就可以不启动分区存储,让以前的文件读取正常使用。但是 targetSdkVersion = 30 中不行了,强制开启分区存储,如果是覆盖安装,可以增加android:preserveLegacyExternalStorage="true",暂时关闭分区存储,好让开发者完成数据迁移的工作,只要卸载重装就会失效了。
build.gradle
targetSdkVersion 30
AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, onCreate, Target30");
setContentView(R.layout.activity_main);
try {
saveFile();
} catch (IOException e) {
e.printStackTrace();
}
}
private void saveFile() throws IOException {
if (PermissionChecker.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED) {
//getExternalStoragePublicDirectory方法被弃用,分区存储开启后就不允许访问了
String filePath = Environment.getExternalStoragePublicDirectory("").toString() + "/test.txt";
Log.d(TAG, "zwm, filePath: " + filePath);
FileWriter fileWriter = new FileWriter(filePath);
fileWriter.write("Hello, Android!");
fileWriter.close();
Log.d(TAG, "zwm, file write success");
} else {
Log.d(TAG, "zwm, permission not granted");
}
}
}
日志打印:
2020-09-15 09:27:09.425 23168-23168/com.tomorrow.target30 D/MainActivity: zwm, onCreate, Target30
2020-09-15 09:27:09.518 23168-23168/com.tomorrow.target30 D/MainActivity: zwm, filePath: /storage/emulated/0/test.txt
2020-09-15 09:27:09.519 23168-23168/com.tomorrow.target30 W/System.err: java.io.FileNotFoundException: /storage/emulated/0/test.txt: open failed: EACCES (Permission denied)
2.2 后台位置信息访问权限
在搭载 Android 11 的设备上,当应用中的某项功能请求在后台访问位置信息时,用户看到的系统对话框不再包含用于启用后台位置信息访问权限的按钮。如需启用后台位置信息访问权限,用户必须在设置页面上针对应用的位置权限设置一律允许选项。
从 Android 10 系统的设备开始,就需要请求后台位置权限 ACCESS_BACKGROUND_LOCATION,并选择始终允许才能获得后台位置权限。Android 11 设备上再次加强对后台权限的管理,主要表现在系统对话框上,对话框不再提示始终允许字样,而是提供了位置权限的设置入口,需要在设置页面选择始终允许才能获得后台位置权限。
在搭载 Android 11 系统的设备上,targetVersion小于30的时候,可以前台后台位置权限一起申请,并且对话框提供了文字说明,表示需要随时获取用户位置信息,进入设置选择始终允许即可。但是targetVersion为30的时候,你必须单独申请后台位置权限,而且要在获取前台权限之后,顺序不能乱,并且无任何提示,需要开发者自己设计提示样式。
build.gradle
targetSdkVersion 30
AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
String[] foregroundPermissions = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION};
String[] backgroundPermissions = {Manifest.permission.ACCESS_BACKGROUND_LOCATION};
String[] permissions = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, onCreate, Target30");
setContentView(R.layout.activity_main);
checkLocationPermissions();
}
private void checkLocationPermissions() {
boolean foregroundGranted = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
Log.d(TAG, "zwm, foregroundGranted: " + foregroundGranted);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (!foregroundGranted) {
ActivityCompat.requestPermissions(this, foregroundPermissions, 90);
}
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
boolean backgroundGranted = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED;
Log.d(TAG, "zwm, backgroundGranted: " + backgroundGranted);
if (!foregroundGranted) {
ActivityCompat.requestPermissions(this, permissions, 91);
} else if (!backgroundGranted) {
ActivityCompat.requestPermissions(this, backgroundPermissions, 92);
}
} else {
boolean backgroundGranted = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED;
Log.d(TAG, "zwm, backgroundGranted: " + backgroundGranted);
if (!foregroundGranted) {
ActivityCompat.requestPermissions(this, foregroundPermissions, 93);
} else if (!backgroundGranted) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("需要提供后台位置权限,请在设置页面选择始终允许")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this, backgroundPermissions, 94);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.create().show();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Log.d(TAG, "zwm, requestCode: " + requestCode);
for (int i = 0; i < permissions.length; i++) {
Log.d(TAG, "zwm, permission: " + permissions[i] + ", granted: " + grantResults[i]);
}
}
}
日志打印:
2020-09-15 14:22:36.850 16409-16409/? D/MainActivity: zwm, onCreate, Target30
2020-09-15 14:22:36.890 16409-16409/? D/MainActivity: zwm, foregroundGranted: false
2020-09-15 14:22:36.890 16409-16409/? D/MainActivity: zwm, backgroundGranted: false
2020-09-15 14:22:38.899 16409-16409/com.tomorrow.target30 D/MainActivity: zwm, requestCode: 93
2020-09-15 14:22:38.899 16409-16409/com.tomorrow.target30 D/MainActivity: zwm, permission: android.permission.ACCESS_COARSE_LOCATION, granted: 0
2020-09-15 14:22:38.899 16409-16409/com.tomorrow.target30 D/MainActivity: zwm, permission: android.permission.ACCESS_FINE_LOCATION, granted: 0
2020-09-15 14:22:43.041 16409-16409/com.tomorrow.target30 D/MainActivity: zwm, onCreate, Target30
2020-09-15 14:22:43.054 16409-16409/com.tomorrow.target30 D/MainActivity: zwm, foregroundGranted: true
2020-09-15 14:22:43.055 16409-16409/com.tomorrow.target30 D/MainActivity: zwm, backgroundGranted: false
2020-09-15 14:22:47.245 16409-16409/com.tomorrow.target30 D/MainActivity: zwm, requestCode: 94
2020-09-15 14:22:47.245 16409-16409/com.tomorrow.target30 D/MainActivity: zwm, permission: android.permission.ACCESS_BACKGROUND_LOCATION, granted: 0
2.3 软件包可见性
Android 11 更改了应用查询用户已在设备上安装的其他应用以及与之交互的方式。使用新的 <queries> 元素,应用可以定义一组自身可访问的其他应用。通过告知系统应向您的应用显示哪些其他应用,此元素有助于鼓励最小权限原则。
在 Android 11 中,如果你想去获取其他应用的信息,比如包名,名称等,不能直接获取了,必须在清单文件中添加 <queries> 元素,告知系统你要获取哪些应用信息或者哪一类应用。
<manifest>
<queries>
<package android:name="com.tomorrow.target28" />
</queries>
</manifest>
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "zwm, onCreate, Target30");
setContentView(R.layout.activity_main);
PackageManager pm = getPackageManager();
List<ApplicationInfo> list = pm.getInstalledApplications(PackageManager.GET_META_DATA);
for (ApplicationInfo app : list) {
Log.d(TAG, "zwm, packageName: " + app.packageName);
}
}
}
2.4 自动重置权限
如果应用以 Android 11 为目标平台并且数月未使用,系统会通过自动重置用户已授予应用的运行时敏感权限来保护用户数据。此操作与用户在系统设置中查看权限并将应用的访问权限级别更改为拒绝的做法效果一样。如果应用已遵循有关在运行时请求权限的最佳做法,那么您不必对应用进行任何更改。这是因为,当用户与应用中的功能互动时,您应该会验证相关功能是否具有所需权限。
2.5 前台服务类型
从 Android 9 开始,应用仅限于在前台访问摄像头和麦克风。为了进一步保护用户,Android 11 更改了前台服务访问摄像头和麦克风相关数据的方式。如果您的应用以 Android 11 为目标平台并且在某项前台服务中访问这些类型的数据,您需要在该前台服务的声明的 foregroundServiceType 属性中添加新的 camera 和 microphone 类型。
例如,如果应用某项前台服务需要访问位置信息、摄像头和麦克风,那么需要这样添加:
<manifest>
<service ...
android:foregroundServiceType="location|camera|microphone" />
</manifest>
网友评论