当前位置 : 主页 > 网络编程 > 其它编程 >

AndroidQ(10.0API29)版本新特性和兼容性适配

来源:互联网 收集:自由互联 发布时间:2023-07-02
摘要1、本文档基于谷歌AndroidQ官方文档和一加Q版本应用兼容性整改指导2、本文档主要对影响比较大的部分进行简单总结,内容并不全面;3、版本号对应关系:Android-QAnd 摘要 1、本文档
摘要1、本文档基于谷歌AndroidQ官方文档和一加Q版本应用兼容性整改指导2、本文档主要对影响比较大的部分进行简单总结,内容并不全面;3、版本号对应关系:Android-QAnd

摘要

1、本文档基于谷歌AndroidQ官方文档和一加Q版本应用兼容性整改指导 2、本文档主要对影响比较大的部分进行简单总结,内容并不全面; 3、版本号对应关系:

Android-Q = Android-10 = Api29 Android-P = Android-9.0 = Api28

一、Android Q 行为变更:版本新特性

权限受影响应用如何启用(影响范围)存储权限访问和共享外部存储设备中的文件的应用adb shell sm set-isolated-storage on(下文详述)定位权限在后台时请求访问用户位置信息的应用这种权限策略在 Android Q 上始终处于启用状态从后台启动Activity不需要用户互动就启动 Activity 的应用关闭允许系统执行后台活动开发者选项即可启用限制设备标识符(deviceId)访问设备序列号或 IMEI 的应用在搭载 Android Q 的设备上安装应用无线扫描权限使用 WLAN API 和 Bluetooth API 的应用以 Android Q 为目标平台

上面的内容官方文档将这一部分内容独立于Q 行为变更:所有应用来介绍,是因为这一部分内容庞大且重要,其中最大的更新就是用户隐私权限变更. 因为无线扫描权限这种权限的变更影响较少。本文不作详述,如有涉及请查阅官方文档。

二、兼容性适配

以下对各个权限分别作介绍以及解决方法。

1、存储权限

Android Q 在外部存储设备中为每个应用提供了一个“隔离存储沙盒”(例如 /sdcard)。任何其他应用都无法直接访问您应用的沙盒文件。由于文件是您应用的私有文件,因此您不再需要任何权限即可在外部存储设备中访问和保存自己的文件。此变更可让您更轻松地保证用户文件的隐私性,并有助于减少应用所需的权限数量。

