本知识点只是个人见解,具体知识及使用请查阅官网,以免被误导,同时大家可以对此文发表自己的见解。
阅读本文之前最好先看看官网的对8.0系统的全面讲解
我们需要自己动手去查看新特性,以便用已知的知识来解决未知的问题
https://developer.android.google.cn/about/versions/oreo/android-8.0.html (此处是中文版的google开发者文档)
8.0新增和优化了很多功能,以上是部分截图
再来看看本文的适配内容包括:
1 启动页适配
2 android 8.0 手机版本更新,系统内新版本安装适配
3 渠道通知适配
4 图标适配
5 超长屏幕尺寸适配
6 后台执行限制适配建议(对后台服务和隐式广播的限制)
最后给出了一些获取最新andrid系统的网站
1 启动页适配
新版本刚刚发,显示就用用户反馈更新后无法启动,然后我就赶紧去找bug,发现了这个错误.
有一句是这样的Only fullscreen opaque activities can request orientation
,也就是说只有全屏不透明的activity才可以设置方向,既然知道问题所在就好办了。
原因
出现这样的问题,绝大多数都是因为我们为了提高用户体验,手动取消App启动白屏或者黑屏的时候,将Splash界面设为了透明,然后这个时候又设置了方向为垂直,从而导致了这个问题。
解决
如果不考虑配置屏幕方向的话,直接去掉方向的设置即可解决问题,注意好此处需要注意保存好页面数据,否则屏幕旋转会导致页面重新创建。
如果,去掉透明属性的话,就需要解决app启动白屏(黑屏)的问题,请参考博客 https://www.jianshu.com/p/c24058c3d385
如果既要固定屏幕方向,又要透明属性以下办法可以解决:
1.找到你设置透明的Activity,然后在他的theme中将android:windowIsTranslucent
改为false
eg:<item name="android:windowIsTranslucent">false</item>
2.再加入<item name="android:windowDisablePreview">true</item>
就搞定了。
2 8.0的新版本的安装适配
安装适配问题在8.0及以上的模拟器上面是需要的,不然无法安装设备。但是亲测,在小米的8.0部分机型和华为部分8.0机型即使没有适配也是可以安装的,安装前勾选下未知来源apk的风险即可。所以在新版本apk能够正常覆盖的情况下,就不必适配了,当然适配是个坑,用不用看大家自己了
解决:
1、在清单文件中增加请求安装权限
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
2、申请打开未知来源权限。
3、打开未知来源授权列表,开启权限。
权限申请部分用到的是AndPermission,具体使用方法参考wiki http://www.yanzhenjie.com/AndPermission/
public static boolean installApp(final Context context, final File appFile) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { //判断是否是8.0 boolean b = context.getPackageManager().canRequestPackageInstalls(); if (b) { toInstallApp(context, appFile); //获取到了权限则直接安装应用,否则先请求权限 } else { sDefaultRationale = new DefaultRationale(); //此控件用来向用户解释权限 http://www.yanzhenjie.com/AndPermission/ sPermissionSetting = new PermissionSetting(context); //此控件用来引导用户跳转到设置页面开启权限 AndPermission.with(com.reds.didi.view.ActivityManager.getTopActivity()) //此处用来正式申请权限 .permission(Manifest.permission.REQUEST_INSTALL_PACKAGES) .rationale(sDefaultRationale) .onGranted(new Action() { @Override public void onAction(List<String> permissions) { //权限申请成功的回调 ToastUtil.showToast("权限设置成功!"); toInstallApp(context, appFile); } }) .onDenied(new Action() { @Override public void onAction(@NonNull List<String> permissions) { //权限申请失败的回调 ToastUtil.showToast("权限设置失败!"); toInstallApp(context, appFile); if (AndPermission.hasAlwaysDeniedPermission(com.reds.didi.view.ActivityManager.getTopActivity(), permissions)) { sPermissionSetting.showSetting(permissions);//申请失败,引导用户去设置界面手动开启 } } }) .start(); } } else { toInstallApp(context, appFile);//8.0以下直接安装应用 } return true; } catch (Exception e) { e.printStackTrace(); } return false; }
安装应用的代码如下:
private static void toInstallApp(Context context, File appFile) { if (sPermissionSetting != null) { sPermissionSetting.release(); sPermissionSetting = null; } if (sDefaultRationale != null) { sDefaultRationale.release(); sDefaultRationale = null; } Intent intent = new Intent(Intent.ACTION_VIEW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //此处是7.0的适配方案,即需要设置FileProvider,具体可以参考这篇文章 https://www.jianshu.com/p/56b9fb319310 Uri fileUri = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileProvider", appFile); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setDataAndType(fileUri, "application/vnd.android.package-archive"); } else { intent.setDataAndType(Uri.fromFile(appFile), "application/vnd.android.package-archive");// 7.0以下则直接跳转至安装页面 } if (context.getPackageManager().queryIntentActivities(intent, 0).size() > 0) { context.startActivity(intent); } }
此处我是用的严振杰大佬的AndPermission,需要有几点说明:
1 对话框 DefaultRationale和PermissionStting里面的对话框最好自己定义,并在合适的时机释放内存。对话框最好自己写,对话框的字体颜色要设置,不然用户无法点击,因为找不到按钮
这里是文档wiki http://www.yanzhenjie.com/AndPermission/
例如我自己写的:
/** * Created by YanZhenjie on 2018/1/1. */ public final class PermissionSetting { private final Context mContext; private AlertDialog mQuickAlertDialog; public PermissionSetting(Context context) { this.mContext = context; } public void showSetting(final List<String> permissions) { if (ListUtil.isEmpty(permissions))return; //防止没有获取到权限,或者是重复获取权限的弹框 List<String> permissionNames = Permission.transformText(mContext, permissions); if (ListUtil.isEmpty(permissionNames))return; //防止没有获取到权限,或者是重复获取权限的弹框 String message = mContext.getString(R.string.message_permission_always_failed, TextUtils.join("\n", permissionNames)); final SettingService settingService = AndPermission.permissionSetting(mContext); // AlertDialog.newBuilder(mContext) // .setCancelable(false) // .setTitle(R.string.title_dialog) // .setMessage(message) // .setPositiveButton(R.string.setting, new DialogInterface.OnClickListener() { // @Override // public void onClick(DialogInterface dialog, int which) { // settingService.execute(); // } // }) // .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { // @Override // public void onClick(DialogInterface dialog, int which) { // settingService.cancel(); // } // }) // .show(); if (TextUtils.isEmpty(message))return; //防止没有获取到权限,或者是重复获取权限的tankua if (mQuickAlertDialog == null) { mQuickAlertDialog = new AlertDialog(mContext).builder(); } if (mQuickAlertDialog.isShowing()) { return; } mQuickAlertDialog // .setTitle(getString(R.string.title_dialog)) .setMsg(message) .setPositiveButton("去设置", new View.OnClickListener() { @Override public void onClick(View v) { settingService.execute(); } }).setNegativeButton("残忍拒绝", new View.OnClickListener() { @Override public void onClick(View v) { settingService.cancel(); release(); } }).show(); } public void release() { if (mQuickAlertDialog != null) { mQuickAlertDialog.dismiss(); mQuickAlertDialog = null; } } }
2 安装的逻辑涉及到7.0的适配,可以参考文章 https://www.jianshu.com/p/56b9fb319310
3 系统通知适配
Google这次对于8.0系统通知渠道的推广态度还是比较强硬的。
如下异常:
# main(1)
android.app.RemoteServiceException
Bad notification for startForeground: java.lang.RuntimeException: invalid channel for service notification: Notification(channel=null pri=0 contentView=null vibrate=null sound=null defaults=0x0 flags=0x40 color=0x00000000 vis=PRIVATE)
同时首先,如果你升级了appcompat库,那么所有使用appcompat库来构建通知的地方全部都会进行废弃方法提示,如下所示:
上图告诉我们,此方法已废弃,需要使用带有通知渠道的方法才行。
当然,Google也并没有完全做绝,即使方法标为了废弃,但还是可以正常使用的。可是如果你将项目中的targetSdkVersion指定到了26或者更高,那么Android系统就会认为你的App已经做好了8.0系统的适配工作,当然包括了通知栏的适配。这个时候如果还不使用通知渠道的话,那么你的App的通知将完全无法弹出。因此这里给大家的建议就是,一定要适配。如果未适配成功,请留言给我!
先来看看我自己写的
/** * 创建通知 */ private void setUpNotification() { if (mDismissNotificationProgress) { return; } mBuilder = new NotificationCompat.Builder(this,"newversion"); mBuilder.setContentTitle("开始下载") .setContentText("正在连接服务器") .setSmallIcon(R.mipmap.lib_update_app_update_icon) .setLargeIcon(AppUpdateUtils.drawableToBitmap(AppUpdateUtils.getAppIcon(DownloadService.this))) .setOngoing(true) .setAutoCancel(true) .setWhen(System.currentTimeMillis()); //8.0系统适配 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { mNotificationManager.createNotificationChannel(getNotificationChannel()); //在通知显示前调用 } mNotificationManager.notify(NOTIFY_ID, mBuilder.build()); }
@RequiresApi(api = Build.VERSION_CODES.O) public NotificationChannel getNotificationChannel(){ //设置quda String channelId = "newversion"; String channelName = "升级夜游团新版本"; int importance = NotificationManager.IMPORTANCE_DEFAULT; return new NotificationChannel(channelId, channelName, importance); }
具体适配步骤:
1 创建通知渠道号
notificationManager.createNotificationChannel(channel);
channel里面有三个参数,这三个参数会让用户看到,所以不同功能配置不同.此渠道配置必须在显示通知之前调用,而且只会走一次
2 显示通知
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); Notification notification = new NotificationCompat.Builder(this, "newversion") .setContentTitle("发现新版本") .setContentText("版本1.0.9 特性:xxxxxxx") .setWhen(System.currentTimeMillis()) .setSmallIcon(R.drawable.icon) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon)) .setAutoCancel(true) .build(); manager.notify(1, notification); }
以上就配置完成.
至于为什么需要适配渠道,渠道是什么,如何区分渠道,新的系统通知栏如何交互,请看郭霖大佬的博客
https://blog.csdn.net/guolin_blog/article/details/79854070
4 图标适配
如果你将targetSdkVersion指定到了26,但是却没有进行Android 8.0系统的应用图标适配,那么会出现什么样的效果呢?这里我举几个反面示例:
在androidStudio中,按下Windows:Ctrl+Shift+A / Mac:command+shft+A 快捷键,并输入Image Asset,如下所示:
点击回车键打开Asset Studio编辑器,在这里就可以进行应用图标适配了。
这个Asset Studio编辑器非常简单好用,一学就会。左边是操作区域,右边是预览区域。
先来看操作区域,第一行的Icon Type保持默认就可以了,表示同时创建兼容8.0系统以及老版本系统的应用图标。第二行的Name用于指定应用图标的名称,这里也保持默认即可。接下来的三个页签,Foreground Layer用于编辑前景层,Background Layer用于编辑背景层,Legacy用于编辑老版本系统的图标。
再来看预览区域,这个就十分简单了,用于预览应用图标的最终效果。在预览区域中给出了可能生成的图标形状,包括圆形、圆角矩形、方形等等。注意每个预览图标中都有一个圆圈,这个圆圈叫作安全区域,必须要保证图标的前景层完全处于安全区域当中才行,否则可能会出现图标被手机厂商的mask裁剪掉的情况。
更详细的具体细节和步骤,看大佬博客 https://blog.csdn.net/guolin_blog/article/details/79417483
5 超长屏幕尺寸适配
系统根据Android操作系统级别控制完成操作的方式:
- 如果您的应用目标为Android 8.0(API级别26)或更高,则根据其布局填充整个屏幕。
- 如果您的应用目标为Android 7.1(API等级25)或更低,则系统会将应用界面的大小限制为16:9(大约1.86)的窗口。如果应用程序在屏幕宽高比较大的设备上运行,则该应用程序会显示在16:9的信箱中,从而导致部分屏幕未使用。
如果您的应用布局无法适应任意大的纵横比,您可以通过设置最大纵横比在所有Android操作系统级别上明确实施letterboxing。我们建议比例为2.4(12:5)。
要为Android 8.0(API级别26)和更高级别设置最大宽高比,请 android:MaxAspectRatio
在您的<activity>
代码中使用最大比率 。以下示例显示如何声明2.4的最大宽高比:
<!-- Render on full screen up to screen aspect ratio of 2.4 --><!-- Use a letterbox on screens larger than 2.4 --><activity android:maxAspectRatio="2.4"> ...</activity>
对于Android 7.1及更低版本,添加 <meta-data>
元素中指定android.max_aspect
的元素,如下所示:<application>
<!-- Render on full screen up to screen aspect ratio of 2.4 -->
<!-- Use a letterbox on screens larger than 2.4 -->
<meta-data android:name="android.max_aspect" android:value="2.4" />
如果您设置了最大宽高比,请不要忘记也设置 android:resizeableActivity false
。否则,最大宽高比不起作用。
6 后台执行限制适配建议
后台适配这块,坑比较多,每个手机业务不同,策略不同,只能给出建议来,让自己去斟酌
5.1 后台服务限制
先来了解一些基本概念:
在后台中运行的服务会消耗设备资源,这可能降低用户体验。 为了缓解这一问题,系统对这些服务施加了一些限制。也就是说无法在后台去启动""后台服务""了.
解决办法可以从以下概念入手:
系统可以区分 前台 和 后台 应用。 (用于服务限制目的的后台定义与内存管理使用的定义不同;一个应用按照内存管理的定义可能处于后台,但按照能够启动服务的定义又处于前台。)如果满足以下任意条件,应用将被视为处于前台:
- 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
- 具有前台服务。
另一个前台应用已关联到该应用(不管是通过绑定到其中一个服务,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的服务,那么该应用处于前台:
- IME
- 壁纸服务
- 通知侦听器
- 语音或文本服务
如果以上条件均不满足,应用将被视为处于后台。
绑定服务不受影响
这些规则不会对绑定服务产生任何影响。 如果您的应用定义了绑定服务,则不管应用是否处于前台,其他组件都可以绑定到该服务。
处于前台时,应用可以自由创建和运行前台服务与后台服务。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用服务。
在该时间窗结束后,应用将被视为处于 空闲 状态。 此时,系统将停止应用的后台服务,就像应用已经调用服务的“Service.stopSelf()
”方法。
在这些情况下,后台应用将被置于一个临时白名单中并持续数分钟。 位于白名单中时,应用可以无限制地启动服务,并且其后台服务也可以运行。
处理对用户可见的任务时,应用将被置于白名单中,例如:
处理一条高优先级 Firebase 云消息传递 (FCM) 消息。
接收广播,例如短信/彩信消息。
- 从通知执行
PendingIntent
。
在很多情况下,您的应用都可以使用 JobScheduler
作业替换后台服务。 例如,CoolPhotoApp 需要检查用户是否已经从朋友那里收到共享的照片,即使该应用未在前台运行。
同样,建议之前先了解下以下概念:
Android 8.0 引入了一种全新的方法,即 Context.startForegroundService()
,以在前台启动新服务。
在系统创建服务后,应用有五秒的时间来调用该服务的 startForeground()
方法以显示新服务的用户可见通知。
注意: 如果应用在此时间限制内未调用 startForeground()
,则系统将停止服务并声明此应用为 ANR。
参照以上概念,官网的建议是:
- 如果处于后台时您的应用需要创建一个前台服务,请使用新的
NotificationManager.startServiceInForeground()
方法,而不是创建一个后台服务,然后尝试将其推到前台。
- 如果服务容易被用户注意,请将其设为前台服务。 例如,播放音频的服务始终应为前台服务。
使用 NotificationManager.startServiceInForeground()
而不是 startService()
创建服务。 * 寻找一种使用计划作业实现服务功能的方式。 如果服务未在执行容易立即被用户注意到的操作,一般情况下,您都能够使用计划作业。
发生网络事件时,请使用 FCM 选择性地唤醒您的应用,而不是在后台轮询。
在应用正常处于前台之前,请推迟后台工作。
可以参考: https://blog.csdn.net/code_shen/article/details/78422358?locationNum=9&fps=1(仅仅参考)
5.1 广播限制
Android 8.0 的应用无法继续在其清单中为隐式广播注册广播接收器
那么这句话怎么理解呢? 也就是说隐式广播在清单中注册了,当你在后台的时候,广播发送出去了,你的app是无法响应这些广播的
那么需要解释""隐式广播" 是什么意思?
隐式广播是一种不专门针对该应用的广播。 例如,ACTION_PACKAGE_REPLACED
就是一种隐式广播,因为它将发送到注册的所有侦听器,让后者知道设备上的某些软件包已被替换。
不过,ACTION_MY_PACKAGE_REPLACED
不是隐式广播,因为不管已为该广播注册侦听器的其他应用有多少,它都会只发送到软件包已被替换的应用。
- 应用可以继续在它们的清单中注册显式广播。
应用可以在运行时使用
Context.registerReceiver()
为任意广播(不管是隐式还是显式)注册接收器。需要签名权限的广播不受此限制所限,因为这些广播只会发送到使用相同证书签名的应用,而不是发送到设备上的所有应用。
在许多情况下,之前注册隐式广播的应用使用 JobScheduler
作业可以获得类似的功能。
这里解释下哪些广播可以被接收到(即不受8.0系统的限制):https://developer.android.google.cn/guide/components/broadcast-exceptions.html
那么也就是说,我们只要发送针对我们自己app的广播入手,或者是动态的去注册接收器还是可以解决这个问题的.
同样,官网给的建议方法是: 检查在您应用的清单中定义的广播接收器。 如果您的清单为显式广播声明了接收器,您必须予以替换。 可能的解决方法包括:
通过调用
Context.registerReceiver()
而不是在清单中声明接收器的方式在运行时创建接收器。使用计划作业检查条件是否会触发隐式广播。(测试!测试!测试!)
可以参考: https://www.jianshu.com/p/5a885f26efb9(仅仅参考)
https://www.jianshu.com/p/ca3d87a4cdf3(广播的基本概念讲解)
最后,需要注意点的是一定要兼容最新版本!一定要兼容最新版本!一定要兼容最新版本
最新的Android系统发布网站及咨询,可以经常逛逛csdn或者其他网站如: