关于Android7.0适配(SDK24)

49 篇文章 0 订阅
47 篇文章 0 订阅

##一、FileProvider

对于 Android 7.0,提供了非常多的变化,不过和我们开发者关联最大的,或者说必须要适配的就是去除项目中传递 file:// 类似格式的 Uri 了。

对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在应用外部公开 file:// URI , 如果一项包含文件 URI 的 intent 离开应用,则应用出现故障,并出现 FileUriExposedException 异常。

要应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。如需了解有关权限和共享文件的详细信息,请参阅 共享文件

FileProvider 是 ContentProvider 的一个子类,它的作用也比较明显,file://Uri 不给用,那么换个 Uri 为 content:// 来替代。

###FileProvider使用大概分为以下几个步骤:

#####a、manifest中申明FileProvider

<manifest>
  ...
  <application>
    ...
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.xx.xx.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/paths" />
    </provider>
    ...
  </application>
</manifest>

android:name:provider你可以使用v4包提供的FileProvider,或者自定义的,只需要在name申明就好了,一般使用系统的就足够了。

android:authorities:类似schema,命名空间之类,提供后面使用。

android:exported:false表示我们的provider不需要对外开放。

android:grantUriPermissions:申明为true,你才能获取临时共享权限。

#####b、res/xml中定义对外暴露的文件夹路径
新建filepaths.xml,文件名随便起,方便引用。
在 filepaths.xml 文件中,便可以指定文件存储的区域和路径。例如,以下路径元素告诉 FileProvider,你打算为私有文件区域的 files/ 子目录 请求内容 URI

<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <files-path name="my_files" path="files"/>
</paths>

必须包含以下元素中一个或者多个子元素

<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>

在 paths 节点内部支持以下几个子节点,分别为:

子节点含义
<root-path>代表设备的根目录 new File("/")
<files-path>代表 context.getFileDir()
<cache-path>代表 context.getCacheDir()
<external-path>代表 Environment.getExternalStorageDirectory()
<external-files-path>代表 context.getExternalFilesDirs()
<external-cache-path>代表 getExternalCacheDirs()

例如:
<files-path name="name" path="path" />
物理路径相当于Context.getFilesDir() + /path/。
例子:
<external-path name = “my_pictures” path = “/Pictures/CacheImage/”></external-path>

String path = Environment.getExternalStorageDirectory().getPath() + "/Pictures/CacheImage/142323232.jpg";
File file = new File(path);
if (file.exists()) {
   Uri uri = FileProvider.getUriForFile(Context, "com.xx.xx.fileproviderr", file);
   Log.i(TAG, "uri:" + uri.toString());
}

输出结果:uri:content://com.xx.xx.fileprovider/my_pictures/142323232.jpg

#####c、生成content://类型的Uri
将之前传递的 file:// 替换成 FileProvoider 需要用到的 content://,这就需要用到 FileProvider.getUriForFile() 方法了

    public static Uri getUriForFile(Context context, String authority, File file) {
        final PathStrategy strategy = getPathStrategy(context, authority);
        return strategy.getUriForFile(file);
    }

也可以使用Context.getUriForFile来实现:

File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), 
                 authority , newFile)

getUriForFile()方法需要传入 一个authority 的参数,这正是我们前面在 AndroidManifest.xml 文件中配置的 android:authorities 参数
调用这个方法会自动得到一个 file:// 转换成 content:// 的一个 Uri 对象。

#####d、给Uri授予临时权限

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
               | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

FLAG_GRANT_READ_URI_PERMISSION:表示读取权限;
FLAG_GRANT_WRITE_URI_PERMISSION:表示写入权限。

#####e、使用Intent传递Uri
最后拍照代码示例:

File imagePath = new File(Context.getFilesDir(), "files");
if (!imagePath.exists()){imagePath.mkdirs();}
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(), 
                 "com.xx.xx.fileprovider", newFile);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
// 授予目录临时共享权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
               | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(intent, 100);  

##二、分屏

#####a.
Android N允许用户一次在屏幕中使用两个App,用户可以左右并排/上下摆放两个App来使用。用户还可以左右/上下拖拽中间的那个小白线来改变两个App的尺寸。

#####b.
用户操作来进入分屏模式的:
1.点击右下角的方块,进入任务管理器,长按一个App的标题栏,将其拖入屏幕的高亮区域,这个App金进入了分屏模式。然后在任务管理器中选择另一个App,单击它使得这个App也进入分屏模式。
2.打开一个App,然后长按右下角的方块,此时已经打开的这个App将进入分屏模式。然后在屏幕上的任务管理器中选择另外一个App,单击它使得这个App也进入分屏模式。

#####c.
分屏模式的生命周期
官方说法:在分屏模式下,用户最近操作、激活过的Activity将被系统视为topmost。而其他的Activity都属于paused状态,即使它是一个对用户可见的Activity。但是这些可见的处于paused状态的Activity将比那些不可见的处于paused状态的Activity得到更高优先级的响应。当用户在一个可见的paused状态的Activity上操作时,它将得到恢复resumed状态,并被系统视为topmost。而之前那个那个处于topmpst的Activity将变成paused状态。

#####d.
通过android:resizeableActivity属性设置是否开启分屏

