Android targetSdkVersion 从22提到25 你需要知道的一切

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

Android 6.0

  • 运行时权限

相机,图库,下载,语音,定位....
此版本引入了一种新的权限模式,如今,用户可直接在运行时管理应用权限。这种模式让用户能够更好地了解和控制权限,同时为应用开发者精简了安装和自动更新过程。用户可为所安装的各个应用分别授予或撤销权限。
对于以 Android 6.0(API 级别 23)或更高版本为目标平台的应用,请务必在运行时检查和请求权限。要确定您的应用是否已被授予权限,请调用新增的 checkSelfPermission()方法。要请求权限,请调用新增的requestPermissions() 方法。即使您的应用并不以 Android 6.0(API 级别 23)为目标平台,您也应该在新权限模式下测试您的应用。如需了解有关在您的应用中支持新权限模式的详情,请参阅使用系统权限。如需了解有关如何评估新模式对应用的影响的提示,请参阅权限最佳做法
权限管理工具类

package cn.loveshow.live.util;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;

import java.util.ArrayList;
import java.util.Arrays;


/**
 * Created by Fungo_Xiaoke on 2017/5/4 14:18.
 * eamil:luoxiaoke@yuntutv.net
 * 权限工具类
 */
public class PermissionUtils {


/**
 * @param context     上下文
 * @param activity    activity
 * @param permissions 权限数组
 * @param requestCode 申请码
 * @return true 有权限  false 无权限
 */
public static boolean checkAndApplyfPermissionActivity(Activity activity, String[] permissions, int requestCode) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        permissions = checkPermissions(activity, permissions);
        if (permissions != null && permissions.length > 0) {
            ActivityCompat.requestPermissions(activity, permissions, requestCode);
            return false;
        } else {
            return true;
        }
    } else {
        return true;
    }
}

/**
 * @param context     上下文
 * @param mFragment   fragment
 * @param permissions 权限数组
 * @param requestCode 申请码
 * @return true 有权限  false 无权限
 */
public static boolean checkAndApplyfPermissionFragment( Fragment mFragment, String[] permissions, int requestCode) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        permissions = checkPermissions(mFragment.getActivity(), permissions);
        if (permissions != null && permissions.length > 0) {
            if (mFragment.getActivity() != null) {
                mFragment.requestPermissions(permissions, requestCode);
            }
            return false;
        } else {
            return true;
        }
    } else {
        return true;
    }
}

/**
 * @param context     上下文
 * @param permissions 权限数组
 * @return 还需要申请的权限
 */
private static String[] checkPermissions(Context context, String[] permissions) {
    if (permissions == null || permissions.length == 0) {
        return new String[0];
    }
    ArrayList<String> permissionLists = new ArrayList<>();
    permissionLists.addAll(Arrays.asList(permissions));
    for (int i = permissionLists.size() - 1; i >= 0; i--) {
        if (ContextCompat.checkSelfPermission(context, permissionLists.get(i)) == PackageManager.PERMISSION_GRANTED) {
            permissionLists.remove(i);
        }
    }

    String[] temps = new String[permissionLists.size()];
    for (int i = 0; i < permissionLists.size(); i++) {
        temps[i] = permissionLists.get(i);
    }
    return temps;
    }


    /**
     * 检查申请的权限是否全部允许
     */
    public static boolean checkPermission(int[] grantResults) {
        if (grantResults == null || grantResults.length == 0) {
            return true;
        } else {
            int temp = 0;
            for (int i : grantResults) {
                if (i == PackageManager.PERMISSION_GRANTED) {
                    temp++;
                }
            }
            return temp == grantResults.length;
        }
    }

/**
 * 没有获取到权限的提示
 *
 * @param permissions 权限名字数组
 */
public static void showPermissionsToast(Activity activity, @NonNull String[] permissions) {
    if (permissions.length > 0) {
        for (String permission : permissions) {
            showPermissionToast(activity, permission);
        }
     }
   }

/**
 * 没有获取到权限的提示
 *
 * @param permission 权限名字
 */
