weijunfeng
2/12/2019 - 9:34 AM

android6.0、7.0、8.0新特性总结之开发应用时加以考虑的一些主要变

野路子

android6.0、7.0、8.0新特性总结之开发应用时加以考虑的一些主要变更。
android6.0
参考一:简书Android 6.0 新特性详解  https://www.jianshu.com/p/30fb0c1c88ad

参考二:关于Android6.0以上系统的权限问题  http://blog.csdn.net/Administratortwd/article/details/74942015

参考三:值得你关注的Android6.0上的重要变化(一)  http://blog.csdn.net/w7849516230/article/details/49704077

参考四:值得你关注的Android6.0上的重要变化(二)  http://blog.csdn.net/w7849516230/article/details/50358907

参考五:Android从1.0到 6.0各版本的差别  http://blog.csdn.net/huang_rong12/article/details/51252186

参考六:Android历史版本 版本号对照 版本分布(截止到2017.11)http://blog.csdn.net/bunny1024/article/details/70800018

参考七:Android发展史(Android各版本特性-技术篇)(至android6.0) http://blog.csdn.net/u012964796/article/details/50664794

参考八: 了解Android已发布的各种版本(即1.0、2.0、3.0、4.0、5.0、6.0、7.0、8.0)
http://blog.csdn.net/zythemoon/article/details/78469562
参考九:Android中判断当前API的版本号http://blog.csdn.net/wangsf1112/article/details/51545101

参考十:关于Android6.0权限适配的问题http://blog.csdn.net/zhangxing52077/article/details/53523914

参考十一:Android 6.0权限机制及开发流程详解http://blog.csdn.net/ccpat/article/details/51151863

参考十二:Android6.0动态权限申请步骤以及需要注意的一些坑https://www.jianshu.com/p/a51593817825

参考十三:彻底解决Android6.0权限管理问题http://blog.csdn.net/lf_hycz/article/details/60886040

 

android7.0 
参考一:android 7.0新特性http://blog.csdn.net/jiabailong/article/details/52411300

参考二:Android7.0新特性,及Android N适配http://blog.csdn.net/wulianghuan/article/details/59112049

参考三:Android7.0适配教程与心得http://blog.csdn.net/heiya0409/article/details/52794458

参考四:Android7.0适配(含实例)http://blog.csdn.net/qq_35100676/article/details/70670172

参考五: Android 7.0 之拍照与图片裁剪适配http://blog.csdn.net/yyh352091626/article/details/54908624

参考六:Android 7.0的适配问题,android.os.FileUriExposedException

参考七:项目android 6.0,7.0 版本适配问题http://blog.csdn.net/u010567192/article/details/71642349

参考八:android app版本升级(DownloadManager、适配6.0、7.0)http://blog.csdn.net/u010296640/article/details/73496690

参考九:Android N(Android 7.0)版本适配(开发者应该知道的一切新特性与新功能)
http://blog.csdn.net/ourpush/article/details/52389893
参考十:Android7.0新特性介绍(一)——多窗口支持http://blog.csdn.net/r3lish/article/details/52874661

参考十一: Android7.0新特性介绍(二)——通知增强功能http://blog.csdn.net/r3lish/article/details/52882842

参考十二:android7.0对开发有影响的变动http://blog.csdn.net/u010499721/article/details/52870038

参考十三:Android7.0适配教程,心得https://www.jianshu.com/p/56b9fb319310

参考十四:Android 7.0http://www.android-doc.com/guide/components/android7.0.html

参考十五:Android 7.0 新特性详解https://www.jianshu.com/p/ed5ad6bf634e?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

 android8.0
参考一:开发者必看|Android 8.0 新特性及开发指南http://www.cnblogs.com/qyun/p/6715195.html

参考二:安卓8.0和7.0有什么区别 8.0系统新功能盘点http://android.tgbus.com/news/news/201707/562028.shtml

参考三:Android 8.0有什么新功能?看看这个就够了http://android.tgbus.com/faq/563308.shtml

参考四: Android O (8.0) 新特性介绍http://blog.csdn.net/qq_22393017/article/details/73293348

参考五:Android 8.0新特性(看这篇文章就够了)http://blog.csdn.net/lzllzllhl/article/details/76889957

参考六:Android 8.0 新特性有哪些?https://www.jianshu.com/p/42c87abafe3e

参考七: Android8.0运行时权限策略变化和适配方案http://blog.csdn.net/yanzhenjie1003/article/details/76719487

参考八:值得你关注的Android8.0(Android O)上的重要变化http://blog.csdn.net/w7849516230/article/details/68935953

参考九:Android 8.0 功能和 APIhttps://developer.android.google.cn/about/versions/oreo/android-8.0.html

参考十:公司项目Android8.0适配分析http://m.blog.csdn.net/xwh_1230/article/details/78437775

参考十一:Android 8.0 功能和 API(developer.android.google.cn)https://developer.android.google.cn/about/versions/oreo/android-8.0.html


--------------------- 
作者:TheScar 
来源:CSDN 
原文:https://blog.csdn.net/qq_38127722/article/details/81974403 
版权声明:本文为博主原创文章,转载请附上博文链接!
Android8.0无法接收隐式广播消息
2018年07月02日 16:56:22 chenrenxiang 阅读数:3899


