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. HDU5791--Two (DP)

    题意:两个数列a,b,求相同的子序列有多少对,内容相同位置不同也算不同. 题解:dp[i][j]表示a数列前i个数个 b数列前j个数 有多少对 递推方程: dp[i][j] = dp[i-1][j-1 ...

  2. nodejs -mysql模块链接数据库创建库创建表单。

    var mysql = require('mysql'); var connection= mysql.createConnection({ host:'localhost', user:'root' ...

  3. UIWebView(本地数据部分)

    创建UIWebView和UISegmentedControl webView用于显示内容,segmentedControl用于切换读取内容的类型 为了方便起见 用拖拉控件形式布局完界面 /* 使用UI ...

  4. [置顶] 如何更改CSDN博客高亮代码皮肤的样式,使博客看起来更有范(推荐)

    由于本人写博客的时候,也没有配置博客的相关属性,因此贴出来的代码块都是CSDN默认的,因此代码背景色都是白色的,如下所示: 但是本人在浏览他人博客的时候,发现有些博客的代码块看起来比较有范,整个代码库 ...

  5. [转]对form:input标签中的数字进行格式化

    原文地址:https://blog.csdn.net/qq_29662201/article/details/80708373 数字进行格式化(保留2位小数) 单独使用<fmt:formatNu ...

  6. thymeleaf-extras-shiro

    thymeleaf-extras-shiro 转载:https://github.com/theborakompanioni/thymeleaf-extras-shiro A Thymeleaf di ...

  7. jQuery给动态添加生成的元素绑定事件的方法

    <div id="testdiv">   <ul></ul> </div> 需要给<ul>里面动态添加的<li&g ...

  8. CentOS7.0安装Nginx-1.12.0

    一.安装准备 首先由于nginx的一些模块依赖一些lib库,所以在安装nginx之前,必须先安装这些lib库,这些依赖库主要有g++.gcc.openssl-devel.pcre-devel和zlib ...

  9. jQuery查找

    导航查找方法: 向下查找兄弟标签: $().next() $().nextAll() 向上查找兄弟标签: 可以查找所有兄弟标签: 查找子标签 查找父级标签: $().parent() $().pare ...

  10. /etc/auto.master - automounter的主映射文件

    描述(DESCRIPTION) 当机器启动自动挂载器时, autofs(8) 脚本就会查寻 auto.master 这个主映射文件.文件中的每行分别指明,一个挂载点以及与对应的需要被挂载的文件系统.通 ...