Android 7.0分屏适配和文件权限

Android 7.0分屏适配和文件权限

  • 分屏适配
  • 文件权限

一、分屏适配

从Android N开始,添加了分屏和画中画功能。
在手持设备上,两个应用可以在“分屏”模式中左右并排或上下并排显示。
在电视设备上,应用可以使用“画中画”模式,如:用户与另一个应用交互的同时继续播放视频。

分屏,用户可以拖动两个应用之间的分割线,放大其中一个应用,同时缩小另一个。分屏情况下,通过拖动两个应用的分割线,应用可占屏幕的1/3、 1/2、 2/3。

1、进入和退出分屏

(1)进入分屏
  • 若用户打开 Overview 屏幕并长按 Activity 标题,则可以拖动该 Activity 至屏幕突出显示的区域,使 Activity 进入多窗口模式。
  • 若用户长按 Overview 按钮,设备上的当前 Activity 将进入多窗口模式,同时将打开 Overview 屏幕,用户可在该屏幕中选择要共享屏幕的另一个 Activity。
(2)退出分屏
  • 分屏状态下,长按 Overview 按钮
  • 拖动分割线,使应用全屏

2、分屏支持

在清单文件的 或 节点中设置该属性,启用或禁用多窗口显示:

android:resizeableActivity=["true" | "false"]

如果该属性设置为 true,Activity 将能以分屏和自由形状模式启动。 如果此属性设置为 false,Activity 将不支持多窗口模式。 如果该值为 false,且用户尝试在多窗口模式下启动 Activity,该 Activity 将全屏显示。

3、分屏应用的生命周期

若不在Manifest的Activity声明以下属性,应用分屏大小改变时:
* 如果未声明,则Activity都会销毁重启。
* 如果已声明,则Activity不会销毁重启,

android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout"

在分屏情况下,分屏时,用户点击分屏的某个界面,则获取
* 得到焦点的Activity 由 onPause -> onResume
* 失去焦点的Activity 由 onResume -> onPause

4、如何适配分屏

获取屏幕宽度

private void initScreenWidthDp() {
    WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    DisplayMetrics dm = new DisplayMetrics();
    wm.getDefaultDisplay().getMetrics(dm);
    sScreenWidthDp = (int) (dm.widthPixels / dm.density);
}

计算处于几分屏

public class ScreenManager {
    public static ScreenStatus sScreenStatus = ScreenStatus.FULL;
    /**
     * 0.6 < ratio < 0.7  2/3屏
     * 0.45 < ratio < 0.55  1/2屏
     * 0.3 < ratio < 0.4  1/3屏
     * 其他情况为全屏
     * @param widthDp 当前界面的宽度
     * */
    public static void setScreenStatusByWidth(int widthDp) {
        float ratio = (float) widthDp / FileBrowseApplication.getScreenWidthDp();
        if (ratio > 0.6 && ratio < 0.7) {
            sScreenStatus = ScreenStatus.TWO_THIRDS;
        } else if (ratio < 0.55 && ratio > 0.45) {
            sScreenStatus = ScreenStatus.HALF;
        } else if (ratio < 0.4 && ratio > 0.3) {
            sScreenStatus = ScreenStatus.ONE_THIRD;
        } else {
            sScreenStatus = ScreenStatus.FULL;
        }
    }

    public static ScreenStatus getScreenStatus() {
        return sScreenStatus;
    }

    public enum ScreenStatus {
        ONE_THIRD, HALF, TWO_THIRDS, FULL
    }
}

屏幕大小变化时,设置几分屏

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        ScreenManager.setScreenStatusByWidth(newConfig.screenWidthDp);
        changeViewByStatus();
    }

Notice:在onCreate()中也要设置分屏状态, 否则在分屏情况下打开某个Activity会导致分屏状态不对。

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (Utils.isGEandroid24()) {
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    initMultiWindow();
                }
            }, 0);
        }
    }

    private void initMultiWindow() {
        WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(dm);
        int widthDp = (int) (dm.widthPixels / dm.density);
        ScreenManager.setScreenStatusByWidth(widthDp);
        if (Utils.isGEandroid24() && isInMultiWindowMode()) {
            changeViewByStatus();
        }
    }

二、文件权限:

在Android 7.0及以上系统执行以下代码,使用Uri.fromFile(file)传递文件路径给第三方应用,将直接抛出FileUriExposedException异常(应用内使用则不存在该问题)。

Intent intent = new Intent(Intent.ACTION_VIEW);
File file = new File(fileItemInfo.getFilePath());
String mimeType = MimeHelper.getMimeType(fileItemInfo);
intent.setDataAndType(Uri.fromFile(file), mimeType);
startActivity(intent);

1、Intent 发送端

(1)在AndroidManifest中声明

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

其中file_paths.xml内容如下:

 "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>
  • 代表设备的根目录new File(“/”);
  • 代表context.getFilesDir()
  • 代表context.getCacheDir()
  • 代表Environment.getExternalStorageDirectory()
  • 代表context.getExternalFilesDirs()
  • 代表getExternalCacheDirs()

“”为空代表可以使用所有文件,若path不为“”,如:

"external"  path="pics" />

其代表的目录即为:Environment.getExternalStorageDirectory()/pics,其他同理。
当这么声明以后,代码可以使用你所声明的当前文件夹以及其子文件夹。

(2)在代码中使用

如打开内存卡根目录的文件,如 test.jpg, 则其文件路径为 content://com.seewo.easifinder.fileprovider/external/test.jpg

public static void setIntentDataAndType(Context context, Intent intent, String type, File file, boolean canWrite) {
        if (Utils.isGEandroid24()) {
            intent.setDataAndType(getUriForFile(context, file), type);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            if (canWrite) {
                intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            }
        } else {
            intent.setDataAndType(Uri.fromFile(file), type);
    }
}


private static Uri getUriForFile24(Context context, File file) {
    Uri fileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);
    return fileUri;
}

2、Intent 接收端

(1)获取文件类型

Uri returnUri = getIntent().getData();
String mimeType = getContentResolver().getType(returnUri);

(2)获取文件大小和名字

Uri uri = getIntent().getData();
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
cursor.moveToFirst();
String name = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
String size = cursor.getString(cursor.getColumnIndex(OpenableColumns.SIZE));

(3)获取文件句柄

ParcelFileDescriptor mParcelFileDescriptor = null;
try {
    mParcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r");
} catch (FileNotFoundException e) {
    e.printStackTrace();
}
FileDescriptor fd = mParcelFileDescriptor.getFileDescriptor();
FileInputStream fis = new FileInputStream(fd);

3、这样会导致什么问题?

  • 很多应用没有适配content://格式的uri
  • 不知道文件的真实路径(如编辑某个文件,不能直接保存,只能另存)

4、7.0及以上如何传递真实路径。

  • 使用key-value
  • 使用disableDeathOnFileUriExposure
使用disableDeathOnFileUriExposure

(1) 全局使用file://格式,在Applicaition中声明:

private void disableDeathOnFileUriExposure(){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        try {
             Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure");
             m.invoke(null);
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
}

(2) 局部使用file://格式

public void safelyStartActivity(TargetInfo cti) {
     // We're dispatching intents that might be coming from legacy apps, so
     // don't kill ourselves.
     StrictMode.disableDeathOnFileUriExposure();
     try {
            safelyStartActivityInternal(cti);
     } finally {
            StrictMode.enableDeathOnFileUriExposure();
     }
}

相关资料:

  • 多窗口支持
  • FileProvider

你可能感兴趣的:(Android)