Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

FileProvider N 7.0 升级 安装APK 选择文件 拍照 临时权限 MD


目录

问题

我们在开发 app 时避免不了需要添加应用内升级功能。当 app 启动时,如果检测到最新版本,将 apk 安装包从服务器下载下来,执行安装。

安装apk的代码一般写法如下,网上随处可以搜到

public static void installApk(Context context, File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri data = Uri.fromFile(file); //核心代码
intent.setDataAndType(data, "application/vnd.android.package-archive");
context.startActivity(intent);
}

然而,当我们在Android7.0手机中执行时,会发现会报如下错误日志:

Caused by: android.os.FileUriExposedException: file:///storage/emulated/0/Android/data/net.csdn.blog.ruancoder/cache/test.apk exposed beyond app through Intent.getData()
at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
at android.net.Uri.checkFileUriExposed(Uri.java:2346)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8933)
at android.content.Intent.prepareToLeaveProcess(Intent.java:8894)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517)
at android.app.Activity.startActivityForResult(Activity.java:4224)
at android.support.v4.app.BaseFragmentActivityJB.startActivityForResult(BaseFragmentActivityJB.java:50)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:79)
at android.app.Activity.startActivityForResult(Activity.java:4183)
at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:859)
at android.app.Activity.startActivity(Activity.java:4507)
at android.app.Activity.startActivity(Activity.java:4475)

官方文档的相关描述

FileUriExposedException官方文档 的一些描述:

  • 当应用程序将文件以【file://】形式的Uri公开到另一个应用程序时抛出的异常
  • 不鼓励这种曝光方式,因为接收的app可能无法访问你所共享的路径。例如,接收app可能未请求运行时权限Manifest.permission.READ_EXTERNAL_STORAGE ,或者平台可能跨用户配置文件边界[user profile boundaries]共享Uri。
  • 相反,应用程序应使用【content://】形式的Uris,以便平台可以扩展接收应用程序的临时权限以访问资源。
  • 仅针对 Build.VERSION_CODES.N 或更高版本的应用程序抛出此操作。 早期SDK版本的app可以以【file://】形式的Uri共享文件,但强烈建议不要这样做。

FileProvider 官方文档 的一些描述:

  • FileProvider是ContentProvider的一个特殊子类,它通过创建 content:// Uri 而不是 file:/// Uri 来促进与应用程序关联的文件的安全共享。
  • content URI 允许您使用临时访问权限来获取读写访问权限。当您创建包含 content URI 的Intent时,为了将 content URI 发送到客户端app,您还可以调用 Intent.setFlags() 来添加权限。只要接收 Activity 的堆栈处于活动状态,客户端应用程序就可以使用这些权限。对于转到Service的Intent,只要Service正在运行,权限就可用。
  • 相比之下,要控制对 file:/// Uri 的访问,您必须修改基础文件的文件系统权限。您提供的权限可供任何app使用,并在您更改之前保持有效。这种访问level从根本上说是不安全的。
  • 通过 content URI 提高文件访问安全性使FileProvider成为Android安全基础架构的关键部分。

配置

不要再去看垃圾官方文档了,也不要去看网上各种垃圾文章了,也不要通过看源码什么的去研究怎么个性化设置了,MLGB,我在几个月的时间内几次尝试理清怎么配置,结果还是发现了各种各样的bug,真的不要再折腾了,这些个性化的东西不重要,只要按照我下面的配置就行了,保证是最简单稳定的。

声明 FileProvider

在清单文件中声明FileProvider:

<manifest>
<application>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

其中:

  • android:name 固定写法

    如果要覆盖FileProvider方法的任何默认行为,可扩展FileProvider类并在这里使用完全限定的类名。

  • android:authorities 需自定义,是用来标识该 provider 的唯一标识,建议结合包名来保证 authority 的唯一性

    Set the android:authorities attribute to a URI authority based on a domain you control; for example, if you control the domain mydomain.com you should use the authority com.mydomain.fileprovider

  • android:exported 必须设置成 false,否则运行时会报错 java.lang.SecurityException: Provider must not be exported

    the FileProvider does not need to be public

  • android:grantUriPermissions 用来控制是否允许临时授予文件的访问权限,必须设置成 true
  • meta-data 节点
    • android:name 固定写法。
    • android:resource 指定共享文件的路径,此文件放在res/xml/

配置 resource

文件内容完全照抄就行了,鳖折腾。

res/xml/下添加file_paths.xml配置文件

<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path
name="files"
path="/"/>
<cache-path
name="cache"
path="/"/>
<external-path
name="external"
path="/"/>
<external-files-path
name="external_file_path"
path="/"/>
<external-cache-path
name="external_cache_path"
path="/"/>
<!--<external-media-path
name="external-media-path"
path=""/>-->
</paths>

