对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。
要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。也就是说,对于应用间共享文件这块,Android N中做了强制性要求
来看一段代码
String cachePath = getApplicationContext().getExternalCacheDir().getPath();
File picFile = new File(cachePath, "test.jpg");
Uri picUri = Uri.fromFile(picFile);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, picUri);
startActivityForResult(intent, 100);
这是常见的打开系统相机拍照的代码,拍照成功后,照片会存储在picFile文件中。
这段代码在Android 7.0之前是没有任何问题,但是如果你尝试在7.0的系统上运行,会抛出FileUriExposedException异常。
FileProvider使用大概分为以下几个步骤:
...
...
"android.support.v4.content.FileProvider"
android:authorities="com.demo.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
"android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/paths" />
...
android:name:provider你可以使用v4包提供的FileProvider,或者自定义的,只需要在name申明就好了,一般使用系统的就足够了。
android:authorities:类似schema,命名空间之类,后面会用到。
android:exported:false表示我们的provider不需要对外开放。
android:grantUriPermissions:申明为true,你才能获取临时共享权限。
新建paths.xml,文件名随便起,后面会引用到。
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images"/>
paths>
name:一个引用字符串。
path:文件夹“相对路径”,完整路径取决于当前的标签类型。
path可以为空,表示指定目录下的所有文件、文件夹都可以被共享。
paths这个元素内可以包含以下一个或多个,具体如下:
<files-path name="name" path="path" />
物理路径相当于Context.getFilesDir() + /path/。
<cache-path name="name" path="path" />
物理路径相当于Context.getCacheDir() + /path/。
<external-path name="name" path="path" />
物理路径相当于Environment.getExternalStorageDirectory() + /path/。
<external-files-path name="name" path="path" />
物理路径相当于Context.getExternalFilesDir(String) + /path/。
<external-cache-path name="name" path="path" />
物理路径相当于Context.getExternalCacheDir() + /path/。
我们通常通过File生成Uri的代码是这样:
File picFile = xxx;
Uri picUri = Uri.fromFile(picFile);
这样生成的Uri,路径格式为file://xxx。这种Uri是无法在App之间共享的,我们需要生成content://xxx类型的Uri,方法就是通过Context.getUriForFile来实现:
File imagePath = new File(Context.getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(),
"com.demo.fileprovider", newFile)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
FLAG_GRANT_READ_URI_PERMISSION:表示读取权限;
FLAG_GRANT_WRITE_URI_PERMISSION:表示写入权限。
以开头的拍照代码作为示例,需要这样改写:
File imagePath = new File(Context.getFilesDir(), "images");
if (!imagePath.exists()){imagePath.mkdirs();}
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = getUriForFile(getContext(),
"com.mydomain.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);
于Android N后台的优化主要是关闭了三项系统广播:网络状态变更广播、拍照广播以及录像广播。
网络变化的广播(CONNECTIVITY_CHANGE),当网络发生变化时所有注册了隐式监听网络变化的app都会被启动。删除这些广播可以显著提升设备性能和用户体验。同样地,拍照广播和录视频广播(ACTION_NEW_PICTURE or ACTION_NEW_VIDEO)也会出现上述情况。
在Android N平台下即使在Manifest.xml清单文件中注册了 CONNECTIVITY_ACTION广播,在网络发生变化时也不会接收到任何的信息。但是正在前台运行的应用程序如果在主线程中通过Context.registerReceiver()动态注册了CONNECTIVITY_ACTION广播,该应用程序仍然可以接收到该广播。
Android N允许用户一次在屏幕中使用两个App,用户可以左右并排/上下摆放两个App来使用。用户还可以左右/上下拖拽中间的那个小白线来改变两个App的尺寸。
如何操作来进入分屏模式的:
官方说法:在分屏模式下,用户最近操作、激活过的Activity将被系统视为topmost。而其他的Activity都属于paused状态,即使它是一个对用户可见的Activity。但是这些可见的处于paused状态的Activity将比那些不可见的处于paused状态的Activity得到更高优先级的响应。当用户在一个可见的paused状态的Activity上操作时,它将得到恢复resumed状态,并被系统视为topmost。而之前那个那个处于topmpst的Activity将变成paused状态。
那么这种可见的pause的状态将带来什么影响呢?
在分屏模式中,一个App可以在对用户可见的状态下进入paused状态,所以你的App在处理业务时,应该知道自己什么时候应该真正的暂停。例如一个视频播放器,如果进入了分屏模式,就不应该在onPaused()回调中暂停视频播放,而应该在onStop()回调中才暂停视频,然后在onStart回调中恢复视频播放。关于如果知道自己进入了分屏模式,在Android N的Activity类中,增加了一个void onMultiWindowChanged(boolean inMultiWindow)回调,所以我们可以在这个回调知道App是不是进入了分屏模式。
分屏时Activity的生命周期
当前显示自己的应用页面,长按多任务键时出现分屏
onConfigurationChanged()-> onMultiWindowModeChanged()-> onPause()-> onStop()-> onDestroy()-> onCreate()-> onStart()-> onResume()-> onPause()
分屏时长按多任务键,全屏显示自己的应用时
onPause()-> onStop()-> onDestroy()-> onCreate()-> onStart()-> onResume()-> onPause()-> onConfigurationChanged()-> onMultiWindowModeChanged()-> onResume()
怎样才能让App进入分屏模式呢?有下面这几个属性。
android:resizeableActivity
直接在AndroidManifest.xml中的或者标签下设置新的属性android:resizeableActivity=”true”。
设置了这个属性后,你的App/Activity就可以进入分屏模式了。
如果这个属性被设为false,那么你的App将无法进入分屏模式,如果你在打开这个App时,长按右下角的小方块,App将仍然处于全屏模式,系统会弹出Toast提示你无法进入分屏模式。这个属性在你target到Android N后,android:resizeableActivity的默认值就是true。
注意:假如你没有适配到Android N(targetSDKVersion < Android N),打包App时的compileSDKVersion < Android N,你的App也是可以支持分屏的!!!!原因在于:如果你的App没有设置 仅允许Activity竖屏/横屏,即没有设置android:screenOrientation=”XXX”属性时,运行Android N系统的设备还是可以将你的App分屏!! 但是这时候系统是不保证运行时的稳定性的,在进入分屏模式时,系统首先也会弹出Toast来提示你说明这个风险。
最新的Android N SDK中,Activity类中增加了下面的方法。
现在可以实现在两个分屏模式的Activity之间拖动内容。Android N Preview SDK中,View已经增加支持Activity之间拖动的API。具体的类和方法主要用到下面几个新的接口:
在FirstActivity中,发起拖拽。
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;
}
}
});
在SecondActivity中,接收这个拖拽的结果,在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;
}
});
分屏功能的实现主要依赖于ActivityManagerService与WindowManagerService这两个系统服务,它们都位于system_server进程中。该进程是Android系统中一个非常重要的系统进程。Framework中的很多服务都位于这个进程中。
整个Android的架构是CS的模型,应用程序是Client,而system_server进程就是对应的Server。
应用程序调用的很多API都会发送到system_server进程中对应的系统服务上进行处理,例如startActivity这个API,最终就是由ActivityManagerService进行处理。
而由于应用程序和system_server在各自独立的进程中运行,因此对于系统服务的请求需要通过Binder进行进程间通讯(IPC)来完成调用,以及调用结果的返回。
ActivityManagerService负责Activity管理。
对于应用中创建的每一个Activity,在ActivityManagerService中都会有一个与之对应的ActivityRecord,这个ActivityRecord记录了应用程序中的Activity的状态。ActivityManagerService会利用这个ActivityRecord作为标识,对应用程序中的Activity进程调度,例如生命周期的管理。
实际上,ActivityManagerService的职责远超出的它的名称,ActivityManagerService负责了所有四大组件(Activity,Service,BroadcastReceiver,ContentProvider)的管理,以及应用程序的进程管理。
WindowManagerService负责Window管理。包括:
窗口的创建和销毁
窗口的显示与隐藏
窗口的布局
窗口的Z-Order管理
焦点的管理
输入法和壁纸管理
等等 每一个Activity都会有一个自己的窗口,在WindowManagerService中便会有一个与之对应的WindowState。WindowManagerService以此标示应用程序中的窗口,并用这个WindowState来存储,查询和控制窗口的状态。
ActivityManagerService与WindowManagerService需要紧密配合在一起工作,因为无论是创建还是销毁Activity都牵涉到Actiivty对象和窗口对象的创建和销毁。这两者是既相互独立,又紧密关联在一起的。
Task和Stack