沙盒,简单而言就是应用私有专属文件夹,并且访问这个文件夹无需权限。

  • 沙箱目录: /sdcard/Android/sandbox/packagename/
  • 任何其他应用都无法直接访问您应用的沙盒文件。由于文件是您应用的私有文件,因此您不再需要任何>权限即可在外部存储设备中访问和保存自己的文件
  • 当app卸载后,沙箱中的文件删除
  • 谷歌官方推荐应用在沙盒内存储文件的地址为 Context.getExternalFilesDir()下的文件夹。比如要存储一张图片,则应放在Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)中。

    以下将按访问的目标文件的地址介绍如何适配。

  • 访问自己文件:Q中用更精细的媒体特定权限替换并取消了 READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE权限,并且无需特定权限,应用即可访问自己沙盒中的文件。
  • 访问系统媒体文件:Q中引入了一个新定义媒体文件的共享集合,如果要访问沙盒外的媒体共享文件,比如照片,音乐,视频等,需要申请新的媒体权限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO,申请方法同原来的存储权限。
  • 访问系统下载文件:对于系统下载文件夹的访问,暂时没做限制,但是,要访问其中其他应用的文件,必须允许用户使用系统的文件选择器应用来选择文件。
  • 访问其他应用沙盒文件:如果你的应用需要使用其他应用在沙盒内创建的文件,请点击使用其他应用的文件,本文不做介绍。
  • 所以请判断当应用运行在Q平台上时,取消对READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE两个权限的申请。并替换为新的媒体特定权限。

    影响范围

    Google 把 Android Q 上会被沙箱化条件设为 Target SDK 至少为 Q (29) 的应用或者运行 Android Q 时全新安装的应用。不符合这个条件的应用将会运行在兼容模式下,在兼容模式中应用行为大致和过去相同,以保证不会出现严重的数据丢失问题。兼容模式在应用重新安装后会被关闭。 注意:即使应用Target SDK <29也会被沙箱化

    影响点

  • 沙箱化后,沙箱目录:/sdcard/Android/sandbox/packagename/,应用不能再通过 Java File API 来互相访问内部存储文件数据,应用访问 “/sdcard” 实质上访问的是你的应用的沙箱目录,可以理解为“存储重定向”; getExternalStoragePublicDirectory(),getExternalStorageDirectory(),/storage/emulated/0 都会直接映射到沙箱目录;访问这些文件路径存在自动映射,因此正常直接使用理论上都是没问题的。
  • 可能存储数据丢失:当用户手机从Android Q以下版本升级到Android Q版本的时候,应用访问不了之前保存在/sdcard下面的内容。
  • 沙箱空间跟过去的内部存储空间内 “Android/data/packagename” 的文件夹一样,会在应用卸载时被永久清除无法恢复,因此比如一些用户主动下载保存的文件固然不能存在沙箱空间,需要存到沙箱外面
  • 情况描述

  • 从Android10开始应用将不可直接访问外部存储(/sdcard)文件,否则抛异常。
  • 在AndroidQ上运行: targetSdkVersion=Q,默认启用过滤视图,应用以外的文件需要通过存储访问框架(SAF,StorageAccessFramework)读写。
  • 解决方法

    方法一、停用过滤视图,使用旧版存储模式

    ...

    方法二、将文件存储到过滤视图中,官方推荐。

    // /Android/data/com.example.androidq/files/Documents File dir = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);

    优点:不用申请读写权限; 缺点:随应用卸载而删除;

    方法三、使用存储访问框架(SAF),由用户指定要读写的文件。 ​ 这个功能Android 4.4(API: 19)就有,参考官方文档。

    方法四、获取用户指定的某个目录的读写权限 ​ 从Android5.0(Api 21)开始就有,官方文档。

    步骤

    1. 申请目录的访问权限 会打开系统的文件目录,由用户自己选择允许访问的目录,不用申请WRITE/READ_EXTERNAL_STORAGE权限。

    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);startActivityForResult(intent, REQ_CODE);

    允许了之后通过onActivityResult()的intent.getData()得到该目录的Uri,通过Uri可获取子目录和文件。这种方式的缺点是应用重装后权限失效,即使可以保存了这个Uri也没用。

    Uri dirUri = intent.getData();// 持久化;应用重装后权限失效,即使知道这个uri也没用SPUtil.setValue(this, SP_DOC_KEY, dirUri.toString());//重要:少这行代码手机重启后会失去权限getContentResolver().takePersistableUriPermission(dirUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);

    2. 通过Uri读写文件

    • 创建文件

    // 在mUri目录(‘DuoKan’目录)下创建'test.txt'文件private void createFile() { DocumentFile documentFile = DocumentFile.fromTreeUri(this, mUri); DocumentFile file = documentFile.createFile("text/plain", "test.txt"); if (file != null }}

    主要用到DocumentFile类,和File类的方法类似,有isFile、isDirectory、exists、listFiles等方法

    • 删除文件

    //删除"test.txt"private void deleteFile() { DocumentFile documentFile = DocumentFile.fromTreeUri(this, mUri); // listFiles(),列出所有的子文件和文件夹 for (DocumentFile file : documentFile.listFiles()) { if (file.isFile() LogUtil.log("deleteFile: " + delete); break; } }}

    • 写入数据

    private void writeFile(Uri uri) { try { ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "w"); //这种方法会覆盖原来文件内容 OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(pfd.getFileDescriptor())); // 不能传uri.toString(),否则FileNotFoundException // OutputStreamWriter output = new OutputStreamWriter(new FileOutputStream(uri.toString(), true)); output.write("这是一段文件写入测试\n"); output.close(); LogUtil.log("写入成功。"); } catch (IOException e) { LogUtil.log(e); }}

    2、定位权限

    • 为了让用户更好地控制应用对位置信息的访问权限,Android Q 引入了新的位置权限ACCESS_BACKGROUND_LOCATION。与现有的 ACCESS_FINE_LOCATION 和ACCESS_COARSE_LOCATION 权限不同,新权限仅会影响应用在后台运行时对位置信息的访问权。除非应用的某个 Activity 可见或应用正在运行前台服务,否则应用将被视为在后台运行。

    • 与iOS系统一样,Q中也加入了后台位置权限ACCESS_BACKGROUND_LOCATION,如果应用需要在后台时也获得用户位置(比如滴滴),就需要动态申请ACCESS_BACKGROUND_LOCATION权限。

    • 当然如果不需要的话,应用就无需任何改动,且谷歌会按照应用的targetSDK作出不同处理: targetSDK <= P 应用如果请求了ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION权限,Q设备会自动帮你申请ACCESS_BACKGROUND_LOCATION权限。

    情况描述

  • 后台应用要获取位置信息需要动态申请权限,
  • 在AndroidQ上运行:
    • targetSdkVersion
    • targetSdkVersion>=Q,需申请;
    • 应用变为后台应用90s后开始定位失败(Pixel AndroidQ-beta6)
  • ACCESS_BACKGROUND_LOCATION不能单独申请,需要和ACCESS_COARSE_LOCATION/ACCESS_FINE_LOCATION一起申请
  • 解决方法

  • 动态申请即可;

  • 启动前台服务

  • 3、禁止后台启动activity

    官方文档

    情况描述

  • AndroidQ上,后台启动Activity会被系统忽略,不管targetSdkVersion多少;
  • AndroidQ上,即使应用有前台服务也不行;
  • AndroidQ以下版本没影响。
  • 解决方法

    发送全屏通知:

    //AndroidManifest 声明新权限,不用动态申请Intent intent = new Intent(this, ScopedStorageActivity.class);PendingIntent pendingIntent = PendingIntent.getActivity(this, REQ_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);Notification notification = new NotificationCompat.Builder(this, Constants.CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle("Incoming call") .setContentText("(919) 555-1234") .setPriority(NotificationCompat.PRIORITY_HIGH) .setCategory(NotificationCompat.CATEGORY_ALARM) //设置全屏通知后,发送通知直接启动Activity .setFullScreenIntent(pendingIntent, true) .build();NotificationManager manager = getSystemService(NotificationManager.class);manager.notify(445456, notification);

    但是:在华为mate20(Api-28)上需要到设置中打开横幅通知;原生AndroidQ(beta6)上有效。

    4、设备硬件标识符访问限制

    限制应用访问不可重设的设备识别码,如 IMEI、序列号等,系统应用不受影响。

    原来的做法

    TelephonyManager tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);tm.getDeviceId();Build.getSerial();

  • 在低于AndroidQ的系统上没问题
  • 在AndroidQ及以上的系统上运行时:
    • 如targetSdkVersion
    • 如targetSdkVersion>=Q,抛异常:
  • SecurityException: getDeviceId: The user 10196 does not meet the requirements to access device identifiers.

    解决方案

    方案一: 使用AndroidId代替,缺点是应用签署密钥或用户(如系统恢复出产设置)不同返回的Id不同。与实际测试结果相符。 经实际测试:相同签名密钥的不同应用androidId相同,不同签名的应用androidId不同。恢复出产设置或升级系统没测。

    String androidId = Settings.System.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);

    方案二:

    通过硬件信息拼接,缺点是还是不能保证唯一。 经测试:似乎与方案一比更稳定,不受密钥影响,但非官方建议,没安全感。

    private static String makeDeviceId(Context context) { String deviceInfo = new StringBuilder() .append(Build.BOARD).append("#") .append(Build.BRAND).append("#") //CPU_ABI,这个值和appp使用的so库是arm64-v8a还是armeabi-v7a有关,舍弃 //.append(Build.CPU_ABI).append("#") .append(Build.DEVICE).append("#") .append(Build.DISPLAY).append("#") .append(Build.HOST).append("#") .append(Build.ID).append("#") .append(Build.MANUFACTURER).append("#") .append(Build.MODEL).append("#") .append(Build.PRODUCT).append("#") .append(Build.TAGS).append("#") .append(Build.TYPE).append("#") .append(Build.USER).append("#") .toString(); try { //22a49a46-b39e-36d1-b75f-a0d0b9c72d6c return UUID.nameUUIDFromBytes(deviceInfo.getBytes("utf8")).toString(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } String androidId = Settings.System.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); return androidId;}

    参考文献

    官方文档

    上一篇:MySql惯用的数据类型优化
    下一篇:没有了
    网友评论