首页 > 其他分享 >android-解决 Android N 上 报错:android.os.FileUriExposedException

android-解决 Android N 上 报错:android.os.FileUriExposedException

时间:2023-09-12 15:03:07浏览次数:47  
标签:Uri FileUriExposedException file 报错 android intent 下载 Intent


我们使用手机的时候经常会看到应用程序提示升级,大部分应用内部都需要实现升级提醒和应用程序文件(APK文件)下载。

一般写法都差不多,比如在启动app的时候,通过api接口获得服务器最新的版本号,然后和本地的版本号比较,来判断是否需要弹出提示框下载,当然也可以通过推送的自定义消息来实现。

我们这里主要讨论的是应用程序下载,并在通知栏提醒下载完成。 
实现过程大致分为三步:

  1. 创建一个service
  2. 在service启动的时候创建一个广播接受者,用于接受下载完成的广播
  3. 当BroadcastReceiver接受到下载完成的广播时,开始执行安装。

主要通过系统提供的DownloadManager进行下载,DownloadManager下载完成会发送广播,具体使用看下面完整的代码。如果详细了解可以参考Android系统下载管理DownloadManager功能介绍及使用示例下面创建新的文件DownloadService.java

public 
    class 
    DownLoadService 
    extends 
    Service
     
   
 
  

    /**广播接受者*/
   

     
   
 
  
private

     
   
 
  

    /**系统下载管理器*/
   

     
   
 
  
private

     
   
 
  

    /**系统下载器分配的唯一下载任务id,可以通过这个id查询或者处理下载任务*/
   

     
   
 
  
private 
    long

     
   
 
  

    /**TODO下载地址 需要自己修改,这里随便找了一个*/
   

     
   
 
  
private String downloadUrl=
    "http://dakaapp.troila.com/download/daka.apk?v=3.0";
   

     
   
 
  

     
   

     
   
 
  
@Nullable

     
   
 
  
@Override

     
   
 
  
public IBinder 
    onBind(Intent intent) {
   

     
   
 
  
return 
    null;
   

     
   
 
  

    }
   

     
   
 
  

     
   

     
   
 
  
@Override

     
   
 
  
public 
    int 
    onStartCommand(Intent intent, 
    int flags, 
    int

     
   
 
  

     
   

     
   
 
  
new

     
   
 
  
@Override

     
   
 
  
public 
    void 
    onReceive(Context context, Intent intent) {
   

     
   
 
  

    install(context);
   

     
   
 
  

    //销毁当前的Service
   

     
   
 
  

    stopSelf();
   

     
   
 
  

    }
   

     
   
 
  

    };
   

     
   
 
  
new

     
   
 
  

    //下载需要写SD卡权限, targetSdkVersion>=23 需要动态申请权限
   

     
   
 
  
this)
   

     
   
 
  

    // 申请权限
   

     
   
 
  

    .request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
   

     
   
 
  
new

     
   
 
  
@Override

     
   
 
  
public 
    void 
    call(Boolean granted) {
   

     
   
 
  
if(granted){
   

     
   
 
  

    //请求成功
   

     
   
 
  

    startDownload(downloadUrl);
   

     
   
 
  
else{
   

     
   
 
  

    // 请求失败回收当前服务
   

     
   
 
  

    stopSelf();
   

     
   
 
  

     
   

     
   
 
  

    }
   

     
   
 
  

    }
   

     
   
 
  

    });
   

     
   
 
  
return

     
   
 
  

    }
   

     
   
 
  

     
   

     
   
 
  

    /**
   

     
   
 
  

    * 通过隐式意图调用系统安装程序安装APK
   

     
   
 
  

    */
   

     
   
 
  
public 
    static 
    void 
    install(Context context) {
   

     
   
 
  
new

     
   
 
  

    // 由于没有在Activity环境下启动Activity,设置下面的标签
   

     
   
 
  

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   

     
   
 
  

    intent.setDataAndType(Uri.fromFile(
   

     
   
 
  
new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), 
    "myApp.apk")),
   

     
   
 
  
"application/vnd.android.package-archive");
   

     
   
 
  

    context.startActivity(intent);
   

     
   
 
  

    }
   

     
   
 
  

     
   

     
   
 
  
@Override

     
   
 
  