App里有一个自己在Manifest文件里面注册的广播接收器(Receiver),到Android8.0的机器上调试的时候却接收不到消息,一番搜索和研究之后,才发现Android8.0已经对隐式广播做了限制。



版本区别
对于targetSdkVersion 在26或者以上的App,在Manifest里面注册的Receiver已经受到限制,而用Java代码动态注册的Receiver则不受影响。

targetSdkVersion 在 25或以下的App,其Receiver不受影响,即使在Android8.0以上的机器上运行。

如果targetSdkVersion 在26或者以上,在Manifest注册的Receiver可能无法接收到广播消息,并且会在Logcat里面打印出如下消息:

BroadcastQueue: Background execution not allowed: receiving Intent { act=com.xiaoqiang.try.something.receiver flg=0x2010 (has extras) } to xiaoqiang.com.trysomething/.broadcast.TheReceiver



有哪些影响
首先要了解一个概念。官方文档里提到implicit broadcast,可译为隐式广播,指那些没有指定接收App(即包名)的广播。 
- 系统发送的广播毫无疑问都是隐式广播,因此基本上都会受到影响,除了部分受豁免广播之外 
- App发送的自定义隐式广播,都会受到影响



为何限制隐式广播
总所周知,在Manifest里面注册的系统广播接收器会被缓存在系统中,即使当App关闭之后,如果有相应的广播发出,应用程序仍然会被唤醒。比如如果有20个App在Manifest里面注册了ACTION_BOOT_COMPLETED的广播接收器监听设备启动,那么当设备启动时,就会有20个应用程序被唤醒并作出相应的动作。而动态注册的广播则跟随组件的生命周期而消存。因此在Manifest里面注册广播接收器的App越多,设备的性能就越容易受到影响,限制隐式广播主要是为了优化系统性能。



如何应对这一限制
分析了受限制的原因之后,就知道该如何应对这一影响了。 
- 优先使用动态注册Receiver的方式,能动态注册绝不使用Manifest注册

    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("com.xiaoqiang.try.something.receiver");
    TheReceiver receiver = new TheReceiver();
    registerReceiver(receiver, intentFilter);
1
2
3
4
如果一定要Manifest注册,那么当发送广播的时候,指定广播接收者的包名,即发送显式广播
    Intent intent = new Intent("com.xiaoqiang.try.something.receiver");
    intent.putExtra("receive","test broadcast");
    intent.setPackage(getPackageName());
    //intent.setComponent(...)
    sendBroadcast(intent);
1
2
3
4
5
如果要接收系统广播,而对应的广播在Android8.0中无法被接收,那么只能暂时把App的targetSdkVersion改为25或以下。
Android8.0 静态receiver接收不到隐式广播
2018年09月28日 20:26:23 明朗晨光 阅读数:1717
 版权声明:本文为博主原创文章,未经博主允许不得转载。	https://blog.csdn.net/u011386173/article/details/82889275
Android8.0的新特性值得注意一下,不然会出现很多莫名的问题。。。

后台执行限制
Android 8.0 为提高电池续航时间而引入的变更之一是,当您的应用进入已缓存状态时,如果没有活动的组件,系统将解除应用具有的所有唤醒锁。

此外,为提高设备性能,系统会限制未在前台运行的应用的某些行为。具体而言:

现在,在后台运行的应用对后台服务的访问受到限制。
应用无法使用其清单注册大部分隐式广播(即,并非专门针对此应用的广播)。
默认情况下,这些限制仅适用于针对 O 的应用。不过,用户可以从 Settings 屏幕为任意应用启用这些限制,即使应用并不是以 O 为目标平台。

Android 8.0 还对特定函数做出了以下变更:

如果针对 Android 8.0 的应用尝试在不允许其创建后台服务的情况下使用 startService() 函数,则该函数将引发一个 IllegalStateException。
新的 Context.startForegroundService() 函数将启动一个前台服务。现在,即使应用在后台运行,系统也允许其调用 Context.startForegroundService()。不过,应用必须在创建服务后的五秒内调用该服务的 startForeground() 函数。
 

以下这个就是Android8.0的后台限制新特性导致的,果然要踩过坑,才印象深刻。

自己编写了一个demo跟踪广播的发送流程。

demo:注册一个静态receiver,然后发送广播。

Activity中发送广播的代码:

public class MainActivity extends Activity {
    public static final String TAG = "TEST";
    public final String TEST_ACTION = "com.maureen.test.TEST_ACTION";
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
 
    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"onResume");
        Log.d(TAG,">>sendBroadcast");
        Intent intent = new Intent(TEST_ACTION);
        sendBroadcast(intent);
        Log.d(TAG,"<<sendBroadcast");
    }
 
    private BroadcastReceiver mTestReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            Log.d(TAG,"Dynamic receiver:action="+action);
        }
    };
 
}
AndroidManifest.xml中注册静态receiver:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.atc6111.testswitchicon">
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
 
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
 
 
        <receiver android:name=".MyTestReceiver">
            <intent-filter>
                <action android:name="com.maureen.test.TEST_ACTION"/>
            </intent-filter>
        </receiver>
    </application>
 
</manifest>
Receiver对应的java文件:

public class MyTestReceiver extends BroadcastReceiver {
    private final String TAG = "TEST-MyTestReceiver";
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG,"Static receiver:action=" + intent.getAction());
    }
}
好的,现在开始运行这个demo。结果这个receiver一直都没有收到广播。。。。。

查看log,发现了warning信息:

W BroadcastQueue: Background execution not allowed: receiving Intent { act=com.maureen.test.TEST_ACTION flg=0x10 } to com.example.atc6111.testswitchicon/.MyTestReceiver
"Background execution not allowed" 打印出该warning的代码:



即是以下两种情况静态receiver不会接收到广播:

发送的intent设置了FLAG --FLAG_RECEIVER_EXCLUDE_BACKGROUND;
以下情况的均满足时:
         ①intent没有指定接收组件,也就是没有setComponent

         ②intent没有执行接收的package,也就是没有setPackage

        ③发送的intent没有设置FLAG-FLAG_RECEIVER_INCLUDE_BACKGROUND

        ④给定的权限并不都是签名权限。

根据这两种情况,即是说静态receiver接收不了隐式广播。本来打算采用最简单的方法添加Flag来解决的。

但是奇怪的是,Android Studio里没有FLAG_RECEIVER_INCLUDE_BACKGROUND!!!!

然后,只好在发送intent的时候setPackage。

   @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"onResume");
        Log.d(TAG,">>sendBroadcast");
        Intent intent = new Intent(TEST_ACTION);
        intent.setPackage(getPackageName());
        sendBroadcast(intent);
        Log.d(TAG,"<<sendBroadcast");
    }
修改之后,静态Receiver就能收到广播了。

但是如果就是想让静态receiver接收到隐式广播呢?应该还有其他方法,待学。。。。

解决Android 8.0及以上系统接收不到广播的问题
96  听风1413 
 0.5 2018.12.05 14:46* 字数 354 阅读 569评论 0喜欢 3
最近把自己的手机系统升级到了android 8.0,然后以前能正常运行的项目,莫名其妙的出了问题,有个地方广播接收不到了,然后分别在6.0和7.0的设备上运行了项目,发现一切正常,擦,估计是高版本系统的问题,又要适配,不过,在调试程序时,在打印出的日志里面发现了这句话:
Background execution not allowed: receiving Intent { act=izis_MinaPushServiceOpen_kyteach flg=0x10 (has extras) } to cn.izis.kyteach/.receiver.DataReceiverPublic
后来在网上查了 android8.0及以上系统关于广播的规定:发现果然是对隐式广播做了限定,如果targetSdkVersion >=26,在Manifest里面注册的Receiver可能无法接收到广播消息(我的广播接收者就是像这样静态注册的):

<receiver android:name=".receiver.DataReceiverPublic">
            <intent-filter>
                <action android:name="izis_MinaPushServiceOpen_kyteach" />
            </intent-filter>
        </receiver>
但是因为这个静态广播涉及的代码和逻辑比较多,所以完全改成动态注册的方式,还是有点繁琐的,后来查阅相关资料,还是有解决办法的,加上后面这段代码:

if(Build.VERSION.SDK_INT >= 26){
                       ComponentName componentName=new ComponentName(getApplicationContext(),"cn.izis.kyteach.receiver.DataReceiverPublic");//参数1-包名 参数2-广播接收者所在的路径名
                       myIntent.setComponent(componentName);
                   }
但是如果是在2个不同的包之间发送广播,这句话是没用的,而是需要加上后面这段代码:

if(Build.VERSION.SDK_INT >= 26){
myIntent.addFlags(0x01000000);//加上这句话,可以解决在android8.0系统以上2个module之间发送广播接收不到的问题}
哈哈,又可以正常的接收广播了!!
上面所说即:若App的TargetSDK达到了26, 我们正常静态注册的广播就没有用了。能用的仅有以下豁免的Broadcast, 包括我们自己正常发广播,如果不指定包名, 静态注册的也是收不到的。PS:动态注册是没有影响的

https://developer.android.com/guide/components/broadcast-exceptions

在我们收不到广播的时候,系统会有如下打印,即这个后台的广播接收器不会被执行

04-21 04:12:27.513 2431 4821 W BroadcastQueue: Background execution not allowed:******************

如何应对这一限制
知道了上面的限制后,我们正常的应对方式为

能动态注册,就不静态注册
如果一定要静态注册, 发送的时候指定包名,即发送显式广播
如果要接收系统广播,而对应的广播在Android8.0中无法被接收,那么只能暂时把App的targetSdkVersion改为25或以下,但这招已经不顶用了,工信部要求targetSDK必须26以上
如果我们不想发显式广播(因为我们不知道有谁要收广播),对方又不能动态注册,只能静态注册(许多应用希望是被动唤醒),我们应该怎么办呢?

我们看上面的异常:

04-21 04:12:27.513 2431 4821 W BroadcastQueue: Background execution not allowed:******************

这行log是哪边打印的呢?

我们去搜索一下:http://androidxref.com/

其代码在:frameworks/base/services/core/java/com/android/server/am/BroadcastQueue.java#1275

代码里面有个skip变量是用来标志是否跳过的,很显然1275行打印出来了,skip为true了那就, 我们不希望这个判断能够进去。
那么如合让判断不进去呢?看下面代码。

1267                    } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
1268                            || (r.intent.getComponent() == null
1269                                && r.intent.getPackage() == null
1270                                && ((r.intent.getFlags()
1271                                        & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
1272                                && !isSignaturePerm(r.requiredPermissions))) {
1273                        mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
1274                                component.getPackageName());
1275                        Slog.w(TAG, "Background execution not allowed: receiving "
1276                                + r.intent + " to "
1277                                + component.flattenToShortString());
1278                        skip = true;
1279                    }
有这么个判断r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND intent中携带了EXCLUDE_BACKGROUND标志位肯定进去,很显然我们正常都不带,只有希望后台收不到的时候才会带。
r.intent.getComponent() == null, 这个肯定不会为null的。为null是必须跳过
r.intent.getPackage() == null, 若包名为空,那肯定也跳过
r.intent.getFlags() & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0 不能带有FLAG_RECEIVER_INCLUDE_BACKGROUND这个标志位,若带了,那就进不去了,这不就是我们希望的结果么。
那么方案有了,携带 FLAG_RECEIVER_INCLUDE_BACKGROUND 这个标志位。我们发现在AS中使用Intent是找不到这个标志位的,应该是hide了,没有被编译进SDK。

看一下,果然,那么我们直接带硬编码即可。

/**
 * If set, the broadcast will always go to manifest receivers in background (cached
 * or not running) apps, regardless of whether that would be done by default.  By
 * default they will only receive broadcasts if the broadcast has specified an
 * explicit component or package name.
 *
 * NOTE: dumpstate uses this flag numerically, so when its value is changed
 * the broadcast code there must also be changed to match.
 *
 * @hide
 */
public static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000;
因此得出结论:发送广播的时候携带intent.addFlags(0x01000000); 即能让广播突破隐式广播限制。
Android 系统(80)---Android 8.0 7.0 6.0 动态权限管理
2018年06月03日 16:02:36 zhangbijun1230 阅读数:1079
Android 8.0 7.0 6.0 动态权限管理
1.Android6.0之后运行时权限策略变化
从Android6.0(API23)开始,对系统权限做了很大的改变,在之前用户安装app前,只是把app需要的使用的权限列出来告知用户一下,app安装后都可以访问这些权限。从6.0开始,一些敏感权限需要在使用是动态申请,并且用户可以选择拒绝授权访问这些权利,已授予过的权限,用户也可以去app设置界面去关闭授权。这对用户来说提高了安全性,可以防止一些应用恶意访问用户数据,但是对于开发来说,也增加了不少的工作量,这块不做适配处理的话,app在访问权限的时候容易出现crash。

2.权限等级
权限主要分为normal、dangerous、signature和signatureOrSystem四个等级,常规情况下我们只需要了解前两种,即正常权限和危险权限。
"normal"	The default value. A lower-risk permission that gives requesting applications access to isolated application-level features, with minimal risk to other applications, the system, or the user. The system automatically grants this type of permission to a requesting application at installation, without asking for the user's explicit approval (though the user always has the option to review these permissions before installing).
"dangerous"	A higher-risk permission that would give a requesting application access to private user data or control over the device that can negatively impact the user. Because this type of permission introduces potential risk, the system may not automatically grant it to the requesting application. For example, any dangerous permissions requested by an application may be displayed to the user and require confirmation before proceeding, or some other approach may be taken to avoid the user automatically allowing the use of such facilities.
"signature"	A permission that the system grants only if the requesting application is signed with the same certificate as the application that declared the permission. If the certificates match, the system automatically grants the permission without notifying the user or asking for the user's explicit approval.
"signatureOrSystem"	
Old synonym for "signature|privileged". Deprecated in API level 23.

A permission that the system grants only to applications that are in a dedicated folder on the Android system image or that are signed with the same certificate as the application that declared the permission. Avoid using this option, as the signature protection level should be sufficient for most needs and works regardless of exactly where apps are installed. The "signatureOrSystem" permission is used for certain special situations where multiple vendors have applications built into a system image and need to share specific features explicitly because they are being built together.
2.1、正常权限
正常权限涵盖应用需要访问其沙盒外部数据或资源,但对用户隐私或其他应用操作风险很小的区域。应用声明其需要正常权限,系统会自动授予该权限。例如设置时区,只要应用声明过权限,系统就直接授予应用此权限。

2.2、危险权限
危险权限涵盖应用需要涉及用户隐私信息的数据或资源,或者可能对用户存储的数据或其他应用的操作产生影响的区域。例如读取用户联系人,在6.0以上系统中,需要在运行时明确向用户申请权限。

危险权限

3、运行时请求权限
3.1、检查权限
应用每次需要危险权限时,都要判断应用目前是否有该权限。兼容库中已经做了封装,只需要通过下面代码即可:

int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,    Manifest.permission.WRITE_CALENDAR);
如果有权限则返回PackageManager.PERMISSION_GRANTED,否则返回PackageManager。PERMISSION_DENIED。

3.2、请求权限
当应用需要某个权限时,可以申请获取权限,这时会有弹出一个系统标准Dialog提示申请权限,此Diolog不能定制,用户同意或者拒绝后会通过方法onRequestPermissionsResult()返回结果。

 ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CODE);
3.3、处理权限请求响应
当用户处理权限请求后,系统会回调申请权限的Activity的onRequestPermissionsResult()方法,只需要覆盖此方法,就能获得返回结果.

 @Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
 
}
4.Android O的运行时权限策略变化
4.1在 Android O 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。

