适配Android N (7.0)需要解决的问题汇总

  • Post author:
  • Post category:其他




java.lang.SecurityException: MODE_WORLD_READABLE no longer supported异常的原因与解决

MODE_WORLD_READABLE文件权限存在安全隐患,所以谷歌先把它标为过时。然后完全的移除掉。


MODE_WORLD_READABLE在Android M之前都是能用的,但是在Android N,使用它会抛出****SecurityException



所以我们可以换一个来使用,比如我用MODE_PRIVATE代替MODE_WORLD_READABLE。

// 在Android N之后,下面俩种情况会出SecurityException
FileOutputStream fos = context.openFileOutput(name,Context.MODE_WORLD_READABLE);


SharedPreferences sp = context.getSharedPreferences("", Context.MODE_WORLD_READABLE)

以上俩段代码,在Android M以前,都是没问问题,但是的在Android N以及以后的版本 ,会报SecurityException异常,修改为下面方式即可

FileOutputStream fos = context.openFileOutput(name,Context.MODE_PRIVATE);


SharedPreferences sp = context.getSharedPreferences("", Context.MODE_PRIVATE)



FileProvider去获取图片的Uri(FileUriExposedException异常的原因与解决)

在Andorid7.0之后,如果我们打开相机或者相册,获取其中图片的Uri,即通过file://URL开头的Url,就会报出

FileUriExposedException



#####7.0之前调用相机

在Android 7.0之前,打开相机拍照,获取照片的Uri,代码为

String imagePath= Environment.getExternalStorageDirectory()+"/demo/"

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

String fileName = "image.jpg";

File file = new File(CACHE_IMG, fileName);

Uri uri = Uri.fromFile(file);  // 获取照片的Uri

intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);

startActivityForResult(intent, 100);

上面的代码会在造成FileUriExposedException异常。因为通过Uri.fromFile(file)获取uri造成了file://URI暴漏。



7.0后怎样获取照片Uri

首先,我们需要在Application中定义一个内容提供者

 <application    
        ...>
        <provider
		    android:name="android.support.v4.content.FileProvider"        //固定值
            android:authorities="com.dujun.demo.fileprovider"              //路径 前面为包名,后面为fileprovider固定值,使用包名便于区分
		    android:exported="false"                                //是否支持其它应用调用当前组件 ,要求为flase
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"            //固定值
                android:resource="@xml/filepaths"/>                           //在res目录下xml文件夹下定义的filepaths.xml文件,名字可以自定义
        </provider>
  </application>

接着,在res目录下定义xml文件夹,然后创建一个filepaths.xml的文件(名称与在清单文件中配置的resource中的名称要相同)

<paths>
	<external-path path="demo" name="my_demo" > 
</paths>

注意:XMl文件是唯一设置分享的目录,不能用代码去设置

filepaths.xml中标签的解释:



  1. <files-path>


    这个标签是

    /data/data//files

    目录 即

    getFilesDir()

    获取到的目录。


  2. <cache-path>


    这个标签表示**/data/data//cache**目录 即

    getCacheDir()

    获取到的目录。


  3. <external-path>


    表示

    Environment.getExternalStorageDirectory()

    获取到的目录,即


    SDCard/Android/data/{package name}/files/

    目录。

    4.


    <external-files-path>


    Context#getExternalFilesDir(String) Context.getExternalFilesDir(null).

    5.


    <external-cache-path>


    Context.getExternalCacheDir().

    在标签中,是通过键值对来设置属性,通常有

    name



    path


    **path:**代表当前标签所表示的目录的下一级目录 比如:

    <external-path path="demo/"

    这个表示的目录为

    Environment.getExternalStorageDirectory()+"/demo/"


    **name:**代表定义在Content中的字段 比如:name = “demoimage” ,并且请求的内容的文件名为demo_image.jpg 则 返回一个URI content://com.example.myapp.fileprovider/demoimage/demo_image.jpg

接这,开始使用。

继续上面调用相机代码

String imagePath= Environment.getExternalStorageDirectory()+"/demo/"

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

String fileName = "image.jpg";

File file = new File(CACHE_IMG, fileName);

Uri uriForFile = FileProvider.getUriForFile(context, "com.dujun.demo.fileprovider", file);      // 

intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);

startActivityForResult(intent, 100);

主要的不同就是在获取图片的Uri上面,在Andorid N之前

Uri uri = Uri.fromFile(file);

获取

在Android N之后

Uri uriForFile = FileProvider.getUriForFile(context, "com.dujun.demo.fileprovider", file);

获取。

###4.4以下版本java.lang.SecurityException: Permission Denial异常的原因与解决。

但是,当我们使用以上方式运行在4.4的模拟器上时,应用还是crash.抛出了**Permission Denial~**异常。

因为在低版本的系统,我们的contentprovider的export设置的也是false;导致Permission Denial。

那么我们能否设置export=true.

查看FileProvider内部代码:

@Override
public void attachInfo(Context context, ProviderInfo info) {
    super.attachInfo(context, info);

    // Sanity check our security
    if (info.exported) {
        throw new SecurityException("Provider must not be exported");
    }
    if (!info.grantUriPermissions) {
        throw new SecurityException("Provider must grant uri permissions");
    }

    mStrategy = getPathStrategy(context, info.authority);
}

发现,如果设置为true,会抛出

throw new SecurityException("Provider must not be exported");


####解决方式1:授权

context中有俩个方法:


grantUriPermission(String toPackage, Uri uri,

int modeFlags)


revokeUriPermission(Uri uri, int modeFlags)

其中grantUriPermission(String toPackage, Uri uri,

int modeFlags)就是授权方法

param

toPackage

: 这个参数即包名,你给哪个应用授权,就传哪个应用的包名

所以这个包名需要动态的获取。

根据Intent查询出的所以符合的应用,都给他们授权

int flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
   | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
List<ResolveInfo> resInfoList = context.getPackageManager()
            .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName; // 获取到要授权的应用的包名
    context.grantUriPermission(packageName, uri, flag); // 调用授权代码进行授权
}

在不需要的时候可以调用

revokeUriPermission

移除权限



解决方式2(推荐)

判断版本,这种方式最简单,也

Uri fileUri = null;
if (Build.VERSION.SDK_INT >= 24) {
    fileUri = FileProvider.getUriForFile(context, "com.dujun.demo.fileprovider", file); 
} else {
    fileUri = Uri.fromFile(file);
}

###使用FileProvider安装Apk

在Android N 之前 我们安装Apk的代码为:

/**
 * @params file 需要安装的apk文件
 */
public static void installApk(File file) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(Uri.fromFile(file),
            "application/vnd.android.package-archive");
    startActivity(intent);
}

同样,在7.0 的系统上,还是抛出

FileUriExposedException


修改代码

Uri fileUri = null;
if (Build.VERSION.SDK_INT >= 24) {
    fileUri = FileProvider.getUriForFile(context, "com.dujun.demo.fileprovider", file); 
} else {
    fileUri = Uri.fromFile(file);
}

继续,又抛出了

SecurityException: Permission Denial

的警告。没有crash

通过上面

grantUriPermission

解决这个问题,没有问题。

但是在这里,可以使用另外一种便捷的方式去解决警告

intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);



引入的第三方库中已经配置了FileProvider(Manifest merger failed问题的解决)

但我们在项目中使用了FileProvider,出现下面的错误:

Error:Execution failed for task ':app:processDebugManifest'.
> Manifest merger failed : Attribute meta-data#android.support.FILE_PROVIDER_PATHS@resource value=(@xml/filepath) from AndroidManifest.xml:815:17-49
	is also present at [com.jph.takephoto:takephoto_library:4.0.3] AndroidManifest.xml:24:17-51 value=(@xml/file_paths).
	Suggestion: add 'tools:replace="android:resource"' to <meta-data> element at AndroidManifest.xml:813:13-815:52 to override.

通过查看错误信息中的这句话

is also present at [com.jph.takephoto:takephoto_library:4.0.3] AndroidManifest.xml:24:17-51 value=(@xml/file_paths).

我们看到了

出错的原因

:因为我使用了第三方的

com.jph.takephoto:takephoto_library

的库,在这个库中也配置了FileProvider.

继续看错误信息中给出的

解决方案

Suggestion: add 'tools:replace="android:resource"' to <meta-data> element at AndroidManifest.xml:813:13-815:52 to override.

重点内容

在我们的

<meta-data

标签中加入这句**

tools:replace="android:resource"

**

然后

在manifest节点中加入命名空间


xmlns:tools="http://schemas.android.com/tools"

 <application    
        ...>
        <provider
		    android:name="android.support.v4.content.FileProvider"       
            android:authorities="com.dujun.demo.fileprovider"          
		    android:exported="false"              
            android:grantUriPermissions="true">
            <meta-data
	            tools:replace="android:resource"  <!--加入这句代码即可解决问题,需要在manifest节点加入命名空间-->
                android:name="android.support.FILE_PROVIDER_PATHS"       
                android:resource="@xml/filepaths"/>  
        </provider>
  </application>



总结

为什么我们在拍照的时候,4.4设备出现权限问题,不通过addFlag去解决。

因为addFlags主要用于setData,setDataAndType以及setClipData(注意:4.4时,并没有将ACTION_IMAGE_CAPTURE转为setClipData实现)这种方式

所以

addFlags方式对于ACTION_IMAGE_CAPTURE在5.0以下是无效的

,所以需要使用grantUriPermission,如果是正常的通过setData分享的uri,使用addFlags是没有问题的

###一些实用工具文旦下载地址:


Gif图片录制软件

:Gif图片录制软件,可以帮助录制电脑屏幕上任意大小内容任意时间长度,而且还可以自己调节内容,可以断点续录。


RxJava中文文档

:Rxjava目前来说是非常流行的异步框架,掌握Rxjava可以在工作中得心应手,这里提供Rxjava中文文档。可以帮助你更好更快的掌握这项技术。


GifView(Gif图片播放器)

:Gif图片播放器,可以帮助你,像看电影一样,调节图播放的进度,而且还可以暂停播放


Android逆向助手

:反编译别人的App是不道德的,但是,如果是为了纯粹的学习,提升自己,还是可以的,Android逆向助手,可以帮助你,获取到你想要的



版权声明:本文为cn_1937原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。