可配置的元素:

  • files-path 对应内部存储目录 Context.getFilesDir()
  • cache-path 对应内部存储目录 Context.getCacheDir()
  • external-path 对应 Environment.getExternalStorageDirectory()
  • external-files-path 对应 Context.getExternalFilesDir()
  • external-cache-path 对应 Context.getExternalCacheDir()
  • external-media-path 对应 Context.getExternalMediaDirs()

系统提供的各种文件路径

很少会用到的路径:

String downloadCache = Environment.getDownloadCacheDirectory().getAbsolutePath(); //【/cache】
String data = Environment.getDataDirectory().getAbsolutePath(); //【/data】
String root = Environment.getRootDirectory().getAbsolutePath(); //【/system】

SD卡上的路径

String ext = Environment.getExternalStorageDirectory().getAbsolutePath(); //【/storage/emulated/0】
String extDowmload = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); //【/storage/emulated/0/Download】
String extDcim = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath(); //【/storage/emulated/0/DCIM】

data/包名下的路径:

String filesPath = getFilesDir().getAbsolutePath(); //【/data/user/0/包名/files】
String cachePath = getCacheDir().getAbsolutePath(); //【/data/user/0/包名/cache】
String extCachePath = getExternalCacheDir().getAbsolutePath(); //【/storage/emulated/0/Android/data/包名/cache】 String extFileDowmloadPath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();//【/storage/emulated/0/Android/data/包名/files/Download】
String extFilesDCIMPath = getExternalFilesDir(Environment.DIRECTORY_DCIM).getAbsolutePath();//【/storage/emulated/0/Android/data/包名/files/DCIM】

使用案例

安装指定路径的apk

记得要申请权限:

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

示例代码:

public class MainActivity extends AppCompatActivity {
private File apkFile;
public static final String FROM_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/temp.apk"; protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); apkFile = new File(FROM_PATH);
findViewById(R.id.tv).setOnClickListener(v -> {
//在6.0的华为手机上不能安装【getFilesDir()】目录下的安装包,但可以安装【getExternalStorageDirectory()】下的安装包
//而在8.0的小米手机上既可以安装【getFilesDir()】目录下的安装包,也可以安装【getExternalStorageDirectory()】下的安装包
File fileDir = new File(getFilesDir(), "bqt");
if (!fileDir.exists()) fileDir.mkdirs();
apkFile = new File(fileDir, "temp2.apk");
copyFile(new File(FROM_PATH), apkFile);
});
findViewById(R.id.tv2).setOnClickListener(v -> installApk(this, apkFile));
} public static void copyFile(File from, File to) {
try {
FileInputStream fis = new FileInputStream(from);
FileOutputStream fos = new FileOutputStream(to);
byte[] buf = new byte[1024];
int len;
while ((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
} private static void installApk(Context context, File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);
//【content://{$authority}/external/temp.apk】或【content://{$authority}/files/bqt/temp2.apk】
} else {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//【file:///storage/emulated/0/temp.apk】
uri = Uri.fromFile(file);
}
Log.i("bqt", "【Uri】" + uri);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
context.startActivity(intent);
}
}

拍照并指定保存位置

记得要申请权限:

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

示例代码:

public class MainActivity extends Activity {

    private int REQUEST_CODE_CAMERA = 10086;
private File tempFile; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.tv).setOnClickListener(v -> {
//TMLGB,在6.0的华为手机上,使用 getFilesDir() 铁定失败
//在8.0的小米6上,使用Environment.getExternalStorageDirectory()也同样失败
File fileDir = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ? getFilesDir() : Environment.getExternalStorageDirectory();
fileDir = new File(fileDir, "bqt");
if (!fileDir.exists()) fileDir.mkdirs();
tempFile = new File(fileDir, "temp");
showCamera(this, tempFile, REQUEST_CODE_CAMERA);
});
} @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_CAMERA && resultCode == Activity.RESULT_OK) {
Log.i("【bqt", tempFile.getAbsolutePath());//【data/user/0/包名/files/bqt/temp】或【/storage/emulated/0/bqt/temp】
}
} public static void showCamera(Activity activity, File file, int requestCode) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
String authority = activity.getPackageName() + ".fileprovider"; //【清单文件中provider的authorities属性的值】
uri = FileProvider.getUriForFile(activity, authority, file);
} else {
uri = Uri.fromFile(file);
}
Log.i("bqt", "【uri】" + uri);//【content://{$authority}/files/bqt/temp】或【file:///storage/emulated/0/bqt/temp】
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
activity.startActivityForResult(intent, requestCode);
}
}

2018-8-28