public 
    void 
    onDestroy() {
   

     
   
 
  

    //服务销毁的时候 反注册广播
   

     
   
 
  

    unregisterReceiver(receiver);
   

     
   
 
  
super.onDestroy();
   

     
   
 
  

    }
   

     
   
 
  

     
   

     
   
 
  
private 
    void 
    startDownload(String downUrl) {
   

     
   
 
  

    //获得系统下载器
   

     
   
 
  

    dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
   

     
   
 
  

    //设置下载地址
   

     
   
 
  
new

     
   
 
  

    //设置下载文件的类型
   

     
   
 
  
"application/vnd.android.package-archive");
   

     
   
 
  

    //设置下载存放的文件夹和文件名字
   

     
   
 
  
"myApp.apk");
   

     
   
 
  

    //设置下载时或者下载完成时,通知栏是否显示
   

     
   
 
  

    request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
   

     
   
 
  
"下载新版本");
   

     
   
 
  

    //执行下载,并返回任务唯一id
   

     
   
 
  

    enqueue = dm.enqueue(request);
   

     
   
 
  

    }
   

     
   
 
  

    }

上面代码使用了RxPermissions第三方库动态申请权限,需要在app/build.gradle文件中进行配置


dependencies {
   

     
   
 
  

    //
    ...

     
   
 
  
'com.tbruyelle.rxpermissions:rxpermissions:0.7.0@aar'

     
   
 
  
'io.reactivex:rxjava:1.1.6' 
    //需要引入RxJava
   

     
   
 
  

    }

记得要配置服务


application

     
   
 
  
...>
   

     
   
 
  
...

     
   
 
  
service 
    android:name=
    ".DownLoadService"/>
   

     
   
 
  
application>


最后在MainActivity中添加按钮,执行操作。运行结果: 

android-解决 Android N 上 报错:android.os.FileUriExposedException_Android

当下载的时候,会有通知栏进度条提示。下载完成会提示安装。不过当前程序如果在Android7.0上就会报错。下面是报错的日志: 

android-解决 Android N 上 报错:android.os.FileUriExposedException_ide_02


Caused by: android.os.FileUriExposedException:
   

     
   
 
  
file:
    ///storage/emulated/
    0
    /Download/myApp
    .apk
     exposed beyond app through Intent
    .getData
    ()
   
1
2

这是由于Android7.0执行了“StrictMode API 政策禁”的原因,不过小伙伴们不用担心,可以用FileProvider来解决这一问题,

现在我们就来一步一步的解决这个问题。

Android 7.0错误原因

随着Android版本越来越高,Android对隐私的保护力度也越来越大。

比如:Android6.0引入的动态权限控制(Runtime Permissions),Android7.0又引入“私有目录被限制访问”,“StrictMode API 政策”。

这些更改在为用户带来更加安全的操作系统的同时也为开发者带来了一些新的任务。如何让你的APP能够适应这些改变而不是crash,是摆在每一位Android开发者身上的责任。

“私有目录被限制访问“ 是指在Android7.0中为了提高私有文件的安全性,面向 Android N 或更高版本的应用私有目录将被限制访问。这点类似iOS的沙盒机制。

” StrictMode API 政策” 是指禁止向你的应用外公开 file:// URI。 如果一项包含文件 file:// URI类型 的 Intent 离开你的应用,应用失败,并出现 FileUriExposedException 异常。

上面用到的代码中的Uri.fromFile 其实就是生成一个file://URL。


//
    ...

     
   
 
  

    intent.setDataAndType(Uri.fromFile(
   

     
   
 
  
new

     
   
 
  

    Environment.DIRECTORY_DOWNLOADS),
   

     
   
 
  
"myApp.apk")),
   

     
   
 
  
"application/vnd.android.package-archive");
   

     
   
 
  

     
   

     
   
 
  

    //....


  • 一旦我们通过这种办法打开其它程序(这里打开系统包安装器)就认为file:// URI类型的 Intent 离开你的应用。这样程序就会发生异常。

接下来就用FileProvider来解决这一问题。

使用FileProvider

使用FileProvider的大致步骤如下:

第一步: 
在AndroidManifest.xml清单文件中注册provider,因为provider也是Android四大组件之一,可以简单把它理解为向外提供数据的组件,这种组件在实际开发中用的频率并不高,四大组件都可以在清单文件中进行配置。


 

application

     
   
 
  
...>
   

     
   
 
  
provider

     
   
 
  
android:name=
    "android.support.v4.content.FileProvider"

     
   
 
  
android:authorities=
    "com.yll520wcf.test.fileprovider"

     
   
 
  
android:grantUriPermissions=
    "true"

     
   
 
  
android:exported=
    "false">
   

     
   
 
  

    <!--元数据-->
   

     
   
 
  
meta-data

     
   
 
  
android:name=
    "android.support.FILE_PROVIDER_PATHS"

     
   
 
  
android:resource=
    "@xml/file_paths"

     
   
 
  
provider>
   

     
   
 
  
application>

注意:

  • exported:要求必须为false,为true则会报安全异常。
  • grantUriPermissions:true,表示授予 URI 临时访问权 
    限。
  • authorities 组件标识,按照江湖规矩,都以包名开头,避免和其它应用发生冲突。

第二步:指定共享的目录 
上面配置文件中 android:resource="@xml/file_paths" 指的是当前组件引用 res/xml/file_paths.xml 这个文件。

我们需要在资源(res)目录下创建一个xml目录,然后创建一个名为“file_paths”(名字可以随便起,只要和在manifest注册的provider所引用的resource保持一致即可)的资源文件,内容如下:

android-解决 Android N 上 报错:android.os.FileUriExposedException_Android_03

  • 代表的根目录: Context.getFilesDir()
  • 代表的根目录: Environment.getExternalStorageDirectory()
  • 代表的根目录: getCacheDir()

上述代码中path=”“,是有特殊意义的,它代码根目录,也就是说你可以向其它的应用共享根目录及其子目录下任何一个文件了。

如果你将path设为path="pictures",那么它代表着根目录下的pictures目录(eg:/storage/emulated/0/pictures),如果你向其它应用分享pictures目录范围之外的文件是不行的。

第三步:使用FileProvider 
上述准备工作做完之后,现在我们就可以使用FileProvider了。 
我们需要将上述安装APK代码修改为如下

public 
    static 
    void 
    install(Context context) {
   

     
   
 
  
new

     
   
 
  

    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
   

     
   
 
  
"myApp.apk");
   

     
   
 
  

    //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件
   

     
   
 
  

    Uri apkUri =
   

     
   
 
  
"com.com.yll520wcf.test.fileprovider", file);
   

     
   
 
  

     
   

     
   
 
  
new

     
   
 
  

    // 由于没有在Activity环境下启动Activity,设置下面的标签
   

     
   
 
  

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   

     
   
 
  

    //添加这一句表示对目标应用临时授权该Uri所代表的文件
   

     
   
 
  

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
   

     
   
 
  
"application/vnd.android.package-archive");
   

     
   
 
  

    context.startActivity(intent);
   

     
   
 
  

    }

上述代码中主要有两处改变: 
1. 将之前Uri改成了有FileProvider创建一个content类型的Uri。 
2. 添加了intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);来对目标应用临时授权该Uri所代表的文件。

上述代码通过FileProviderUri getUriForFile (Context context, String authority, File file)静态方法来获取Uri 
该方法中authority参数就是清单文件中注册provider时填写的authority 
android:authorities="com.yll520wcf.test.fileprovider"。 
按照上面步骤修改就可以兼容Android7.0了。


后期修改,之前没有考虑7.0以下的版本

但是如果此程序在Android7.0以下运行又会报错了,我们需要通过版本判断,当Android7.0及以上需要调用上面的代码,Android7.0以下需要调用7.0以下的代码。这样就OK了。修改install() 方法代码。


/**
   

     
   
 
  

    * 通过隐式意图调用系统安装程序安装APK
   

     
   
 
  

    */
   

     
   
 
  
public 
    static 
    void 
    install(Context context) {
   

     
   
 
  
new

     
   
 
  

    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
   

     
   
 
  
"myApp.apk");
   

     
   
 
  
new

     
   
 
  

    // 由于没有在Activity环境下启动Activity,设置下面的标签
   

     
   
 
  

    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   

     
   
 
  
if(Build.VERSION.SDK_INT>=
    24) { 
    //判读版本是否在7.0以上
   

     
   
 
  

    //参数1 上下文, 参数2 Provider主机地址 和配置文件中保持一致 参数3 共享的文件
   

     
   
 
  

    Uri apkUri =
   

     
   
 
  
"com.a520wcf.chapter11.fileprovider", file);
   

     
   
 
  

    //添加这一句表示对目标应用临时授权该Uri所代表的文件
   

     
   
 
  

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
   

     
   
 
  