4.2对于针对Android O的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准,但是若没有请求相应的权限而进行操作的话就会出现应用crash的情况.

例如,假设某个应用在其清单中列出READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE。应用请求READ_EXTERNAL_STORAGE,并且用户授予了该权限,如果该应用针对的是API级别24或更低级别,系统还会同时授予WRITE_EXTERNAL_STORAGE,因为该权限也属于STORAGE权限组并且也在清单中注册过。如果该应用针对的是Android O,则系统此时仅会授予READ_EXTERNAL_STORAGE,不过在该应用以后申请WRITE_EXTERNAL_STORAGE权限时,系统会立即授予该权限,而不会提示用户。但是若没有申请WRITE_EXTERNAL_STORAGE权限,而去进行写存储卡的操作的时候,就会引起应用的崩溃。

4.3对Android O运行时权限策略变化的应对方案
针对Android O 的运行是的权限特点,我们可以在申请权限的时候要申请权限数组,而不是单一的某一个权限。所以按照上面的危险权限列表我们给系统权限进行分类,把一个组的常量放到数组中,并根据系统版本进行赋值。

/**
 * Created by Yang on 2017/9/20.
 * desc: 由于Android8.0的限制 最好的做法是申请权限的时候一组一组的申请
 */

public final class Permission {

    public static final String[] CALENDAR;
    public static final String[] CAMERA;
    public static final String[] CONTACTS;
    public static final String[] LOCATION;
    public static final String[] MICROPHONE;
    public static final String[] PHONE;
    public static final String[] SENSORS;
    public static final String[] SMS;
    public static final String[] STORAGE;

    static {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            CALENDAR = new String[]{};
            CAMERA = new String[]{};
            CONTACTS = new String[]{};
            LOCATION = new String[]{};
            MICROPHONE = new String[]{};
            PHONE = new String[]{};
            SENSORS = new String[]{};
            SMS = new String[]{};
            STORAGE = new String[]{};
        } else {
            CALENDAR = new String[]{
                    Manifest.permission.READ_CALENDAR,
                    Manifest.permission.WRITE_CALENDAR};

            CAMERA = new String[]{
                    Manifest.permission.CAMERA};

            CONTACTS = new String[]{
                    Manifest.permission.READ_CONTACTS,
                    Manifest.permission.WRITE_CONTACTS,
                    Manifest.permission.GET_ACCOUNTS};

            LOCATION = new String[]{
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_COARSE_LOCATION};

            MICROPHONE = new String[]{
                    Manifest.permission.RECORD_AUDIO};

            PHONE = new String[]{
                    Manifest.permission.READ_PHONE_STATE,
                    Manifest.permission.CALL_PHONE,
                    Manifest.permission.READ_CALL_LOG,
                    Manifest.permission.WRITE_CALL_LOG,
                    Manifest.permission.USE_SIP,
                    Manifest.permission.PROCESS_OUTGOING_CALLS};

            SENSORS = new String[]{
                    Manifest.permission.BODY_SENSORS};

            SMS = new String[]{
                    Manifest.permission.SEND_SMS,
                    Manifest.permission.RECEIVE_SMS,
                    Manifest.permission.READ_SMS,
                    Manifest.permission.RECEIVE_WAP_PUSH,
                    Manifest.permission.RECEIVE_MMS};

            STORAGE = new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE};
        }
    }

}
在Android M以前使用某权限是不需要用户授权的,只要在Manifest中注册即可,在Android M之后需要注册并申请用户授权,所以我们根据系统版本在Android M以前用一个空数组作为权限组,在Android M以后用真实数组权限。

5.接下来用我自己封装的一个权限框架给大家演示
首先添加依赖:
Step 1.Add it in your root build.gradle at the end of repositories:

	allprojects {
		repositories {
			...
			maven { url 'https://jitpack.io' }
		}
	}
Step 2. Add the dependency

	dependencies {
	        compile 'com.github.Andy-13:ZbPermission:1.0.0'
	}
 
然后就可以使用框架来动态请求响应的权限啦(可以一个一个权限申请,也可以一组一组权限申请)
 ZbPermission.with(MainActivity.this)
        .addRequestCode(REQUEST_CONTACT)
        .permissions(Manifest.permission.READ_CONTACTS, Manifest.permission.RECEIVE_SMS, Manifest.permission.WRITE_CONTACTS)
        .request(/*new ZbPermission.ZbPermissionCallback(){
	
            @Override
            public void permissionSuccess(int requestCode) {
	    
                Toast.makeText(MainActivity.this, "成功授予Contact权限: " + requestCode, Toast.LENGTH_SHORT).show();
		
            }
            @Override
            public void permissionFail(int requestCode) {
	    
                Toast.makeText(MainActivity.this, "成功授予Contact拍照权限: " + requestCode, Toast.LENGTH_SHORT).show();
		
            }
	    
        }*/);
注解方法(当参数没有接口的时候,就会在当前类里面寻找相应的注解方法):
 @ZbPermissionSuccess(requestCode = REQUEST_CONTACT)
public void permissionSuccessContact() {

    Toast.makeText(MainActivity.this, "成功授予Contact权限注解" , Toast.LENGTH_SHORT).show();
    
}