FileProvider N 7.0 升级 安装APK 选择文件 拍照 临时权限 MD的更多相关文章

  1. Android 8.0+ 更新安装apk失败的问题

    最近做项目发现Android 8.0+ 更新安装apk时 出现安装失败的情况  总结原因是 缺少安装的权限 Android 8.0 (Android O)为了针对一些流氓软件引导用户安装其他无关应用. ...

  2. Android 升级安装APK兼容Android7.0,解决FileUriExposedException

    我们在开发app时避免不了需要添加应用内升级功能.当app启动时,如果检测到最新版本,将apk安装包从服务器下载下来,执行安装.安装apk的代码一般写法如下,网上随处可以搜到 public stati ...

  3. 升级安装APK兼容Android7.0,解决FileUriExposedException

    见http://blog.csdn.net/ruancoder/article/details/67639621?utm_source=itdadao&utm_medium=referral

  4. android 6.0 Intent 安装apk闪退

    需求描述: 利用android系统自带的DownloadManager下载apk文件,并且打开安装界面. 问题描述: 关于DownloadManager的使用网上有很多例子,在此不啰嗦.下载完成之后在 ...

  5. android 8.0 intent安装apk失败屏幕闪过

    需要做两处设置: 1.android8.0要加一条权限: <uses-permission android:name="android.permission.REQUEST_INSTA ...

  6. AppUtils【获取手机的信息和应用版本号、安装apk】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 一个获取设备的系统版本号.设备的型号.应用版本号code值.应用版本号name值.包名.是否更新.安装apk的工具类. 其实这个工具 ...

  7. Android 7.0系统代码调用安装apk时报错FileUriExposedException完美解决

    项目更新遇到问题   Android项目开发中经常遇到下载更新的需求,以前调用系统安装器执行安装操作代码如下: Intent intent = new Intent(); intent.setActi ...

  8. 下载安装APK(兼容Android7.0)

    我们使用手机的时候经常会看到应用程序提示升级,大部分应用内部都需要实现升级提醒和应用程序文件(APK文件)下载. 一般写法都差不多,比如在启动app的时候,通过api接口获得服务器最新的版本号,然后和 ...

  9. android 版本更新适配8.0,解决8.0手机无法更新自动安装apk

    随着android 7.0的普及android 8.0的也逐渐流行起来,那么google对权限方面又有了新的修改.而且我发现在android8.0中除了一些bug,比如说:在小米6(Android 8 ...

随机推荐

  1. app分组

    将项目中中的urls.py复制到app当中 清空项目名称文件夹下的urls.py文件中的内容,并写入一下内容 from django.conf.urls import url,include urlp ...

  2. Xtreme8.0 - Play with GCD dp

    Play with GCD 题目连接: https://www.hackerrank.com/contests/ieeextreme-challenges/challenges/play-with-g ...

  3. Mac的brew和brew cask区别以及安装brew cask

    brew多用于命令行. brew cask主要用于有GUI的软件,例如VLC等等. brew cask是brew的一个子集,也就是一个扩展. 安装brew cask扩展: ruby -e " ...

  4. 使用 Spring 2.5 注释驱动的 IoC 功能(转)

    基于注释(Annotation)的配置有越来越流行的趋势,Spring 2.5 顺应这种趋势,提供了完全基于注释配置 Bean.装配 Bean 的功能,您可以使用基于注释的 Spring IoC 替换 ...

  5. 反接保护电路 Reverse Voltage Protection

    Reverse Voltage Protection I've long wanted to pull together some reverse polarity protection ideas ...

  6. 【报错】RSA host key for 192.168.1.xxx has changed and you have requested strict checking.

    执行如下对机拷贝命令 scp .ssh/id_rsa.pub phpgo@192.168.1.35:~ 时,报错 RSA host key for 192.168.1.xxx has changed ...

  7. rcp(插件开发)点击按钮出现 The chosen operation is not enabled 解决办法

    别的项目组,遇到以下错误信息: 首先看一下log日志里的异常信息,估计就知道是什么问题了. 项目组遇到的这个错误是source 指向错误 找不到相关的class.

  8. delphi代码实现窗口最小化,最大化,关闭消息发送

      分类: 代码实现窗口最小化,最大化,关闭 var hwnd: hwnd;//句柄 PostMessage(hwnd,WM_SYSCOMMAND, SC_MINIMIZE,0); //最小化Post ...

  9. 在busybox里使用ulimit命令

    刚才想使用ulimit修改用户进程的用户栈的大小,发现busybox里没有这个命令,上google搜索得到如下解释: "ulimit" is a shell builtin, me ...

  10. 在ASP.NET MVC中使用Knockout实践04,控制View Model的json格式内容

    通常,需要把View Model转换成json格式传给服务端.但在很多情况下,View Model既会包含字段,还会包含方法,我们只希望把字段相关的键值对传给服务端. 先把上一篇的Product转换成 ...