Android 6.0到9.0新特性

一. Android6.0(M)
Android系统6.x 的权限分为危险权限(不涉及危险性信息泄露)和普通权限(涉及用户私人信息),危险权限需要动态添加授权申请,不仅仅在清单文件中添加申请。危险权限是分组(9组)的,当组内的一个权限被授予可以执行则其他权限皆可执行。
1.1. 列举权限的分组

<!-- 危险权限 start -->
<!--PHONE-->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.ADD_VOICEMAIL"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<!--CALENDAR-->
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<!--CAMERA-->
<uses-permission android:name="android.permission.CAMERA"/>
<!--CONTACTS-->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!--LOCATION-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--MICROPHONE-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--SENSORS-->
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<!--SMS-->
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<!--STORAGE-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 危险权限 Permissions end -->

1.2. 示例
1.2.1.清单文件(AndroidManifest)注册

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />

1.2.2.逻辑代码

public class PermActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_perm);
        getPersimmionInfo();
    }
    //**************授权信息
    private void getPersimmionInfo() {
        if (Build.VERSION.SDK_INT >= 23) {
            //1. 检测是否添加权限   PERMISSION_GRANTED  表示已经授权并可以使用
            if ((ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) ||
                    (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)) {
                //手机为Android6.0的版本,权限未授权去i请授权
                //2. 申请请求授权权限
                //1. Activity
                // 2. 申请的权限名称
                // 3. 申请权限的 请求码
                ActivityCompat.requestPermissions(this, new String[]
                                {Manifest.permission.READ_EXTERNAL_STORAGE,//读写Sd卡
                                        Manifest.permission.CAMERA//照相机
                                },//读写内存卡
                        666);//
            } else {//手机为Android6.0的版本,权限已授权可以使用
                Toast.makeText(this, "手机为Android6.0的版本,权限已授权可以使用", Toast.LENGTH_SHORT).show();
            }

        } else {//手机为Android6.0以前的版本,可以使用
            Toast.makeText(this, "手机为Android6.0以前的版本可以使用", Toast.LENGTH_SHORT).show();
        }
    }
    //  重写方法    当权限申请后     执行 接收结果的作用
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // 先判断请求码 与 请求时的  是否一致
        if (requestCode == 666) {
            //  判断请求结果长度     且  结果 为  允许访问  则 进行使用;第一次授权成功后
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED
                    && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
                // 可以使用
                Toast.makeText(this, "已经授权读写SD卡和照相机权限,可以使用", Toast.LENGTH_SHORT).show();
            } else {//未授权不可以使用
                // 读写内存卡权限授权将不能使用以下功能。
                Toast.makeText(this, "未授权不可以使用", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

1.3.3. Fragment中onRequestPermissionsResult失效
(1)移到Fragment所依附的Activity
有的时候我们可能在fragment中去检查权限、申请权限,自然就认为回调也就写在fragment中了,而且写了之后也没有报任何错。这也就是为什么回调方法不执行的原因,所以我们把这个回调移到这个Fragment所依附的那个Activity,就可以顺利执行啦。(注意,我这里说的Fragment是v4包下的)
(2)在Fragment中申请运行时权限的时候是使用Fragment本身来进行申请
在Activity中使用的时候我们的代码如下:

ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
        1);

我们在Fragment中申请时不是使用AcyivityCompat而是使用Fragment本身,代码如下:

HomeFragment.this.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
        1);

二. Android7.0(N)
(1)APP应用程序的私有文件不再向使用者放宽
(2)Intent组件传递file://URI的方式可能给接收器留下无法访问的路径,触发FileUriExposedException异常,推荐使用FileProvider
(3)DownloadManager不再按文件名分享私人存储的文件。旧版应用在访问COLUMN_LOCAL_FILENAME时
2.1. FileProvider
2.1.1.问题
Caused by: android.os.FileUriExposedException:
file:///storage/emulated/0/20170601-030254.png
exposed beyond app through ClipData.Item.getUri()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1932)
at android.net.Uri.checkFileUriExposed(Uri.java:2348)
2.1.2.原因
在官方7.0的以上的系统中,尝试传递 file://URI可能会触发FileUriExposedException。
2.1.3.解决方案
FileProvider实际上是ContentProvider的一个子类,它的作用也比较明显了,file:///Uri不给用,那么换个Uri为content://来替代
(1)在资源文件新建xml目录,新建文件
main/res/xml/ rc_file_path

    <?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="" />
<files-path name="files" path="" />
<cache-path name="cache" path="" />
<external-path name="external" path="" />
<external-files-path name="name" path="path" />
<external-cache-path name="name" path="path" />
</paths>

(2)清单文件配置

