##一、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广播,该应用程序仍然可以接收到该广播。
————————————————