美文网首页
Android 7(N)版本适配指南

Android 7(N)版本适配指南

作者: 怡红快绿 | 来源:发表于2020-06-09 20:31 被阅读0次

一、后台优化

Android 7.0 移除了三项隐式广播 CONNECTIVITY_ACTIONACTION_NEW_PICTUREACTION_NEW_PICTURE ,以帮助优化内存使用和电量消耗。此项变更很有必要,因为隐式广播会在后台频繁启动已注册侦听这些广播的应用。删除这些广播可以显著提升设备性能和用户体验。

移动设备会经历频繁的连接变更,例如在 WLAN 和移动数据之间切换时。目前,可以通过在应用清单中注册一个接收器来侦听隐式 CONNECTIVITY_ACTION 广播,让应用能够监控这些变更。由于很多应用会注册接收此广播,因此单次网络切换即会导致所有应用被唤醒并同时处理此广播。

同理,在之前版本的 Android 中,应用可以注册接收来自其他应用(例如相机)的隐式 ACTION_NEW_PICTUREACTION_NEW_PICTURE 广播。当用户使用相机应用拍摄照片时,这些应用即会被唤醒以处理广播。

为缓解这些问题,Android 7.0 应用了以下优化措施:

  • 面向 Android 7.0 开发的应用不会收到 CONNECTIVITY_ACTION 广播,即使它们已有清单条目来请求接受这些事件的通知。在前台运行的应用如果使用 BroadcastReceiver 请求接收通知,则仍可以在主线程中侦听 CONNECTIVITY_CHANGE
  • 应用无法发送或接收 ACTION_NEW_PICTUREACTION_NEW_VIDEO 广播。此项优化会影响所有应用,而不仅仅是面向 Android 7.0 的应用。

针对这项变更,Android 框架提供了多种解决方案来缓解对这些隐式广播的需求。例如,JobScheduler 和新的 WorkManager 提供了强大的机制,可在满足指定条件时(例如连接到不按流量计费网络时)调度网络操作。现在,您还可以使用 JobScheduler 来响应对内容提供程序的更改。JobInfo 对象可封装 JobScheduler 用来调度作业的参数。当满足作业条件时,系统会在应用的 JobService 上执行此作业。

如果以Android 7.0为目标平台的应用仍然需要监听网络更改在设备连接到不按流量计费的网络时执行批量网络活动,使用隐式广播的方案就无法满足需求。

ConnectivityManager监听网络更改

ConnectivityManager API 提供一个更强大的方法,用于仅在满足指定的网络条件时请求回调。首先我们获取到系统的 ConnectivityManager 服务, 并且使用 registerNetworkCallbackNetworkRequest 对象传递给系统,最终系统会通过 ConnectivityManager.NetworkCallback 回调,将网络变更情况告知给应用。

//获取ConnectivityManager 
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//定义ConnectivityManager.NetworkCallback回调方法
callback = new ConnectivityManager.NetworkCallback() {
    // 可用网络接入
    public void onCapabilitiesChanged(@NotNull Network network, @NotNull NetworkCapabilities networkCapabilities) {
        super.onCapabilitiesChanged(network, networkCapabilities);
        LogUtils.d("onCapabilitiesChanged");
        checkNetworkCapabilities(networkCapabilities);
    }

    @Override
    public void onLost(@NonNull Network network) {
        super.onLost(network);
        if (cm != null) {
            Network activeNetwork = cm.getActiveNetwork();
            if (activeNetwork == null) {
                //连接不到可用网络
                return;
            }
            NetworkCapabilities networkCapabilities = cm.getNetworkCapabilities(activeNetwork);
            checkNetworkCapabilities(networkCapabilities);
        }
    }
};
NetworkRequest.Builder builder = new NetworkRequest.Builder();
if (cm != null) {
    cm.registerNetworkCallback(builder.build(), callback);
}

//判断当前网络连接情况
private void checkNetworkCapabilities(NetworkCapabilities networkCapabilities) {
    if (networkCapabilities == null) {
        return;
    }
    // 表明网络连接成功
    if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
        if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
                networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) {
            // 使用WI-FI
            LogUtils.d("WIFI network");
        } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
                networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
            // 使用蜂窝网络
            LogUtils.d("mobile network");
        } else {
            // 未知网络,包括蓝牙、VPN、LoWPAN
            LogUtils.d("unknown network");
        }
    } else {
        //网络连接失败
    }
}

JobScheduler 设备连接到不按流量计费的网络且正在充电