"application/vnd.android.package-archive");
   

     
   
 
  
else{
   

     
   
 
  

    intent.setDataAndType(Uri.fromFile(file),
   

     
   
 
  
"application/vnd.android.package-archive");
   

     
   
 
  

    }
   

     
   
 
  

    context.startActivity(intent);
   

     
   
 
  

    }

标签:Uri,FileUriExposedException,file,报错,android,intent,下载,Intent
From: https://blog.51cto.com/u_14523369/7445464

相关文章

  • Android基础入门教程——8.1.1 Android中的13种Drawable小结 Part 1
    本节引言:从本节开始我们来学习Android中绘图与动画中的一些基础知识,为我们进阶部分的自定义 打下基础!而第一节我们来扣下Android中的Drawable!Android中给我们提供了多达13种的 Drawable,本节我们就来一个个撸一遍!Drawable资源使用注意事项Drawable分为两种: 一种是我们普通的图片......
  • 关于intent之android.intent.action.USER_PRESENT的接收与使用
    在做解锁监听程序时,一开始采用监听屏幕SCREEN_ON和SCREEN_OFF这两个action。但奇怪的是,这两个action只能通过代码动态的形式注册,才能被监听到,使用AndroidManifest.xml完全监听不到。百度后发现这是PowerManager那边在发这个广播的时候做了限制,限制只能有register到代......
  • Android之ListView详解
    前文ListView作为Android最常用的控件之一,同时也是最难的控件之一,其难点主要在意用法的多变性,因此让众多的初学者都比较难掌握,包括我自己,也是在反复需要使用时,总会卡住.而在网上找了众多的ListView的实例,案例等,讲解得不尽人意,甚至让许多初学者有迷惑.所以才觉得写此文,将......
  • android 很棒的UI合集 都是git地址很不错的需要makedown配合使用
    MaterialNameLicenseDemoMaterialDesignLibraryApacheLicenseV2DrawerArrowDrawableApacheLicenseV2MaterialTabsApacheLicenseV2PagerSlidingTabStripApacheLicenseV2material-rippleApacheLicenseV2RippleEffectMITLDrawerApacheLicenseV2material-design-icons......
  • Android静默安装实现方案
    之前有很多朋友都问过我,在Android系统中怎样才能实现静默安装呢?所谓的静默安装,就是不用弹出系统的安装界面,在不影响用户任何操作的情况下不知不觉地将程序装好。虽说这种方式看上去不打搅用户,但是却存在着一个问题,因为Android系统会在安装界面当中把程序所声明的权限展示给用户看,......
  • 关于Spring i18n国际化 报错No message found under code * for locale 'zh_CN'.的解
    第一步创建资源文件国际化文件命名格式:基本名称_语言_国家.properties 这里我建了两个配置文件,一个是zh_CN中文的,一个是en_GB英文的,然后在里面随便写点测试文本语句第二步bean.xmlspring配置文件1<?xmlversion="1.0"encoding="UTF-8"?>2<beansxmlns="http:/......
  • 解决Linux平台下R的报错问题
    安装BiocManagerinstall.packages("BiocManager")加载library(BiocManager)安装ggplot2install.packages("ggplot2")**byte-compileandpreparepackageforlazyloading错误:package‘cli’wasinstalledbeforeR4.0.0:pleasere-installit停止执行......
  • Android开发中常见的设计模式
    Android开发中常见的设计模式对于开发人员来说,设计模式有时候就是一道坎,但是设计模式又非常有用,过了这道坎,它可以让你水平提高一个档次。而在android开发中,必要的了解一些设计模式又是非常有必要的。对于想系统的学习设计模式的同学,这里推荐2本书。一本是HeadFirst系列的HeadH......
  • Mac执行pyautogui.screenshot()时报错
    报错信息---------------------------------------------------------------------------TypeErrorTraceback(mostrecentcalllast)CellIn[3],line1---->1pyautogui.screenshot()File~/anaconda3/lib/python3.11/site-package......
  • opatch报补丁时,oui-patch.xml (Permission denied)报错
    前言一套19.19RAC环境,使用opatch工具安装数据库补丁,第一个节点成功安装,但在第二个节点执行opatch命令时报错。主要的错误有提示:/u01/app/oraInventory/ContentsXML/oui-patch.xml(Permissiondenied),具体如下所示。[grid@19crac235074478]$$ORACLE_HOME/OPatch/opatcha......