##Android 9 Pie重要功能
1、Android 9 Pie 是 Android 的一次重大更新,首先 Pie 带来了一个全新的 Android 仪表板,可以让用户知道其在设备上花费的时间,这与 Apple 的屏幕时间功能类似。应用程序屏幕被重新设计,支持 iPhone X 等带有刘海的显示器设备。Pie 也使用了一种新的基于手势的系统界面,具有类似 iPhone 的滑动功能,可以在操作系统中导航。名为 Shush 的勿扰功能在手机屏幕朝下放置时会使 Android 设备静音,而 Wind Down 功能可让手机界面变为灰色,以减少 Android 用户在特定睡眠时间使用手机的可能性。
2、Android 9 Pie 还带来 AI 驱动的 Adaptive Battery,可通过优先处理用户最有可能使用的应用程序来优化电池电量,利用机器学习技术对系统资源进行有效分配,更专注于用户最常用的应用。如果用户已经针对低耗电模式、应用待机模式以及后台限制对您的应用进行过相关优化,那么它就应该已经能够和动态电量管理特性完美配合。
3、与其它版本的 Android 一样,Android 9 Pie 仅限于部分手机升级使用,每个手机厂商都需要经过定制才能对用户推出相应的版本更新。目前的情况是 Android Pie 已经面向 Pixel 手机推出,接下来几个月内将更新包括 Sony Mobile、Xiaomi、Oppo、Vivo、OnePlus 和 Essential 等厂商的最新设备。此外,适用于 Pixel 设备的系统映像现可供下载,支持手动刷机。
4、请前往 Android开源项目库 资源库中的 Android 9 板块,获取更多 Android 9 的相关资源。
##简要说明Android 9 Pie重要功能
1、手势:Android Pie中加入全新的全面屏操作手势,与iPhone X的操作类似,但屏幕底部依然有导航键,不过只剩返回主菜单和后退。
2、Android Dashboard:这是一种查看APP使用时间的软件,这一点与iOS 12的屏幕使用时间功能类似。
3、Wind Down:在夜间使用手机时能将界面调整为灰色,也就是夜间模式。
4、Adaptive Battery(自适应电池用量):对用户常用的软件进行优化,控制CPU资源占用。
5、Shush勿扰功能:开启该功能后,当手机屏幕朝下,设备可以自动静音。
6、Actions和Slices:这两项功能是为了节省用户操作,比如Actions,检测到手机插入耳机后,会自动跳出音乐相关的App,方便用户选择;而Slices则能在App里节省用户操作,比如搜索打车软件,会自动跳选出叫车回家、目的地等。这类似于Smartisan OS的“一步”、MIUI的“传送门”,主要目的是为了简化用户对手机的操作。
7、界面调整:通知栏、顶部图标等一些功能也有着改变。
##Channel settings, broadcasts, and Do Not Disturb
Android 8.0 引入了通知渠道,允许您为要显示的每种通知类型创建可由用户自定义的渠道。 Android 9 通过下列变更简化通知渠道设置:
屏蔽渠道组:现在,用户可以针对某个应用在通知设置中屏蔽整个渠道组。 您可以使用 isBlocked()函数确定何时屏蔽一个渠道组,从而不会向该组中的渠道发送任何通知。
此外,您的应用可以使用全新的 getNotificationChannelGroup()函数查询当前渠道组设置。
全新的广播 Intent 类型:现在,当通知渠道和渠道组的屏蔽状态发生变更时,Android 系统将发送广播 Intent。 拥有已屏蔽的渠道或渠道组的应用可以侦听这些 Intent 并做出相应的回应。 有关这些 Intent 操作和 extra 的更多信息,请参阅 NotificationManager 参考中更新的常量列表。 有关响应广播 Intent 的信息,请参阅广播。
NotificationManager.Policy 有 3 种新的“请勿打扰”优先级类别:
PRIORITY_CATEGORY_ALARMS优先处理警报。
PRIORITY_CATEGORY_MEDIA优先处理媒体源的声音,如媒体和语音导航。
PRIORITY_CATEGORY_SYSTEM优先处理系统声音。
NotificationManager.Policy 还有 7 种新的“请勿打扰”常量,可以用来抑制视觉中断:
SUPPRESSED_EFFECT_FULL_SCREEN_INTENT防止通知启动全屏 Activity。
SUPPRESSED_EFFECT_LIGHTS屏蔽通知灯。
SUPPRESSED_EFFECT_PEEK防止通知短暂进入视图(“滑出”)。
SUPPRESSED_EFFECT_STATUS_BAR防止通知显示在支持状态栏的设备的状态栏中。
SUPPRESSED_EFFECT_BADGE在支持标志的设备上屏蔽标志。 如需了解详细信息,请参阅修改通知标志。
SUPPRESSED_EFFECT_AMBIENT在支持微光显示的设备上屏蔽通知。
SUPPRESSED_EFFECT_NOTIFICATION_LIST防止通知显示在支持列表视图(如通知栏或锁屏)的设备的列表视图中。
##隐私权变更-现在收的原来越紧,安卓也越来越规范
为了增强用户隐私,Android 9 引入了若干行为变更,如限制后台应用访问设备传感器、限制通过 Wi-Fi 扫描检索到的信息,以及与通话、手机状态和 Wi-Fi 扫描相关的新权限规则和权限组。
无论采用哪一种目标 SDK 版本,这些变更都会影响运行于 Android 9 上的所有应用。
##后台对传感器的访问受限
Android 9 限制后台应用访问用户输入和传感器数据的能力。 如果您的应用在运行 Android 9 设备的后台运行,系统将对您的应用采取以下限制:
您的应用不能访问麦克风或摄像头。
使用连续报告模式的传感器(例如加速度计和陀螺仪)不会接收事件。
使用变化或一次性报告模式的传感器不会接收事件。
如果您的应用需要在运行 Android 9 的设备上检测传感器事件,请使用前台服务。
##限制访问通话记录
Android 9 引入 CALL_LOG 权限组并将 READ_CALL_LOG、WRITE_CALL_LOG和 PROCESS_OUTGOING_CALLS权限移入该组。 在之前的 Android 版本中,这些权限位于 PHONE 权限组。
如果应用需要访问通话记录或者需要处理去电,则您必须向 CALL_LOG权限组明确请求这些权限。 否则会发生 SecurityException。
##限制访问电话号码
在未首先获得 READ_CALL_LOG 权限的情况下,除了应用的用例需要的其他权限之外,运行于 Android 9 上的应用无法读取电话号码或手机状态。
与来电和去电关联的电话号码可在手机状态广播(比如来电和去电的手机状态广播)中看到,并可通过 PhoneStateListener 类访问。 但是,如果没有 READ_CALL_LOG 权限,则 PHONE_STATE_CHANGED 广播和 PhoneStateListener 提供的电话号码字段为空。
要从手机状态中读取电话号码,请根据您的用例更新应用以请求必要的权限:
要通过 PHONE_STATE Intent 操作读取电话号码,同时需要 READ_CALL_LOG 权限和 READ_PHONE_STATE 权限。
要从 onCallStateChanged() 中读取电话号码,只需要 READ_CALL_LOG 权限。 不需要 READ_PHONE_STATE 权限。
##电话信息现在依赖设备位置设置
如果用户在运行 Android 9 的设备上停用设备定位,则以下函数不提供结果:
TelephonyManager.getAllCellInfo()
TelephonyManager.listen()
TelephonyManager.getCellLocation()
TelephonyManager.getNeighboringCellInfo()
##Build.SERIAL 始终设置为 “UNKNOWN” 以保护用户的隐私。
如果您的应用需要访问设备的硬件序列号,您应改为请求 READ_PHONE_STATE权限,然后调用 getSerial()。
##多进程 webview 信息访问限制
在 Android P 中为了提升系统的安全性,用户无法在多进程的 webview 中共享数据目录,该目录下存储的是一些 cookies、Http 缓存和其他一些永久、临时的缓存。当下不少应用会把 webview 放在另一个进程中打开以避免内存泄漏,但是他们 cookies 的设置往往还是在主进程中,所以开发者需要仔细排查自己的应用是否有这么使用,webview 相关运行是否正常等。
##对使用非 SDK 接口的限制
为帮助确保应用稳定性和兼容性,此平台对某些非 SDK 函数和字段的使用进行了限制;无论您是直接访问这些函数和字段,还是通过反射或 JNI 访问,这些限制均适用。 在 Android 9 中,您的应用可以继续访问这些受限的接口;该平台通过 toast和日志条目提醒您注意这些接口。 如果您的应用显示这样的 toast,则必须寻求受限接口之外的其他实现策略。 如果您认为没有可行的替代策略,您可以提交错误以请求重新考虑此限制。
##对于非SDK 接口
浅灰名单:仍可以访问的非 SDK 函数/字段。
深灰名单:
对于目标 SDK 低于 API 级别 28 的应用,允许使用深灰名单接口。
对于目标 SDK 为 API 28 或更高级别的应用:行为与黑名单相同。
黑名单:受限,无论目标 SDK 如何
平台将提示接口并不存在。
例如,无论应用何时尝试使用接口,平台都会引发 NoSuchMethodError/NoSuchFieldException,即使应用想要了解某个特殊类别的字段/函数名单,平台也不会包含接口。
##检测是否使用了非SDK接口
工具veridex
下载工具,阅读README.txt
打包一个应用 APK,建议使用 release 包,排除一些未使用到的单元测试类或者其他因素的影响,取消混淆,将 APK 放到工具目录下;
执行命令 ./appcompat.sh --dex-file=test.apk,在终端上会输出三个名单每个 API 的详细调用处
##P版本三方适配挖孔屏方案
注意,以下接口都是要Build.VERSION.SDK_INT >= 28才能调用到。
1、 新增挖孔屏挖孔尺寸和位置接口
class WindowInsets {
DisplayCutout getDisplayCutout();
}
class DisplayCutout {
int getSafeInsetLeft();
int getSafeInsetTop();
int getSafeInsetRight();
int getSafeInsetBottom();
Region getBounds();
}
2、新窗口布局模式,允许应用程序请求是否在挖孔区域布局:
class WindowManager.LayoutParams {
int layoutInDisplayCutoutMode;
final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
}
layoutInDisplayCutoutMode值说明:
a)LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:默认情况下,全屏窗口不会使用到挖孔区域,非全屏窗口可正常使用挖孔区域。(
模式在全屏显示下跟LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER一样。
当刘海区域完全在系统的状态栏时,LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT的显示效果与LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES一致。)
b)LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS(LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES):
窗口声明使用挖孔区域(模式会让屏幕到延申刘海区域中)
c)LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:窗口声明不使用挖孔区域(模式不会让屏幕到延申刘海区域中,会留出一片黑色区域。)
参数使用示例:
public class NotchActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//去掉标题
requestWindowFeature(Window.FEATURE_NO_TITLE);
//开局就一张背景图
setContentView(R.layout.notch);
//全屏显示
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
WindowManager.LayoutParams lp = getWindow().getAttributes();
//1
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
//2
// lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
//3
// lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
getWindow().setAttributes(lp);
}
}
- Android P提供提供的刘海屏适配方案
a.对于有状态栏的页面,不会受到刘海屏特性的影响,因为刘海屏包含在状态栏中了;
b.全屏显示的页面,系统刘海屏方案会对应用界面做下移处理,避开刘海区显示,这时会看到刘海区域变成一条黑边,完全看不到刘海了;
c.已经适配Android P应用的全屏页面可以通过谷歌提供的适配方案使用刘海区,真正做到全屏显示。
目前Android支持了三类凹口屏幕类型:边角显示屏凹口(斜刘海)、双显示屏凹口(刘海+胡子)、长型显示屏凹口(刘海
Google提供刘海规格<=系统状态栏
双显示屏凹口接口测试代码:
public class NotchActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//开局就一张背景图
setContentView(R.layout.notch);
getNotchParams();
}
@TargetApi(28)
public void getNotchParams() {
final View decorView = getWindow().getDecorView();
decorView.post(new Runnable() {
@Override
public void run() {
WindowInsets rootWindowInsets = decorView.getRootWindowInsets();
if (rootWindowInsets == null) {
Log.e("TAG", "rootWindowInsets为空了");
return;
}
DisplayCutout displayCutout = rootWindowInsets.getDisplayCutout();
Log.e("TAG", "安全区域距离屏幕左边的距离 SafeInsetLeft:" + displayCutout.getSafeInsetLeft());
Log.e("TAG", "安全区域距离屏幕右部的距离 SafeInsetRight:" + displayCutout.getSafeInsetRight());
Log.e("TAG", "安全区域距离屏幕顶部的距离 SafeInsetTop:" + displayCutout.getSafeInsetTop());
Log.e("TAG", "安全区域距离屏幕底部的距离 SafeInsetBottom:" + displayCutout.getSafeInsetBottom());
List<Rect> rects = displayCutout.getBoundingRects();
if (rects == null || rects.size() == 0) {
Log.e("TAG", "不是刘海屏");
} else {
Log.e("TAG", "刘海屏数量:" + rects.size());
for (Rect rect : rects) {
Log.e("TAG", "刘海屏区域:" + rect);
}
}
}
});
}
}
输出结果:
E/TAG: 安全区域距离屏幕左边的距离 SafeInsetLeft:0
E/TAG: 安全区域距离屏幕右部的距离 SafeInsetRight:0
E/TAG: 安全区域距离屏幕顶部的距离 SafeInsetTop:112
E/TAG: 安全区域距离屏幕底部的距离 SafeInsetBottom:112
E/TAG: 刘海屏数量:2
E/TAG: 刘海屏区域:Rect(468, 0 - 972, 112)
E/TAG: 刘海屏区域:Rect(468, 2448 - 972, 2560)
##上面是Android P才有的解决方案,在P之前呢,上面的代码通通都没用。然而我们伟大的国产厂商在Android P之前(基本都是Android O)就用上了高档大气上档次的刘海屏,所以,这也造就了各大厂商在Android P之前的解决方案百花齐放。下面,我们来看下主流厂商:华为、vivo、OPPO、小米等所提供的方案。
##华为
a. 使用刘海区显示
使用新增的meta-data属性android.notch_support。
在应用的AndroidManifest.xml中增加meta-data属性,此属性不仅可以针对Application生效,也可以对Activity配置生效。
如下所示:
<meta-data android:name="android.notch_support" android:value="true"/>
对Application生效,意味着该应用的所有页面,系统都不会做竖屏场景的特殊下移或者是横屏场景的右移特殊处理。
对Activity生效,意味着可以针对单个页面进行刘海屏适配,设置了该属性的Activity系统将不会做特殊处理。
实际上还有一种代码实现的方式,不过代码比较多,这里就不贴了,有兴趣的话可以在文末的链接中点进去看看。
b. 是否有刘海屏
通过以下代码即可知道华为手机上是否有刘海屏了,true为有刘海,false则没有。
public static boolean hasNotchAtHuawei(Context context) {
boolean ret = false;
try {
ClassLoader classLoader = context.getClassLoader();
Class HwNotchSizeUtil = classLoader.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
ret = (boolean) get.invoke(HwNotchSizeUtil);
} catch (ClassNotFoundException e) {
Log.e("Notch", "hasNotchAtHuawei ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("Notch", "hasNotchAtHuawei NoSuchMethodException");
} catch (Exception e) {
Log.e("Notch", "hasNotchAtHuawei Exception");
} finally {
return ret;
}
}
c. 刘海尺寸
华为提供了接口获取刘海的尺寸,如下:
//获取刘海尺寸:width、height
//int[0]值为刘海宽度 int[1]值为刘海高度
public static int[] getNotchSizeAtHuawei(Context context) {
int[] ret = new int[]{0, 0};
try {
ClassLoader cl = context.getClassLoader();
Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
Method get = HwNotchSizeUtil.getMethod("getNotchSize");
ret = (int[]) get.invoke(HwNotchSizeUtil);
} catch (ClassNotFoundException e) {
Log.e("Notch", "getNotchSizeAtHuawei ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("Notch", "getNotchSizeAtHuawei NoSuchMethodException");
} catch (Exception e) {
Log.e("Notch", "getNotchSizeAtHuawei Exception");
} finally {
return ret;
}
}
vivo
vivo在设置–显示与亮度–第三方应用显示比例中可以切换是否全屏显示还是安全区域显示。
a. 是否有刘海屏
public static final int VIVO_NOTCH = 0x00000020;//是否有刘海
public static final int VIVO_FILLET = 0x00000008;//是否有圆角
public static boolean hasNotchAtVivo(Context context) {
boolean ret = false;
try {
ClassLoader classLoader = context.getClassLoader();
Class FtFeature = classLoader.loadClass("android.util.FtFeature");
Method method = FtFeature.getMethod("isFeatureSupport", int.class);
ret = (boolean) method.invoke(FtFeature, VIVO_NOTCH);
} catch (ClassNotFoundException e) {
Log.e("Notch", "hasNotchAtVivo ClassNotFoundException");
} catch (NoSuchMethodException e) {
Log.e("Notch", "hasNotchAtVivo NoSuchMethodException");
} catch (Exception e) {
Log.e("Notch", "hasNotchAtVivo Exception");
} finally {
return ret;
}
}
b. 刘海尺寸
vivo不提供接口获取刘海尺寸,目前vivo的刘海宽为100dp,高为27dp。
##oppo
OPPO目前在设置 – 显示 – 应用全屏显示 – 凹形区域显示控制,里面有关闭凹形区域开关。
a. 是否有刘海屏
public static boolean hasNotchAtOPPO(Context context) {
return context.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
}
b. 刘海尺寸
OPPO不提供接口获取刘海尺寸,目前其有刘海屏的机型尺寸规格都是统一的。不排除以后机型会有变化。
其显示屏宽度为1080px,高度为2280px。刘海区域则都是宽度为324px, 高度为80px。
##小米
a.是否有刘海屏
系统增加了 property ro.miui.notch,值为1时则是 Notch 屏手机。
手头上没有小米8的手机,暂时没法验证,这里就不贴代码了,免得误导大家。后面测试过再放出来。
b. 刘海尺寸
小米的状态栏高度会略高于刘海屏的高度,因此可以通过获取状态栏的高度来间接避开刘海屏,获取状态栏的高度代码如下:
public static int getStatusBarHeight(Context context) {
int statusBarHeight = 0;
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
}
return statusBarHeight;
}
其他手机也可以通过这个方法来间接避开刘海屏,但是有可能有些手机的刘海屏高度会高于状态栏的高度,所以这个方法获取到的结果并不一定安全。