直接在AndroidManifest.xml中的或者标签下设置新的属性android:resizeableActivity=”true”。
设置了这个属性后,你的App/Activity就可以进入分屏模式了。
如果这个属性被设为false,那么你的App将无法进入分屏模式,如果你在打开这个App时,长按右下角的小方块,App将仍然处于全屏模式,系统会弹出Toast提示你无法进入分屏模式。这个属性在你target到Android N后,android:resizeableActivity的默认值就是true。

#####e.
最新的Android N SDK中,Activity类中增加了下面的方法。

inMultiWindow():返回值为boolean,调用此方法可以知道App是否处于分屏模式。
onMultiWindowChanged(boolean inMultiWindow):当Activity进入或者退出分屏模式时,系统会回调这个方法来通知开发者。回调的参数inMultiWindow为boolean类型,如果inMultiWindow为true,表示Activity进入分屏模式;如果inMultiWindow为false,表示退出分屏模式。

#####f.
支持拖拽
现在可以实现在两个分屏模式的Activity之间拖动内容。Android N Preview SDK中,View已经增加支持Activity之间拖动的API。具体的类和方法主要用到下面几个新的接口:

View.startDragAndDrop():View.startDrag() 的替代方法,需要传递View.DRAG_FLAG_GLOBAL来实现跨Activity拖拽。如果需要将URI权限传递给接收方Activity,还可以根据需要设置View.DRAG_FLAG_GLOBAL_URI_READ或者View.DRAG_FLAG_GLOBAL_URI_WRITE。
View.cancelDragAndDrop():由拖拽的发起方调用,取消当前进行中的拖拽。
View.updateDragShadow():由拖拽的发起方调用,可以给当前进行的拖拽设置阴影。
android.view.DropPermissions:接收方App所得到的权限列表。
Activity.requestDropPermissions():传递URI权限时,需要调用这个方法。传递的内容存储在DragEvent中的ClipData里。返回值为前面的android.view.DropPermissions。

在第一个Activity中,发起拖拽。

imageView.setOnTouchListener(new View.OnTouchListener() {
    public boolean onTouch(View view, MotionEvent motionEvent) {
        if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
            /**
             *  构造一个ClipData,将需要传递的数据放在里面
             */
            ClipData.Item item = new ClipData.Item((CharSequence) view.getTag());
            String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};
            ClipData dragData = new ClipData(view.getTag().toString(), mimeTypes, item);
            View.DragShadowBuilder shadow = new View.DragShadowBuilder(imageView);
            /**
             * startDragAndDrop是Android N SDK中的新方法,替代了以前的startDrag,
             * flag需要设置为DRAG_FLAG_GLOBAL
             */
            view.startDragAndDrop(dragData, shadow, null, View.DRAG_FLAG_GLOBAL);
            return true;
        } else {
            return false;
        }
    }
});

在第二个Activity中,接收这个拖拽的结果,在ACTION_DROP事件中,把结果显示出来。

dropedText.setOnDragListener(new View.OnDragListener() {
    @Override
    public boolean onDrag(View view, DragEvent dragEvent) {
        switch (dragEvent.getAction()) {
            case DragEvent.ACTION_DRAG_STARTED:
                Log.d(TAG, "Action is DragEvent.ACTION_DRAG_STARTED");
                break;
            case DragEvent.ACTION_DRAG_ENTERED:
                Log.d(TAG, "Action is DragEvent.ACTION_DRAG_ENTERED");
                break;
            case DragEvent.ACTION_DRAG_EXITED:
                Log.d(TAG, "Action is DragEvent.ACTION_DRAG_EXITED");
                break;
            case DragEvent.ACTION_DRAG_LOCATION:
                break;
            case DragEvent.ACTION_DRAG_ENDED:
                Log.d(TAG, "Action is DragEvent.ACTION_DRAG_ENDED");
                break;
            case DragEvent.ACTION_DROP:
                Log.d(TAG, "ACTION_DROP event");
                //在这里显示接收到的结果
                dropedText.setText(dragEvent.getClipData().getItemAt(0).getText());
                break;
            default:
                break;
        }
        return true;
    }
});

#####g.
分屏原理
分屏功能的实现主要依赖于ActivityManagerService与WindowManagerService这两个系统服务,它们都位于system_server进程中。该进程是Android系统中一个非常重要的系统进程。Framework中的很多服务都位于这个进程中。

整个Android的架构是CS的模型,应用程序是Client,而system_server进程就是对应的Server。

应用程序调用的很多API都会发送到system_server进程中对应的系统服务上进行处理,例如startActivity这个API,最终就是由ActivityManagerService进行处理。

而由于应用程序和system_server在各自独立的进程中运行,因此对于系统服务的请求需要通过Binder进行进程间通讯(IPC)来完成调用,以及调用结果的返回。

##三、广播

Android N后台的优化主要是关闭了三项系统广播:网络状态变更广播、拍照广播以及录像广播。

网络变化的广播(CONNECTIVITY_CHANGE),当网络发生变化时所有注册了隐式监听网络变化的app都会被启动。删除这些广播可以显著提升设备性能和用户体验。同样地,拍照广播和录视频广播(ACTION_NEW_PICTURE or ACTION_NEW_VIDEO)也会出现上述情况。

在Android N平台下即使在Manifest.xml清单文件中注册了 CONNECTIVITY_ACTION广播,在网络发生变化时也不会接收到任何的信息。但是正在前台运行的应用程序如果在主线程中通过Context.registerReceiver()动态注册了CONNECTIVITY_ACTION广播,该应用程序仍然可以接收到该广播。
————————————————

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值