@ZbPermissionFail(requestCode = REQUEST_CONTACT)
public void permissionFailContact() {

    Toast.makeText(MainActivity.this, "授予Contact权限失败注解" , Toast.LENGTH_SHORT).show();
    
}
申请权限的系统回调方法:
 @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    ZbPermission.onRequestPermissionsResult(MainActivity.this, requestCode, permissions, grantResults);
    
}
6.完整的demo代码:
public class MainActivity extends AppCompatActivity{

    private static final String TAG = "MainActivity";
    private final int REQUEST_CONTACT = 50;
    private final int REQUEST_STORAGE = 100;
    private final int REQUEST_CAMERA = 200;
    private Button bt_request_storage;
    private Button bt_request_camera;
    private Button bt_request_contact;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        setListener();
    }

    private void initView() {
        bt_request_contact = (Button) findViewById(R.id.bt_request_contact);
        bt_request_camera = (Button) findViewById(R.id.bt_request_camera);
        bt_request_storage = (Button) findViewById(R.id.bt_request_storage);
    }

    private void setListener() {
        bt_request_contact.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
            //request()方法的参数可以有也可以没有,有且不为空,就会回调ZbPermissionCallback的响应的回调方法,没有或为空,则
            //回调响应的注解方法
                ZbPermission.with(MainActivity.this)
                        .addRequestCode(REQUEST_CONTACT)
                        .permissions(Manifest.permission.READ_CONTACTS, Manifest.permission.RECEIVE_SMS, Manifest.permission.WRITE_CONTACTS)
                        .request(/*new ZbPermission.ZbPermissionCallback() {
                            @Override
                            public void permissionSuccess(int requestCode) {
                                Toast.makeText(MainActivity.this, "成功授予Contact权限: " + requestCode, Toast.LENGTH_SHORT).show();
                            }

                            @Override
                            public void permissionFail(int requestCode) {
                                Toast.makeText(MainActivity.this, "成功授予Contact拍照权限: " + requestCode, Toast.LENGTH_SHORT).show();
                            }
                        }*/);
            }
        });

        bt_request_storage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
            // 没有callback作为参数 就会去调用响应的注解方法
                ZbPermission.needPermission(MainActivity.this, REQUEST_STORAGE, Permission.STORAGE);
            }
        });

        bt_request_camera.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
             // 有callback作为参数,若callback不为空,就会去调用响应的callback方法,否则就会去调用响应的注解方法
                ZbPermission.needPermission(MainActivity.this, REQUEST_CAMERA, Permission.CAMERA, new ZbPermission.ZbPermissionCallback() {
                    @Override
                    public void permissionSuccess(int requestCode) {
                        Toast.makeText(MainActivity.this, "成功授予拍照权限: " + requestCode, Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void permissionFail(int requestCode) {
                        Toast.makeText(MainActivity.this, "授予拍照权限失败: " + requestCode, Toast.LENGTH_SHORT).show();
                    }
                });
            }
        });
    }

    @ZbPermissionSuccess(requestCode = REQUEST_STORAGE)
    public void permissionSuccess() {
        Toast.makeText(MainActivity.this, "成功授予读写权限注解" , Toast.LENGTH_SHORT).show();
    }

    @ZbPermissionFail(requestCode = REQUEST_STORAGE)
    public void permissionFail() {
        Toast.makeText(MainActivity.this, "授予读写权限失败注解" , Toast.LENGTH_SHORT).show();
    }

    @ZbPermissionSuccess(requestCode = REQUEST_CONTACT)
    public void permissionSuccessContact() {
        Toast.makeText(MainActivity.this, "成功授予Contact权限注解" , Toast.LENGTH_SHORT).show();
    }

    @ZbPermissionFail(requestCode = REQUEST_CONTACT)
    public void permissionFailContact() {
        Toast.makeText(MainActivity.this, "授予Contact权限失败注解" , Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        ZbPermission.onRequestPermissionsResult(MainActivity.this, requestCode, permissions, grantResults);
    }
   
}
Android 11来了!

七彩蘑菇科技

发布时间:04-0910:07优质创作者
Android 11为我们带来了新的功能:将支持Android的“无缝更新”功能,该功能对于使用新操作系统启动的设备来说是必需的。在Android R(又称为Android 11)启动时需要虚拟A / B两个分区,这意味着无缝更新系统需要两个分区以减少由系统更新引起的停机时间,并提供一种在更新出现问题时可以恢复的机制。


以前Android系统更新时手机会启动进入恢复状态,并且可能会停留在“安装系统更新”屏幕上长达25分钟。这将导致大量的停机时间,在此期间您将无法运行任何APP,查看任何短信或接听任何电话。发生停机是因为更新系统文件时需要使系统分区脱机,但是无缝更新系统通使用两个系统分区来解决这个问题。


除了无缝更新以外我们盘点一下Android11有哪些重要更新:

5G网络的体验

5G为全球更多用户带来了持续更快的速度和更低的延迟。借助5G,你可以将Wi-Fi应用体验(例如流4K视频或加载更高分辨率的游戏资产)扩展到移动用户,或者您可以构建专为5G设计的新体验。在Android 11中,我们正在增强和更新现有的连接性API,以便您可以利用5G改进的速度。

动态计量API-使用此API,你可以检查连接是否未计量,如果是,则提供更高的分辨率或质量,从而可能使用更多数据。扩展了API的范围,使其包含蜂窝网络,以便您可以识别运营商在连接到运营商的5G网络时提供真正未计量数据的用户。
带宽估算器API-已更新了适用于5G的API,以使其更易于检查下行/上行带宽,而无需轮询网络或计算您自己的估算值。如果调制解调器不提供支持,我们将基于当前连接进行默认估算。