JobScheduler API 提供了一个稳健可靠的机制来安排满足指定条件(例如连入无限流量网络)时所执行的网络操作。

    public static final int MY_BACKGROUND_JOB = 0;
    ...
    public static void scheduleJob(Context context) {
      JobScheduler js =
          (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
      JobInfo job = new JobInfo.Builder(
        MY_BACKGROUND_JOB,
        new ComponentName(context, MyJobService.class))
          .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
          .setRequiresCharging(true)
          .build();
      js.schedule(job);
    }
    

当满足作业条件时,您的应用会收到一个回调,以运行指定的 JobService.class 中的 onStartJob() 方法。

JobScheduler 的一个新替代工具是WorkManager ,这个 API 可用来调度无论应用进程是否存在都需要保证完成的后台任务。WorkManager 根据设备 API 级别等因素选择运行工作的适当方式(直接在应用进程中的线程上以及使用 JobSchedulerFirebaseJobDispatcherAlarmManager)。此外,WorkManager 不需要 Play 服务,并且提供多项高级功能,例如将任务链接在一起或检查任务状态。要了解详情,请参阅 WorkManager

二、权限更改

Android 7.0 做了一些权限更改,这些更改可能会影响到应用的正常运行。

系统权限更改

为了提高私有文件的安全性,面向 Android 7.0 或更高版本的应用私有目录被限制访问 (0700)。此设置可防止私有文件的元数据泄漏,如它们的大小或存在性。此权限更改有多重副作用:

  • 私有文件的文件权限不应再由所有者放宽,为使用 MODE_WORLD_READABLE 和/或 MODE_WORLD_WRITEABLE 而进行的此类尝试将触发 SecurityException。
    注:迄今为止,这种限制尚不能完全执行。应用仍可能使用原生 API 或 File API 来修改它们的私有目录权限。但是,我们强烈反对放宽私有目录的权限。

  • 传递软件包网域外的 file:// URI 可能给接收器留下无法访问的路径。因此,尝试传递 file:// URI 会触发 FileUriExposedException。分享私有文件内容的推荐方法是使用 FileProvider。

  • DownloadManager 不再按文件名分享私人存储的文件。旧版应用在访问 COLUMN_LOCAL_FILENAME 时可能出现无法访问的路径。面向 Android 7.0 或更高版本的应用在尝试访问 COLUMN_LOCAL_FILENAME 时会触发 SecurityException。通过使用 DownloadManager.Request.setDestinationInExternalFilesDir() 或 DownloadManager.Request.setDestinationInExternalPublicDir() 将下载位置设置为公共位置的旧版应用仍可以访问 COLUMN_LOCAL_FILENAME 中的路径,但是我们强烈反对使用这种方法。对于由 DownloadManager 公开的文件,首选的访问方式是使用ContentResolver.openFileDescriptor()。参考Android使用DownloadManager下载安装包并跳转到安装界面

在应用间共享文件

对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。

要在应用间共享文件,必须使用content:// URI格式的Uri ,并授予 URI 临时访问权限。具体步骤如下:

指定 FileProvider

在应用的AndroidManifest.xml文件中添加provider条目

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.xxx.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

android:authorities 属性用于指定一个或多个 URI 授权方的列表,这些 URI 授权方用于标识内容提供程序提供的数据。列出多个授权方时,用分号将其名称分隔开来。

指定可共享的目录

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path path="images/" name="myimages" />
</paths>

name 属性的作用是FileProvider 将路径段myimages 添加到 files/images/ 子目录中文件的内容 URI 中。

<paths> 元素可以有多个子元素,每个子元素指定一个不同的共享目录。

  • root-path:表示根目录,『/』。
  • files-path:表示 content.getFileDir() 获取到的目录。
  • cache-path:表示 content.getCacheDir() 获取到的目录
  • external-path:表示Environment.getExternalStorageDirectory() 指向的目录。
  • external-files-path:表示 ContextCompat.getExternalFilesDirs() 获取到的目录。
  • external-cache-path:表示 ContextCompat.getExternalCacheDirs() 获取到的目录。

现在已经完整地指定了 FileProvider,该提供器可用于为应用内部存储中的 files/ 目录中的文件或 files/ 的子目录中的文件生成内容 URI。当应用为文件生成内容 URI 时,会包含 <provider> 元素中指定的授权 (com.xxx.fileprovider)、路径 myimages/ 以及文件的名称。

例如使用FileProvider请求files/images/目录下的文件android.jpg的URI,FileProvider将会返回如下URI:

content://com.xxx.fileprovider/myimages/android.jpg

获取指定File文件的Uri:

public Uri getUri(Context context, File file) {
    Uri uri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        //第二个参数为 com.xxx.fileprovider
        uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);
    } else {
        uri = Uri.fromFile(file);
    }
    return uri;
}

相关文章

网友评论

      本文标题:Android 7(N)版本适配指南

      本文链接:https://www.haomeiwen.com/subject/nqpstktx.html