Android-8.0适配

本知识点只是个人见解,具体知识及使用请查阅官网,以免被误导,同时大家可以对此文发表自己的见解。

阅读本文之前最好先看看官网的对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否则,最大宽高比不起作用。

注意: 如果您的应用不可调整大小,您应该测试它在尽可能多的设备上的行为。检查所有控件是否可见。有些设备让用户强制应用程序进入全屏显示,这将调整它们的大小。参考自Google中文文档  https://developer.android.google.cn/guide/practices/screens-distribution


6 后台执行限制适配建议

后台适配这块,坑比较多,每个手机业务不同,策略不同,只能给出建议来,让自己去斟酌

5.1 后台服务限制

先来了解一些基本概念:

在后台中运行的服务会消耗设备资源,这可能降低用户体验。 为了缓解这一问题,系统对这些服务施加了一些限制。也就是说无法在后台去启动""后台服务""了.


解决办法可以从以下概念入手:

系统可以区分 前台 和 后台 应用。 (用于服务限制目的的后台定义与内存管理使用的定义不同;一个应用按照内存管理的定义可能处于后台,但按照能够启动服务的定义又处于前台。)如果满足以下任意条件,应用将被视为处于前台:

  • 具有可见 Activity(不管该 Activity 已启动还是已暂停)。
  • 具有前台服务。
  • 另一个前台应用已关联到该应用(不管是通过绑定到其中一个服务,还是通过使用其中一个内容提供程序)。 例如,如果另一个应用绑定到该应用的服务,那么该应用处于前台:

    • IME
    • 壁纸服务
    • 通知侦听器
    • 语音或文本服务

如果以上条件均不满足,应用将被视为处于后台。

处于前台时,应用可以自由创建和运行前台服务与后台服务。 进入后台时,在一个持续数分钟的时间窗内,应用仍可以创建和使用服务。

在该时间窗结束后,应用将被视为处于 空闲 状态。 此时,系统将停止应用的后台服务,就像应用已经调用服务的“Service.stopSelf()”方法。

在这些情况下,后台应用将被置于一个临时白名单中并持续数分钟。 位于白名单中时,应用可以无限制地启动服务,并且其后台服务也可以运行。

处理对用户可见的任务时,应用将被置于白名单中,例如:

在很多情况下,您的应用都可以使用 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或者其他网站如:


猜你喜欢

转载自blog.csdn.net/yang1349day/article/details/80016607