private static void showPermissionToast(Activity activity, @NonNull String permission) {
    if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
        //用户勾选了不再询问,提示用户手动打开权限
        switch (permission) {
            case Manifest.permission.CAMERA:
                ToastUtils.showShort("相机权限已被禁止,请在应用管理中打开权限");
                break;
            case Manifest.permission.WRITE_EXTERNAL_STORAGE:
                ToastUtils.showShort("文件权限已被禁止,请在应用管理中打开权限");
                break;
            case Manifest.permission.RECORD_AUDIO:
                ToastUtils.showShort("录制音频权限已被禁止,请在应用管理中打开权限");
                break;
            case Manifest.permission.ACCESS_FINE_LOCATION:
                ToastUtils.showShort("位置权限已被禁止,请在应用管理中打开权限");
                break;
        }
    } else {
        //用户没有勾选了不再询问,拒绝了权限申请
        switch (permission) {
            case Manifest.permission.CAMERA:
                ToastUtils.showShort("没有相机权限");
                break;
            case Manifest.permission.WRITE_EXTERNAL_STORAGE:
                ToastUtils.showShort("没有文件读取权限");
                break;
            case Manifest.permission.RECORD_AUDIO:
                ToastUtils.showShort("没有录制音频权限");
                break;
            case Manifest.permission.ACCESS_FINE_LOCATION:
                ToastUtils.showShort("没有位置权限");
                break;
          }
     }
  }
}

用法

   if (PermissionUtils.checkAndApplyfPermissionActivity(this,
        new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 
        REQUESRCARMEA)) {
       //获取到权限的操作  没有权限会申请权限  然后在onRequestPermissionsResult处理申请的结果
   }

   @Override
   public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
       super.onRequestPermissionsResult(requestCode, permissions, grantResults);
       if (PermissionUtils.checkPermission(grantResults)) {
           //申请权限成功
            switch (requestCode) {
                 case 0x001:
                     //dosomething
                     break;
                 case 0x002:
                    //dosomething
                     break;
                 ...
            }
       } else {
           //提示没有什么权限
           PermissionUtils.showPermissionsToast(activity, permissions);
            //or 去权限管理界面
            //gotoPermissionManager(mContext);
       }
   }

没有权限去权限管理界面

/**
 * 去应用权限管理界面
 */
public static void gotoPermissionManager(Context context) {
    Intent intent;
    ComponentName comp;
    //防止刷机出现的问题
    try {
        switch (Build.MANUFACTURER) {
            case "Huawei":
                intent = new Intent();
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity");
                intent.setComponent(comp);
                context.startActivity(intent);
                break;
            case "Meizu":
                intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
                intent.addCategory(Intent.CATEGORY_DEFAULT);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                context.startActivity(intent);
                break;
            case "Xiaomi":
                String rom = getSystemProperty("ro.miui.ui.version.name");
                if ("v5".equals(rom)) {
                    Uri packageURI = Uri.parse("package:" + context.getApplicationInfo().packageName);
                    intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
                } else {//if ("v6".equals(rom) || "v7".equals(rom)) {
                    intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
                    intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
                    intent.putExtra("extra_pkgname", context.getPackageName());
                }
                context.startActivity(intent);
                break;
            case "Sony":
                intent = new Intent();
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                comp = new ComponentName("com.sonymobile.cta", "com.sonymobile.cta.SomcCTAMainActivity");
                intent.setComponent(comp);
                context.startActivity(intent);
                break;
            case "OPPO":
                intent = new Intent();
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                comp = new ComponentName("com.color.safecenter", "com.color.safecenter.permission.PermissionManagerActivity");
                intent.setComponent(comp);
                context.startActivity(intent);
                break;
            case "LG":
                intent = new Intent("android.intent.action.MAIN");
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                comp = new ComponentName("com.android.settings", "com.android.settings.Settings$AccessLockSummaryActivity");
                intent.setComponent(comp);
                context.startActivity(intent);
                break;
            case "Letv":
                intent = new Intent();
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                comp = new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.PermissionAndApps");
                intent.setComponent(comp);
                context.startActivity(intent);
                break;
            default:
                getAppDetailSettingIntent(context);
                break;
        }
    } catch (Exception e) {
        getAppDetailSettingIntent(context);
    }
}

/**
 * 获取系统属性值
 */
