本知识点只是个人见解,具体知识及使用请查阅官网,以免被误导,同时大家可以对此文发表自己的见解。
阅读本文之前最好先看看官网的对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界面设为了透明,然后这个时候又设置了方向为垂直,从而导致了这个问题。
解决
a 如果不考虑配置屏幕方向的话,直接去掉方向的设置即可解决问题
注意:此处需要注意保存好页面数据,否则屏幕旋转会导致页面重新创建。
b 如果,去掉透明属性的话,就需要解决app启动白屏(黑屏)的问题,白屏或者黑屏是根据App使用的主题来显示的,下面会给出官方的解决方案
如果既要固定屏幕方向,又解决启动过慢导致的白(黑)屏问题,以下办法可以解决:
1.找到你设置透明的Activity,然后在他的theme中将android:windowIsTranslucent
改为false
eg:<item name="android:windowIsTranslucent">false</item>
2.再加入<item name="android:windowDisablePreview">true</item>
就搞定了。
windowDisablePreview
主题属性是用来关闭启动应用程序时系统进程绘制的初始空白屏幕。但是,与未禁止预览窗口的应用程序相比,此方法可能会导致更长的启动时间。此外,它会强制用户在活动启动时等待而没有任何反馈,无法让用户知道应用程序是否正常工作。
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque"> <!-- The background color, preferably the same as your normal theme --> <item android:drawable="@android:color/white"/> <!-- Your product logo - 144dp color version of your app icon --> <item> <bitmap android:src="@drawable/product_logo_144dp" android:gravity="center"/> </item> </layer-list>
public class MyMainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { // Make sure this is before calling super.onCreate setTheme(R.style.Theme_MyApp); super.onCreate(savedInstanceState); // ... } }
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) { //版本判断 boolean b = context.getPackageManager().canRequestPackageInstalls(); if (b) { toInstallApp(context, appFile); //有权限就直接安装 } else { sDefaultRationale = new DefaultRationale(); //申请权限的对话框说明 sPermissionSetting = new PermissionSetting(context);//申请权限失败,跳转到设置界面 //栈顶没有activity,无法请求权限 if (com.reds.didi.view.ActivityManager.getTopActivity() == null){ return false; } 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); } 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) { //此处涉及到的android7.0的适配,后面欧文给出了具体方案 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"); } if (context.getPackageManager().queryIntentActivities(intent, 0).size() > 0) { context.startActivity(intent); } }Android7.0的适配方案,详情参考:https://www.jianshu.com/p/56b9fb319310
这里直接给出7.0的适配方案
1 创建类FileProvider
import android.support.v4.content.FileProvider; public class UpdateFileProvider extends FileProvider { }2 在AndroidManifest文件中声明<provider>标签
<provider android:name="com.reds.didi.weight.version.UpdateFileProvider" android:authorities="${applicationId}.fileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/new_app_file_paths"/> </provider>
3 在res目录下创建xml文件,然后创建
<?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="external_files" path="." /> <root-path name="root_path" path="." /> </paths>
ok! 具体用法上面给出了哦!
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); }
至于为什么需要适配渠道,渠道是什么,如何区分渠道,新的系统通知栏如何交互,
请看官方视频及介绍
Android 8.0 Oreo 之推送通知的变化 | 中文教学视频 https://juejin.im/post/5a52dfe3f265da3e4e257762
或者郭霖大佬的博客
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操作系统级别上明确声明手机的最大横纵比。我们建议比例为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
的元素,如下所示:
<!-- 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
。否则,最大宽高比不起作用。
android:resizeableActivity 是设置在<application> 标签上面的,例如
<application
android:name=".DidiApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:resizeableActivity="true"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:uiOptions="splitActionBarWhenNarrow"
tools:replace="android:icon,android:theme,android:allowBackup"
tools:targetApi="n">
<!--android:screenOrientation="portrait"-->
<activity
android:name=".view.module.didi.activity.WellComeActivity"
android:launchMode="singleTop"
android:theme="@style/AppTheme.Splash"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
6 后台执行限制适配建议
后台适配这块,坑比较多,每个手机业务不同,策略不同,只能给出建议来,让自己去斟酌
这里先给出以前大佬给出的方案和相关知识点:
https://www.jianshu.com/p/06a1a434e057
http://www.52im.net/thread-1135-1-1.html (强烈推荐)
以下是8.0的关于service的新增的知识点:
6.1 后台服务限制
先来了解一些基本概念:
在后台中运行的服务会消耗设备资源,这可能降低用户体验。 为了缓解这一问题,系统对这些服务施加了一些限制。也就是说无法在后台去启动"后台服务"了.
这里解释下什么是后台服务,解决办法可以从以下概念入手:
系统可以区分 前台 和 后台 应用。 (用于服务限制目的的后台定义与内存管理使用的定义不同;一个应用按照内存管理的定义可能处于后台,但按照能够启动服务的定义又处于前台。)如果满足以下任意条件,应用将被视为处于前台:
- 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
- 具有前台服务。
另一个前台应用已关联到该应用(不管是通过绑定到其中一个服务,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的服务,那么该应用处于前台:
- IME
- 壁纸服务
- 通知侦听器
- 语音或文本服务
如果以上条件均不满足,应用将被视为处于后台。
处于前台时,应用可以自由创建和运行前台服务与后台服务。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用服务。在该时间窗结束后,应用将被视为处于 空闲 状态。 此时,系统将停止应用的后台服务,就像应用已经调用服务的“Service.stopSelf()
”方法。
在这些情况下,后台应用将被置于一个临时白名单中并持续数分钟。 位于白名单中时,应用可以无限制地启动服务,并且其后台服务也可以运行。
处理对用户可见的任务时,应用将被置于白名单中,例如:
处理一条高优先级 Firebase 云消息传递 (FCM) 消息。
接收广播,例如短信/彩信消息。
- 从通知执行
PendingIntent
。
在很多情况下,您的应用都可以使用 JobScheduler
作业替换后台服务。 例如,CoolPhotoApp 需要检查用户是否已经从朋友那里收到共享的照片,即使该应用未在前台运行。
同样,建议之前先了解下以下概念:
Android 8.0 引入了一种全新的方法,即 Context.startForegroundService()
,以在前台启动新服务。
在系统创建服务后,应用有五秒的时间来调用该服务的 startForeground()
方法以显示新服务的用户可见通知。
注意: 如果应用在此时间限制内未调用 startForeground()
,则系统将停止服务并声明此应用为 ANR。
参照以上概念,官网的建议是:
- 如果处于后台时您的应用需要创建一个前台服务,请使用新的API
NotificationManager.startServiceInForeground()
方法,而不是创建一个后台服务,然后尝试将其推到前台。
- 如果服务容易被用户注意,请将其设为前台服务。 例如,播放音频的服务始终应为前台服务。您应该使用
NotificationManager.startServiceInForeground()
,而不是startService()
创建服务。
发生网络事件时,请使用 FCM 选择性地唤醒您的应用,而不是在后台轮询。
在应用正常处于前台之前,请推迟后台工作。
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()
而不是在清单中声明接收器的方式在运行时创建接收器。使用计划作业检查条件是否会触发隐式广播。(测试!测试!测试!)
最后,需要注意点的是一定要兼容最新版本!一定要兼容最新版本!一定要兼容最新版本
最新的Android系统发布网站及咨询,可以经常逛逛csdn或者其他网站如: