本知识点只是个人见解,具体知识及使用请查阅官网,以免被误导,同时大家可以对此文发表自己的见解。
阅读本文之前最好先看看官网的对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 如果不考虑配置屏幕方向的话,直接去掉方向的设置即可解决问题
android:screenOrientation="portrait" 去掉即可
注意:此处需要注意保存好页面数据,否则屏幕旋转会导致页面重新创建。
b 如果,去掉透明属性的话,就需要解决app启动白屏(黑屏)的问题,白屏或者黑屏是根据App使用的主题来显示的,下面会给出官方的解决方案
如果既要固定屏幕方向,又解决启动过慢导致的白(黑)屏问题,以下办法可以解决:
1.找到你设置透明的Activity,然后在他的theme中将android:windowIsTranslucent
改为false
eg:<item name="android:windowIsTranslucent">false</item>
2.再加入<item name="android:windowDisablePreview">true</item>
就搞定了。
注意:
主题属性是用来关闭启动应用程序时系统进程绘制的初始空白屏幕。但是,与未禁止预览窗口的应用程序相比,此方法可能会导致更长的启动时间。此外,它会强制用户在活动启动时等待而没有任何反馈,无法让用户知道应用程序是否正常工作。
Goodle针对以上启动白屏或者黑屏给出了解决方案:
我们建议您不要禁用预览窗口,而要遵循常见的 Material Design 模式。您可以使用活动的 windowBackground 主题属性为开始活动提供简单的自定义绘图。
例如,您可以创建一个新的可绘制文件,并从布局XML和应用程序清单文件中引用它,如下所示:
Layout XML file:
<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>
然后设置<item name="android:windowBackground">@drawable/your xml file</item>
然后在Manifest file声明activity的时候添加主题:
<activity ...android:theme="@style/AppTheme.Launcher" />
ok,大功告成!!
如果需要转回正常主题,转换回正常主题的最简单方法是在调用 setTheme(R.style.AppTheme) 之前调用 super.onCreate() 并 setContentView() :
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 之推送通知的变化 | 中文教学视频 javascript:void(0)
或者郭霖大佬的博客
javascript:void(0)
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裁剪掉的情况。
更详细的具体细节和步骤,看大佬博客 javascript:void(0)
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>
注意: 如果您的应用不可调整大小,您应该测试它在尽可能多的设备上的行为。检查所有控件是否可见。有些设备让用户强制应用程序进入全屏显示,这将调整它们的大小。参考自Google中文文档 https://developer.android.google.cn/guide/practices/screens-distribution
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) 消息。
- 接收广播,例如短信/彩信消息。
- 从通知执行
- 。
在很多情况下,您的应用都可以使用 JobScheduler
那么我们可以从前台应用角度入手,去保活和启动服务
例如,在后台的时候让前台服务或者应用去启动一个后台服务(此处按自己应用的逻辑去处理) 下面会给出具体的解决方案。
那么官网给我们的建议是什么呢?
同样,建议之前先了解下以下概念:
Android 8.0 引入了一种全新的方法,即Context.startForegroundService()
,以在前台启动新服务。在系统创建服务后,应用有五秒的时间来调用该服务的 startForeground()
注意: 如果应用在此时间限制内未调用 startForeground()
,则系统将停止服务并声明此应用为 ANR。
参照以上概念,官网的建议是:
- 如果处于后台时您的应用需要创建一个前台服务,请使用新的API
NotificationManager.startServiceInForeground()
- 方法,而不是创建一个后台服务,然后尝试将其推到前台。
- 如果服务容易被用户注意,请将其设为前台服务。 例如,播放音频的服务始终应为前台服务。您应该使用
NotificationManager.startServiceInForeground()
- ,而不是
- 发生网络事件时,请使用 FCM 选择性地唤醒您的应用,而不是在后台轮询。
- 在应用正常处于前台之前,请推迟后台工作。
5.1 广播限制
Android 8.0 的应用无法继续在其清单中为隐式广播注册广播接收器
那么这句话怎么理解呢? 也就是说隐式广播在清单中注册了,当你在后台的时候,广播发送出去了,你的app是无法响应这些广播的
那么需要解释""隐式广播" 是什么意思?
隐式广播是一种不专门针对该应用的广播。 例如,ACTION_PACKAGE_REPLACED
就是一种隐式广播,因为它将发送到注册的所有侦听器,让后者知道设备上的某些软件包已被替换。不过,ACTION_MY_PACKAGE_REPLACED
- 应用可以继续在它们的清单中注册显式广播。
- 应用可以在运行时使用
- 需要签名权限的广播不受此限制所限,因为这些广播只会发送到使用相同证书签名的应用,而不是发送到设备上的所有应用。
在许多情况下,之前注册隐式广播的应用使用 JobScheduler
这里解释下哪些广播可以被接收到(即不受8.0系统的限制):https://developer.android.google.cn/guide/components/broadcast-exceptions.html
那么也就是说,我们只要发送针对我们自己app的广播入手,或者是动态的去注册接收器还是可以解决这个问题的.
同样,官网给的建议方法是: 检查在您应用的清单中定义的广播接收器。 如果您的清单为显式广播声明了接收器,您必须予以替换。 可能的解决方法包括:
- 通过调用
- 使用计划作业检查条件是否会触发隐式广播。(测试!测试!测试!)
最后,需要注意点的是一定要兼容最新版本!一定要兼容最新版本!一定要兼容最新版本
最新的Android系统发布网站及咨询,可以经常逛逛csdn或者其他网站如:
Android Developers (https://developer.android.google.cn/) 中英文
Android Developers (http://android.xsoftlab.net/) 英文版(比较全)
http://www.androiddevtools.cn/gay_friends.html 综合资料