public static String getSystemProperty(String propName) {
    String line;
    BufferedReader input = null;
    try {
        Process p = Runtime.getRuntime().exec("getprop " + propName);
        input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
        line = input.readLine();
        input.close();
    } catch (IOException ex) {
        Log.e(TAG, "Unable to read sysprop " + propName, ex);
        return null;
    } finally {
        if (input != null) {
            try {
                input.close();
            } catch (IOException e) {
                Log.e(TAG, "Exception while closing InputStream", e);
            }
        }
    }
    return line;
}

  //以下代码可以跳转到应用详情,可以通过应用详情跳转到权限界面(6.0系统测试可用)
 public static void getAppDetailSettingIntent(Context context) {
    Intent localIntent = new Intent();
    localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    if (Build.VERSION.SDK_INT >= 9) {
        localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
        localIntent.setData(Uri.fromParts("package", context.getPackageName(), null));
    } else if (Build.VERSION.SDK_INT <= 8) {
        localIntent.setAction(Intent.ACTION_VIEW);
        localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
        localIntent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName());
    }
    launchApp(context, localIntent);
}

  /**
 * 安全的启动APP
 */
public static boolean launchApp(Context ctx, Intent intent) {
    if (ctx == null)
        throw new NullPointerException("ctx is null");
    try {
        ctx.startActivity(intent);
        return true;
    } catch (ActivityNotFoundException e) {
        Logger.e(e);
        return false;
    }
}
  • 取消支持 Apache HTTP 客户端

Android 6.0 版移除了对 Apache HTTP 客户端的支持。如果您的应用使用该客户端,并以 Android 2.3(API 级别 9)或更高版本为目标平台,请改用HttpURLConnection 类。此 API 效率更高,因为它可以通过透明压缩和响应缓存减少网络使用,并可最大限度降低耗电量。要继续使用 Apache HTTP API,您必须先在 build.gradle 文件中声明以下编译时依赖项:

  android {
      useLibrary 'org.apache.http.legacy'
  }
  • BoringSSL

Android 正在从使用 OpenSSL 库转向使用 BoringSSL 库。如果您要在应用中使用 Android NDK,请勿链接到并非 NDK API 组成部分的加密库,如libcrypto.so和 libssl.so。这些库并非公共 API,可能会在不同版本和设备上毫无征兆地发生变化或出现故障。此外,您还可能让自己暴露在安全漏洞的风险之下。请改为修改原生代码,以通过 JNI 调用 Java 加密 API,或静态链接到您选择的加密库。

bugly错误
bugly错误
  • 通知

此版本移除了 Notification.setLatestEventInfo()方法。请改用 Notification.Builder 类来构建通知。要重复更新通知,请重复使用Notification.Builder 实例。调用 build() 方法可获取更新后的 Notification 实例。
adb shell dumpsys notification 命令不再打印输出您的通知文本。请改用 adb shell dumpsys notification --noredact 命令打印输出 notification 对象中的文本。build()方法在4.1以上(16+)的系统才能用。

notification
例子
gif
  • 音频管理器变更

不再支持通过 AudioManager 类直接设置音量或将特定音频流 静音。[setStreamSolo()](https://developer.android.google.cn/reference/android/media/AudioManager.html#setStreamSolo(int, boolean)) 方法已弃用,您应该改为调用 [requestAudioFocus()](https://developer.android.google.cn/reference/android/media/AudioManager.html#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int)) 方法。类似地,[setStreamMute()](https://developer.android.google.cn/reference/android/media/AudioManager.html#setStreamMute(int, boolean)) 方法也已弃用,请改为调用 [adjustStreamVolume()](https://developer.android.google.cn/reference/android/media/AudioManager.html#adjustStreamVolume(int, int, int)) 方法并传入方向值 ADJUST_MUTEADJUST_UNMUTE

  • Android 密钥库变更

从此版本开始,Android 密钥库提供程序不再支持 DSA。但仍支持 ECDSA。
停用或重置安全锁定屏幕时(例如,由用户或设备管理员执行此类操作时),系统将不再删除需要闲时加密的密钥,但在上述事件期间会删除需要闲时加密的密钥。

  • APK 验证

该平台现在执行的 APK 验证更为严格。如果在清单中声明的文件在 APK 中并不存在,该 APK 将被视为已损坏。移除任何内容后必须重新签署 APK。

http://www.jianshu.com/p/95790125b7f4
http://blog.csdn.net/lxk_1993/article/details/73784883

Android7.0

  • 系统权限更改

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

  • 在应用间共享文件

对于面向 Android 7.0 的应用,Android 框架执行的 StrictModeAPI 政策禁止在您的应用外部公开 file://URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并现 FileUriExposedException。异常。要在应用间共享文件,您应发送一项 content://URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用FileProvider类。如需了解有关权限和共享文件的详细信息,请参阅共享文件

  • FileProvider用法

AndroidManiFest.xml添加

  <application>
  ...
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.fileProvider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>
  ...
  </application>
配置applicationId

res目录下新建xml文件夹,创建provider_paths.xml文件

  <?xml version="1.0" encoding="utf-8"?>
  <resources>
    <paths>
    <!--  前面两个是bugly的 -->
    <!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
    <external-path
        name="beta_external_path"
        path="Download/" />
    <!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
    <external-path
        name="beta_external_files_path"
        path="Android/data/" />

    <external-path
        name="sdcard_files"
        path="" />
    <!--相机相册裁剪-->
    <external-files-path
        name="camera_has_sdcard"
        path="" />
    <files-path
        name="camera_no_sdcard"
        path="" />
    </paths>
    <!--<paths>-->
    <!-- xml文件是唯一设置分享的目录 ,不能用代码设置-->
     <!--1.<files-path>        getFilesDir()  /data/data//files目录-->
     <!--2.<cache-path>        getCacheDir()  /data/data//cache目录-->
     <!--3.<external-path>       Environment.getExternalStorageDirectory()  -->
     <!--4.<external-files-path>    
       Context.getExternalFilesDir(String)  Context.getExternalFilesDir(null)  
       == SDCard/Android/data/你的应用的包名/files/ 目录-->
     <!--5.<external-cache-path>      Context.getExternalCacheDir().-->

     <!--  path :代表设置的目录下一级目录 eg:<external-path path="images/"-->
     <!--整个目录为Environment.getExternalStorageDirectory()+"/images/"-->
     <!--name: 代表定义在Content中的字段 eg:name = "myimages" ,并且请求的内容的文件名为default_image.jpg-->
     <!--则 返回一个URI   content://com.example.myapp.fileprovider/myimages/default_image.jpg-->

   <!--</paths>-->
  </resources>

确认下路径名

路径

路径

FileProvider 头部设置的对应标签

FileProvider

FileProvider 获取对应路径逻辑 解析xml文件 对比对应的标签 获取对应的路径

FileProvider

**修改所有用到Uri的地方 图中的 BuildConfig.APPLICATION_ID 最好还是改成 context.getPackageName() **


Uri修改

官方链接:FileProvider

  • APK Signature Scheme v2

Android 7.0引入了全新的 APK Signature Scheme v2。这是加强对包的校验,启动了新的签名后,像美团的多渠道打包方案在7.0机器上就会报错了。
解决的办法也很简单,官方提供了关闭v2签名的方法,只需要在gradle上配置一下即可:
signingConfigs {
release {
.......
v2SigningEnabled false
}
}
参考:Android7.0适配
链接: APK Signature Scheme v2详细介绍

其他

Android6.0

  • USB 连接

默认情况下,现在通过 USB 端口进行的设备连接设置为仅充电模式。要通过 USB 连接访问设备及其内容,用户必须明确地为此类交互授予权限。如果您的应用支持用户通过 USB 端口与设备进行交互,请将必须显式启用交互考虑在内。

  • 浏览器书签变更

此版本移除了对全局书签的支持。android.provider.Browser.getAllBookmarks() 和 android.provider.Browser.saveBookmark() 方法现已移除。同样,READ_HISTORY_BOOKMARKS 权限和 WRITE_HISTORY_BOOKMARKS 权限也已移除。如果您的应用以 Android 6.0(API 级别 23)或更高版本为目标平台,请勿从全局提供程序访问书签或使用书签权限。您的应用应改为在内部存储书签数据。

  • 硬件标识符访问权

为给用户提供更严格的数据保护,从此版本开始,对于使用 WLAN API 和 Bluetooth API 的应用,Android 移除了对设备本地硬件标识符的编程访问权。WifiInfo.getMacAddress()方法和 BluetoothAdapter.getAddress()方法现在会返回常量值 02:00:00:00:00:00。
现在,要通过蓝牙和 WLAN 扫描访问附近外部设备的硬件标识符,您的应用必须拥有 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 权限。

:当运行 Android 6.0(API 级别 23)的设备发起后台 WLAN 或蓝牙扫描时,在外部设备看来,该操作的发起来源是一个随机化 MAC 地址。

  • WLAN 和网络连接变更

此版本对 WLAN API 和 Networking API 引入了以下行为变更。

在此版本中,相机服务中共享资源的访问模式已从之前的“先到先得”访问模式更改为高优先级进程优先的访问模式。对服务行为的变更包括:

  • 根据客户端应用进程的“优先级”授予对相机子系统资源的访问权,包括打开和配置相机设备。带有对用户可见 Activity 或前台 Activity 的应用进程一般会被授予较高的优先级,从而使相机资源的获取和使用更加可靠;

  • 当高优先级的应用尝试使用相机时,系统可能会“驱逐”正在使用相机客户端的低优先级应用。在已弃用的 Camera API 中,这会导致系统为被驱逐的客户端调用 [onError()](https://developer.android.google.cn/reference/android/hardware/Camera.ErrorCallback.html#onError(int, android.hardware.Camera))。在 Camera2 API 中,这会导致系统为被驱逐的客户端调用 onDisconnected()

  • 在配备相应相机硬件的设备上,不同的应用进程可同时独立打开和使用不同的相机设备。但现在,如果在多进程用例中同时访问相机会造成任何打开的相机设备的性能或能力严重下降,相机服务会检测到这种情况并禁止同时访问。即使并没有其他应用直接尝试访问同一相机设备,此变更也可能导致低优先级客户端被“驱逐”。

  • 更改当前用户会导致之前用户帐户拥有的应用内活动相机客户端被驱逐。对相机的访问仅限于访问当前设备用户拥有的用户个人资料。举例来说,这意味着,当用户切换到其他帐户后,“来宾”帐户实际上无法让使用相机子系统的进程保持运行状态。

  • 运行时

ART 运行时环境现在可正确实现 newInstance() 方法的访问规则。此变更修正了之前版本中 Dalvik 无法正确检查访问规则的问题。如果您的应用使用newInstance() 方法,并且您想重写访问检查,请调用 setAccessible() 方法(将输入参数设置为 true)。如果您的应用使用 v7 appcompat 库v7 recyclerview 库,则您必须更新应用以使用这些库的最新版本。否则,请务必更新从 XML 引用的任何自定义类,以便能够访问它们的类构造函数。此版本更新了动态链接程序的行为。动态链接程序现在可以识别库的 soname 与其路径之间的差异(公开错误 6670),并且现在已实现了按 soname 搜索。之前包含错误的 DT_NEEDED 条目(通常是开发计算机文件系统上的绝对路径)却仍工作正常的应用,如今可能会出现加载失败。现已正确实现 dlopen(3) RTLD_LOCAL 标记。请注意,RTLD_LOCAL 是默认值,因此不显式使用 RTLD_LOCAL 的 dlopen(3) 调用将受到影响(除非您的应用显式使用 RTLD_GLOBAL)。使用 RTLD_LOCAL 时,在随后通过调用 dlopen(3) 加载的库中并不能使用这些符号(这与由 DT_NEEDED 条目引用的情况截然不同)。

在之前版本的 Android 上,如果您的应用请求系统加载包含文本重定位信息的共享库,系统会显示警告,但仍允许加载共享库。从此版本开始,如果您的应用的目标 SDK 版本为 23 或更高,则系统会拒绝加载该库。为帮助您检测库是否加载失败,您的应用应该记录 dlopen(3) 失败日志,并在日志中加入dlerror(3) 调用返回的问题描述文本。要详细了解如何处理文本重定位,请参阅此指南

  • 低电耗模式和应用待机模式

此版本引入了针对空闲设备和应用的最新节能优化技术。这些功能会影响所有应用,因此请务必在这些新模式下测试您的应用。

  • 低电耗模式:如果用户拔下设备的电源插头,并在屏幕关闭后的一段时间内使其保持不活动状态,设备会进入低电耗模式,在该模式下设备会尝试让系统保持休眠状态。在该模式下,设备会定期短时间恢复正常工作,以便进行应用同步,还可让系统执行任何挂起的操作。
  • 应用待机模式:应用待机模式允许系统判定应用在用户未主动使用它时处于空闲状态。当用户有一段时间未触摸应用时,系统便会作出此判定。如果拔下了设备电源插头,系统会为其视为空闲的应用停用网络访问以及暂停同步和作业。

要详细了解这些节能变更,请参阅对低电耗模式和应用待机模式进行针对性优化

  • 文本选择

现在,当用户在您的应用中选择文本时,您可以在一个浮动工具栏中显示“剪切”“复制”“粘贴”等文本选择操作。其在用户交互实现上与为单个视图启用上下文操作模式中所述的上下文操作栏类似。
要实现可用于文本选择的浮动工具栏,请在您的现有应用中做出以下更改:

请注意,如果您使用 Android 支持库 22.2 修订版,浮动工具栏不向后兼容,默认情况下 appcompat 会获得对 ActionMode 对象的控制权。这会禁止显示浮动工具栏。要在ActionMode 中启用 AppCompatActivity 支持,请调用 getDelegate(),然后对返回的setHandleNativeActionModesEnabled() 对象调用 AppCompatDelegate,并将输入参数设置为 false。此调用会将 ActionMode 对象的控制权交还给框架。在运行 Android 6.0(API 级别 23)的设备上,框架可以支持 ActionBar 模式或浮动工具栏模式;而在运行 Android 5.1(API 级别 22)或之前版本的设备上,框架仅支持 ActionBar 模式。

  • Android for Work 变更

此版本包含下列针对 Android for Work 的行为变更:

Android7.0

  • 无障碍改进

为提高平台对于视力不佳或视力受损用户的易用性,Android 7.0 做出了一些更改。这些更改一般并不要求更改您的应用代码,不过您应仔细检查并使用您的应用测试这些功能,以评估它们对用户体验的潜在影响。

  • 电池和内存

Android 7.0 包括旨在延长设备电池寿命和减少 RAM 使用的系统行为变更。这些变更可能会影响您的应用访问系统资源,以及您的应用通过特定隐式 intent 与其他应用交互的方式。

  • 屏幕缩放

Android 7.0 支持用户设置显示尺寸,以放大或缩小屏幕上的所有元素,从而提升设备对视力不佳用户的可访问性。用户无法将屏幕缩放至低于最小屏幕宽度 sw320dp,该宽度是 Nexus 4 的宽度,也是常规中等大小手机的宽度。

正常效果

运行 Android 7.0 系统映像的设备增大显示尺寸后的效果。

当设备密度发生更改时,系统会以如下方式通知正在运行的应用:

  • 如果是面向 API 级别 23 或更低版本系统的应用,系统会自动终止其所有后台进程。这意味着如果用户切换离开此类应用,转而打开 Settings 屏幕并更改 Display size 设置,则系统会像处理内存不足的情况一样终止该应用。如果应用具有任何前台进程,则系统会如处理运行时更改中所述将配置变更通知给这些进程,就像对待设备屏幕方向变更一样。
  • 如果是面向 Android 7.0 的应用,则其所有进程(前台和后台)都会收到有关配置变更的通知,如处理运行时更改中所述。

大多数应用并不需要进行任何更改即可支持此功能,不过前提是这些应用遵循 Android 最佳做法。具体要检查的事项:

  • 在屏幕宽度为 sw320dp 的设备上测试您的应用,并确保其充分运行。
  • 当设备配置发生变更时,更新任何与密度相关的缓存信息,例如缓存位图或从网络加载的资源。当应用从暂停状态恢复运行时,检查配置变更。
    注:如果您要缓存与配置相关的数据,则最好也包括相关元数据,例如该数据对应的屏幕尺寸或像素密度。保存这些元数据便于您在配置变更后决定是否需要刷新缓存数据。
  • 避免用像素单位指定尺寸,因为像素不会随屏幕密度缩放。应改为使用与密度无关像素 (dp) 单位指定尺寸。
  • NDK 应用链接至平台库

从 Android 7.0 开始,系统将阻止应用动态链接非公开 NDK 库,这种库可能会导致您的应用崩溃。此行为变更旨在为跨平台更新和不同设备提供统一的应用体验。即使您的代码可能不会链接私有库,但您的应用中的第三方静态库可能会这么做。因此,所有开发者都应进行相应检查,确保他们的应用不会在运行 Android 7.0 的设备上崩溃。如果您的应用使用原生代码,则只能使用公开 NDK API

  • 低电耗模式

Android 6.0(API 级别 23)引入了低电耗模式,当用户设备未插接电源、处于静止状态且屏幕关闭时,该模式会推迟 CPU 和网络活动,从而延长电池寿命。而 Android 7.0 则通过在设备未插接电源且屏幕关闭状态下、但不一定要处于静止状态(例如用户外出时把手持式设备装在口袋里)时应用部分 CPU 和网络限制,进一步增强了低电耗模式。

图 1. 低电耗模式如何应用第一级系统活动限制以延长电池寿命的图示。

当设备处于充电状态且屏幕已关闭一定时间后,设备会进入低电耗模式并应用第一部分限制:关闭应用网络访问、推迟作业和同步。如果进入低电耗模式后设备处于静止状态达到一定时间,系统则会对PowerManager.WakeLock
AlarmManager 闹铃、GPS 和 WLAN 扫描应用余下的低电耗模式限制。无论是应用部分还是全部低电耗模式限制,系统都会唤醒设备以提供简短的维护时间窗口,在此窗口期间,应用程序可以访问网络并执行任何被推迟的作业/同步。
图 2. 低电耗模式如何在设备处于静止状态达到一定时间后应用第二级系统活动限制的图示。

请注意,激活屏幕或插接设备电源时,系统将退出低电耗模式并移除这些处理限制。此项新增的行为不会影响有关使您的应用适应 Android 6.0(API 级别 23)中所推出的旧版本低电耗模式的建议和最佳做法,如对低电耗模式和应用待机模式进行针对性优化中所讨论。您仍应遵循这些建议(例如使用 Google 云消息传递 (GCM) 发送和接收消息)并开始安排更新计划以适应新增的低电耗模式行为。

  • Project Svelte:后台优化

Android 7.0 移除了三项隐式广播,以帮助优化内存使用和电量消耗。此项变更很有必要,因为隐式广播会在后台频繁启动已注册侦听这些广播的应用。删除这些广播可以显著提升设备性能和用户体验。
移动设备会经历频繁的连接变更,例如在 WLAN 和移动数据之间切换时。目前,可以通过在应用清单中注册一个接收器来侦听隐式 CONNECTIVITY_ACTION
广播,让应用能够监控这些变更。由于很多应用会注册接收此广播,因此单次网络切换即会导致所有应用被唤醒并同时处理此广播。
同理,在之前版本的 Android 中,应用可以注册接收来自其他应用(例如相机)的隐式 ACTION_NEW_PICTUREACTION_NEW_VIDEO 广播。当用户使用相机应用拍摄照片时,这些应用即会被唤醒以处理广播。

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

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

如果您的应用使用任何 intent,您仍需要尽快移除它们的依赖关系,以正确适配 Android 7.0 设备。Android 框架提供多个解决方案来缓解对这些隐式广播的需求。例如,JobScheduler API 提供了一个稳健可靠的机制来安排满足指定条件(例如连入无限流量网络)时所执行的网络操作。您甚至可以使用 JobScheduler 来适应内容提供程序变化。如需了解有关 Android N 中后台优化以及如何改写应用的详细信息,请参阅后台优化

更多信息请猛戳下面的官方链接

Android 6.0 变更
Android 7.0 变更

转载请以链接形式标明出处:
http://www.jianshu.com/p/95790125b7f4
本文出自:103style
or
csdn
http://blog.csdn.net/lxk_1993/article/details/73784883
本文出自:lxk_1993

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容