<provider android:name="android.support.v4.content.FileProvider"
              android:authorities="com.fsh.lfmf.FileProvider"
              android:exported="false"
              android:grantUriPermissions="true">
        <meta-data
              android:name="android.support.FILE_PROVIDER_PATHS"
              android:resource="@xml/rc_file_path" />
    </provider>
    ```
(3)
在paths节点内部支持以下几个子节点,分别为:
<root-path/> 代表设备的根目录new File("/");
<files-path/> 代表context.getFilesDir()
<cache-path/> 代表context.getCacheDir()
<external-path/> 代表Environment.getExternalStorageDirectory()
<external-files-path>代表context.getExternalFilesDirs()
<external-cache-path>代表getExternalCacheDirs()
每个节点都支持两个属性:
name
path
path即为代表目录下的子目录,比如:
<external-path
        name="external"
        path="pics" />
代表的目录即为:Environment.getExternalStorageDirectory()/pics,其他同理。
当这么声明以后,代码可以使用你所声明的当前文件夹以及其子文件夹。
刚才我们说了,现在要使用content://uri替代file://uri,那么,content://的uri如何定义呢?总不能使用文件路径吧,那不是骗自己么~
所以,需要一个虚拟的路径对文件路径进行映射,所以需要编写个xml文件,通过path以及xml节点确定可访问的目录,通过name属性来映射真实的文件路径。
/**
 * 拍照
 */
  public void camera(int photoType) {
     // 判断存储卡是否可以用,可用进行存储
     if (hasSdcard()) {
        File dir = new File(IMAGE_DIR);
        if (!dir.exists()) {
            dir.mkdir();
        }
        tempFile = new File(dir,
                System.currentTimeMillis() + "_" + PHOTO_FILE_NAME);
        //从文件中创建uri
        Uri uri = null;
        //判断是否是Android7.0以及更高的版本
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".FileProvider", tempFile);
        } else {
            uri = Uri.fromFile(tempFile);
        }
        Intent intent = new Intent();
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.addCategory(intent.CATEGORY_DEFAULT);
        // 开启一个带有返回值的Activity,请求码为PHOTO_P_CAREMA
        startActivityForResult(intent, photoType);
    } else {
        ToastUtil.showShort(this, "未找到存储卡,无法拍照!");
    }
}
2.1.3.用途场景
(1)拍照
(2)版本更新,安置app。

2.1. 使用FileProvider兼容安装apk
2.1.1.Android7.0以前安装apk
```java
public void installApk(View view) {
      File file = new File(Environment.getExternalStorageDirectory(),       "testandroid7-debug.apk");
      Intent intent = new Intent(Intent.ACTION_VIEW);
      intent.setDataAndType(Uri.fromFile(file),
            "application/vnd.android.package-archive");
      startActivity(intent);
  }

拿个7.0的原生手机跑一下,android.os.FileUriExposedException又来了~~
android.os.FileUriExposedException: file:///storage/emulated/0/testandroid7-debug.apk exposed beyond app through Intent.getData()
2.1.2.Android7.0以后安装apk

protected void installApk(File file) {
     Intent intent = new Intent();
     // 执行动作
     intent.setAction(Intent.ACTION_VIEW);
     //从文件中创建uri
     Uri uri = null;
     //判断是否是Android7.0以及更高的版本
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//7.0
        uri = FileProvider.getUriForFile(getActivity(),   BuildConfig.APPLICATION_ID + ".FileProvider", file);
        Intent install = new Intent(Intent.ACTION_VIEW);
        install.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);//添加这一句表示对目标应用临时授权该Uri所代表的文件
        install.setDataAndType(uri, "application/vnd.android.package-archive");
        startActivity(install);
    } else {
        uri = Uri.fromFile(file);
        // 执行的数据类型
        intent.setDataAndType(uri, "application/vnd.android.package-archive");
        startActivity(intent);
    }
}