5G不仅可以带出家中,还可以让你与周围的世界(从朋友,家人到企业)进行无缝互动,从而增强“移动”体验。

对新屏幕的支持

设备制造商通过将令人兴奋的新外形尺寸和设备屏幕推向市场来不断创新。在平台中扩展了对这些功能的支持,并提供了API,可让你优化应用。

钻孔屏和瀑布屏幕 -应用程序可以使用现有的显示切口API管理针孔和瀑布屏幕。如果需要,可以使用新的API,让您的应用使用包括边缘在内的整个瀑布屏幕,并带有插图以帮助您管理边缘附近的互动。
通讯

与您的朋友和同事沟通是许多人在手机上所做的最重要的事情。在Android 11中,我们引入了有助于开发人员创建更深层对话体验的更改,其中一些将在开发版1中看到早期版本:

通知阴影中的“ 专用对话”部分 -用户可以在自己喜欢的应用中立即找到与人们的正在进行的对话。
气泡 -气泡是一种在手机上执行多任务处理时保持对话可见和可访问的方法。消息和聊天应用应在通知中使用Bubbles API在Android 11中启用此功能。
在通知回复中插入图片 -如果你的应用支持图片复制/粘贴,则现在可以让用户将资产直接插入到通知内联回复中,以实现更丰富的交流以及应用本身。作为DP1的一部分,你将看到Chrome中的图像复制支持以及通过Gboard剪贴板提供的图像粘贴支持。
实时,双边通信应用程序应使用共享/会话快捷方式API来提供Android将在整个电话中显示的People目标以及Bubble API,以允许用户在以其他功能使用设备时进行会话。

神经网络API 1.3

神经网络API(NNAPI)专为在Android设备上运行用于计算机学习的计算密集型操作而设计。在Android 11中,扩展可供开发人员使用的操作和控件。

隐私权与安全性

隐私一直是Android的核心,每年都添加了更多方法来保护用户安全并提高透明度和控制力。这些更改已受到用户的欢迎-例如,在Android 10中,添加了“正在使用应用程序”权限选项,以使用户可以更精细地控制其位置并限制后台位置访问。到目前为止,当获得“正在使用应用程序”选项时,大约有一半的用户选择了它。在Android 11中,通过新的权限选项,对作用域存储的更新等来继续关注用户隐私。


专注于提高每个Android版本的安全性标准-从通过每月安全性更新访问更多设备到在最新平台中构建更多保护。在Android 11中,Android的纵深防御策略扩展到了平台的更多区域,并为应用程序添加了新功能和API。

预览更新

整个Android 11发布周期中定期更新预览系统图像和SDK。此初始预览版本仅适用于开发人员,不适合日常使用或消费者使用。

下面是Android 11进度计划:


不知道小伙伴们对Android 11的即将到来是否期待呢?欢迎大家在下方评论起评论,喜欢我的小伙伴记得点关注哟——谢谢
Android Pie(9.0) 新特性和适配策略

Arthur_7724
1
2018.09.14 09:44:24
字数 3,056
阅读 15,573
Android Pie(9.0) New Features
内容:
刘海屏适配
通知功能的变更
隐私权变更
对使用非 SDK 接口的限制 和 适配策略
非Activity-Context启动Activity
Apache HTTP 客户端弃用,影响采用非标准 ClassLoader 的应用
前台服务
API 变更
Display Cutout Support
Android 9 支持最新的全面屏,其中包含为摄像头和扬声器预留空间的屏幕缺口。 通过 DisplayCutout 类可确定非功能区域的位置和形状,这些区域不应显示内容。 要确定这些屏幕缺口区域是否存在及其位置,使用 getDisplayCutout()函数。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    View decorView = getWindow().getDecorView();
    WindowInsets rootWindowInsets = decorView.getRootWindowInsets();
    if (rootWindowInsets != null) {
        DisplayCutout cutout = rootWindowInsets.getDisplayCutout();
        List<Rect> boundingRects = cutout.getBoundingRects();
        if (boundingRects != null && boundingRects.size() > 0) {
            String msg;
            for (Rect rect : boundingRects) {
                msg = s+"left-" + rect.left;
                Log.d(TAG, msg);
            }
         }
    }
}
用新的窗口布局属性 layoutInDisplayCutoutMode 为设备屏幕缺口周围的内容进行布局。 可以将此属性设为下列值之一:

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT

The window is allowed to extend into the DisplayCutout area, only if the DisplayCutout is fully contained within a system bar. Otherwise, the window is laid out such that it does not overlap with the DisplayCutout area.

LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES

The window is always allowed to extend into the DisplayCutout areas on the short edges of the screen. The window will never extend into a DisplayCutout area on the long edges of the screen.屏幕短边有cutout,会延伸过去;若cutout在长边,一定不会延伸过去。

In this mode, the window extends under cutouts on the short edge of the display in both portrait and landscape, regardless of whether the window is hiding the system bars

On the other hand, should the cutout be on the long edge of the display, a letterbox will be applied such that the window does not extend into the cutout on either long edge在长边有cutout的情况,会排出在外。

LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER

The window is never allowed to overlap with the DisplayCutout area.

This should be used with windows that transiently set View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION to avoid a relayout of the window when the respective flag is set or cleared.

WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
getWindow().setAttributes(lp);
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 权限组明确请求这些权限。 否则会发生 <mark>SecurityException</mark>。

限制访问电话号码
在未首先获得 READ_CALL_LOG 权限的情况下,除了应用的用例需要的其他权限之外,运行于 Android 9 上的应用无法读取电话号码或手机状态。

与来电和去电关联的电话号码可在手机状态广播(比如来电和去电的手机状态广播)中看到,并可通过 PhoneStateListener 类访问。 但是,如果没有 READ_CALL_LOG 权限,则 PHONE_STATE_CHANGED 广播和 PhoneStateListener <mark>提供的电话号码字段为空</mark>。

要从手机状态中读取电话号码,请根据您的用例更新应用以请求必要的权限:

要通过 PHONE_STATE Intent 操作读取电话号码,同时需要 READ_CALL_LOG 权限和 READ_PHONE_STATE 权限。
要从 onCallStateChanged() 中读取电话号码,只需要 READ_CALL_LOG 权限。 不需要 READ_PHONE_STATE 权限。
电话信息现在依赖设备位置设置
如果用户在运行 Android 9 的设备上<mark>停用设备定位</mark>,则以下函数不提供结果:

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 中,您的应用可以继续访问这些受限的接口;<mark>该平台通过 toast 和日志条目提醒您注意这些接口</mark>。 如果您的应用显示这样的 toast,则必须寻求受限接口之外的其他实现策略。 如果您认为没有可行的替代策略,您可以提交错误以请求重新考虑此限制。

SDK 接口的链接
对于非SDK 接口
浅灰名单:仍可以访问的非 SDK 函数/字段。
深灰名单:
对于目标 SDK 低于 API 级别 28 的应用,允许使用深灰名单接口。
对于目标 SDK 为 API 28 或更高级别的应用:行为与黑名单相同。
黑名单:受限,无论目标 SDK 如何
平台将提示接口并不存在。
例如,无论应用何时尝试使用接口,<mark>平台都会引发 NoSuchMethodError/NoSuchFieldException,</mark>即使应用想要了解某个特殊类别的字段/函数名单,平台也不会包含接口。
检测是否使用了非SDK接口
工具veridex
下载工具,阅读README.txt
打包一个应用 APK,建议使用 release 包,排除一些未使用到的单元测试类或者其他因素的影响,取消混淆,将 APK 放到工具目录下;
执行命令 ./appcompat.sh --dex-file=test.apk,在终端上会输出三个名单每个 API 的详细调用处
非 SDK API 的处理
适配的原则是优先黑名单和深灰名单,浅灰名单在官方未有替代 API 之前可以暂时不适配,在 Android P 上运行也不会有任何问题。

向google申请
在之前 DP 版本时开发者如果遇到了不得不使用的黑名单或者深灰名单 API,需要向 google 官方及时提出反馈 (反馈url)申请将其移动到浅灰名单中,但是目前正式版本已经发布,未得知该申请通道是否仍有效。
针对第三方库调用到了非 SDK API 接口,解决办法当然是直接查询相关资料或者联系库提供方,确认是否有适配 Android P 新版本的 SDK。还有需要提到的一点,就算更换适配完成的第三方 SDK 后,仍然可能会在同一地方扫描出非 SDK API 的调用,这是因为适配工程师只是在调用处加了一个 try-catch 保护逻辑,虽然这样也勉强叫做适配完成,但是还是强烈建议大家使用如下的适配方式:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// Android P or above
} else {
// below Android P
}
严格按照上面的适配方案,扫描工具就不会再扫描出此处的非 SDK API 调用,我们也无需每次都去确认所有非 SDK API 调用处都加了保护逻辑。
当然如果第三方库没有适配也没有近期适配的意向,目前有两种方法:第一种是屏蔽入口;第二种是反编译 SDK,在关键地方加上适配代码;

非Activity-Context启动Activity,现在强制执行 FLAG_ACTIVITY_NEW_TASK 要求
Apache HTTP 客户端弃用,影响采用非标准 ClassLoader 的应用
将 compileSdkVersion 升级到 28 之后,如果在项目中用到了 Apache HTTP client 的相关类,<mark>就会抛出找不到这些类的错误</mark>。这是因为官方已经<mark>在 Android P 的启动类加载器中将其移除</mark>,如果仍然需要使用 Apache HTTP client.

在 Manifest 文件中加入:
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
或者也可以直接将 Apache HTTP client 的相关类打包进 APK 中。
如果它们委托给 系统 ClassLoader,则应用在 Android 9 或更高版本上将失败并显示 NoClassDefFoundError,因为 系统 ClassLoader 不再识别这些类。 为防止将来出现类似问题,一般情况下,应用应通过 应用 ClassLoader加载类,而不是直接访问系统 ClassLoader
前台服务
针对 Android 9 或更高版本并使用前台服务的应用<mark>必须请求</mark> FOREGROUND_SERVICE 权限。 这是普通权限,因此,系统会自动为请求权限的应用授予此权限。

如果针对 Android 9 或更高版本的应用尝试创建一个前台服务且未请求 FOREGROUND_SERVICE,则系统会引发 SecurityException。