三. Android8.0(O)
3.1.1. 通知适配

   public void notification() {
        notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        //判断是否是8.0Android.O
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel chan1 = new NotificationChannel(NotificationConstant.PUSH_CHANNEL_ID,
                    NotificationConstant.PUSH_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
            chan1.setLightColor(Color.GREEN);
            chan1.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
            notificationManager.createNotificationChannel(chan1);
        }
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationConstant.PUSH_CHANNEL_ID);
//        Intent notificationIntent = new Intent(this, SecActivity.class);
//        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
//        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        Intent notificationIntent = new Intent(this, NotificationBroadcastReceiver.class);
        notificationIntent.setAction("notification_clicked");
        notificationIntent.putExtra(NotificationBroadcastReceiver.TYPE2, 111222);
        notificationIntent.putExtra(NotificationBroadcastReceiver.TYPE, 11);
        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, notificationIntent, 0);
        RemoteViews rv = new RemoteViews(getPackageName(), R.layout.notification_layout);
        //修改自定义View中的图片(两种方法)
        //rv.setImageViewResource(R.id.iv,R.mipmap.ic_launcher);
        rv.setImageViewBitmap(R.id.iv_icon, BitmapFactory.decodeResource(getResources(), R.drawable.add_round_green));
        rv.setImageViewBitmap(R.id.iv_content_icon, BitmapFactory.decodeResource(getResources(), R.drawable.add_round_green));
        rv.setTextViewText(R.id.tv_title, "泡沫");//修改自定义View中的歌名
        rv.setTextViewText(R.id.tv_content, "内容");//修改自定义View中的歌名
        builder.setContentTitle("通知标题")//设置通知栏标题
                .setContentIntent(pendingIntent) //设置通知栏点击意图
                .setNumber(++pushNum)
                .setTicker("通知内容") //通知首次出现在通知栏,带上升动画效果的
                .setWhen(System.currentTimeMillis())//通知产生的时间,会在通知信息里显示,一般是系统获取到的时间
                .setSmallIcon(R.mipmap.ic_launcher)//设置通知小ICON
                .setChannelId(NotificationConstant.PUSH_CHANNEL_ID)//通知渠道id
                .setContent(rv)//自定义通知栏布局
                .setAutoCancel(true)//可以点击通知栏的删除按钮删除
                .setDefaults(Notification.DEFAULT_ALL);//通知默认的声音 震动 呼吸灯
        Notification notification = builder.build();
        notification.flags |= Notification.FLAG_AUTO_CANCEL;
        if (notificationManager != null) {
            notificationManager.notify(NotificationConstant.PUSH_NOTIFICATION_ID, notification);
        }
    }
    ```
3.1.2.安装apk
Android 8.0去除了“允许未知来源”选项,所以如果我们的App有安装App的功能(检查更新之类的),那么会无法正常安装。
首先在AndroidManifest文件中添加安装未知来源应用的权限:
<!-- 如果是安卓8.0,应用编译配置的targetSdkVersion>=26,请务必添加以下权限 -->
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
这样系统会自动询问用户完成授权。当然你也可以先使用 canRequestPackageInstalls()查询是否有此权限,如果没有的话使用Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES这个action将用户引导至安装未知应用权限界面去授权。
示例:
```java
public class SecActivity extends AppCompatActivity {
    private static final int REQUEST_CODE_UNKNOWN_APP = 100;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sec);
    }
    private void installAPK(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            boolean hasInstallPermission = getPackageManager().canRequestPackageInstalls();
            if (hasInstallPermission) {
                //安装应用
            } else {
                //跳转至“安装未知应用”权限界面,引导用户开启权限
                Uri selfPackageUri = Uri.parse("package:" + this.getPackageName());
                Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, selfPackageUri);
                startActivityForResult(intent, REQUEST_CODE_UNKNOWN_APP);
            }
        }else {
            //安装应用
        }
    }
    //接收“安装未知应用”权限的开启结果
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_UNKNOWN_APP) {
            boolean hasInstallPermission = false;
               if(android.os.Build.VERSION.SDK_INT>=       android.os.Build.VERSION_CODES.O) {
hasInstallPermission= getPackageManager().canRequestPackageInstalls();
}
      if (hasInstallPermission) {
         //安装应用
        installAPK();
       }
        }
    }
}

3.3. Only fullscreen opaque activities can request orientation
3.3.1.问题
Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
at android.app.Activity.onCreate(Activity.java:1038)
at android.support.v4.app.SupportActivity.onCreate(SupportActivity.java:66)
at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:321)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:84)
at com.fsh.lfmf.base.MyBaseActivity.onCreate(MyBaseActivity.java:24)
at com.fsh.lfmf.activity.RegisterActivity.onCreate(RegisterActivity.java:102)
at android.app.Activity.performCreate(Activity.java:7183)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1220)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2910)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6944)
3.3.2.原因
在Android8.0环境下,如果一个Activity在Manifest中设置了方向(横,竖屏),即android:screenOrientation=”landscape”/”portrait”,并且指定的android:theme中使用的style带有透明属性

3.3.3.解决方法
方法一:Activity设置android:windowIsTranslucent=false,然后设置指定屏幕方向;
方法二:Activity设置android:windowIsTranslucent=true,然后设置android:screenOrientation=”behind”,这样就可以保持屏幕方向统一了。

扫描二维码关注公众号,回复: 8612732 查看本文章

四.Android9.0(P)
4.1.网络请求
4.1.1.问题
CLEARTEXT communication to life.115.com not permitted by network security policy。
4.1.2.原因
Android P 限制了明文流量的网络请求(http),非加密的流量请求都会被系统禁止掉。建议使用https进行传输。
4.1.3.解决方案
(1)在资源文件新建xml目录,新建文件

main/res/xml/ network_security_config
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

(2)清单文件配置

 <application
android:networkSecurityConfig="@xml/network_security_config">
<uses-library
    android:name="org.apache.http.legacy"
    android:required="false" />
</application>

4.2. 其他Api的修改
4.2.1.问题
java.lang.IllegalArgumentException: Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed。
4.1.2. 解决方案

if (Build.VERSION.SDK_INT >= 26) {
    canvas.clipPath(mPath);
    } else {
    canvas.clipPath(mPath, Region.Op.REPLACE);
    }
发布了22 篇原创文章 · 获赞 31 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_36158551/article/details/88380604