【適配整理】Android 7.0 調取系統相機崩潰解決android.os.FileUriExposedException


一、寫在前面

     最近由於廖子堯忙於自己公司的事情和 OkGo (一款專注於讓網絡請求更簡單的網絡框架) ,故讓LZ 接替維護 ImagePicker(一款支持單、多選、旋轉和裁剪的圖片選擇器),也是處理了諸多bug,最近總算趨於穩定了,這里就把Android N (API 24) 以上的相機適配方案分享給大家。

  Android Nougat 也是被更新很久了,作為一名 Andorid 開發者,我們有義務時刻准備自己調整 targetSdkVersion 為最近的一個,於是從之前的 23 直接提高到了 25 。

  和往常一樣,每當我們調整targetSdkVersion,我們需要檢查我們的代碼的每一部分工作的非常好。如果你只是簡單地更改代碼,我可以說,你的應用程序正在崩潰或故障的高風險。在這種情況下,當你改變你的應用程序的targetSdkVersion 24,我們需要檢查每一個功能完美的作品在Android的牛軋糖(24)以上。

    拿到 7.0 的小米5測試機后,迫不及待對自己維護的 ImagePicker 測試了一個遍,然而的確和大家所提的issuse一樣,在調用系統相機的時候直接崩潰了。

  

二、到底是什么引發了 7.0 相機崩潰

    跟進錯誤日志到源碼發現,在我們調用相機獲取 Uri 的時候發生了崩潰。

  

  原因很明顯,file:// 不被允許作為一個附加的 Uri 的意圖,否則會拋出 FileUriExposedException 。

三、為什么在 Android Nougat 下 file:// 不被允許?

     你可能會很好奇為什么 Android 團隊決定改變這種行為。

     其實背后有一個很好的理由,如果文件路徑被發送到目標應用程序(相機應用程序在這種情況下),文件將完全訪問通過相機應用程序的過程,而不僅僅只有發起者能收到。

     

    但讓我們考慮一下,實際上是由我們的應用程序去啟動攝像頭拍照,並保存作為我們的應用程序的代表文件。因此,該文件的訪問權限應該是我們的應用程序而不是攝像頭應用程序本身。這就是為什么現在 file:// 在 targetSdkVersion 24 中要求每一位開發者都去完成這個任務。

四、那到底怎么解決?

     既然 file:// 不再被允許,那我們應該怎么處理呢?答案是通過 FileProvider 去解決它。

     

 

     我們應該怎么讓 FileProvider 解決好它。

     1、首先是在 AndroidManifest.xml 中申明

<provider
android:authorities="${applicationId}.provider"
android:name=".ImagePickerProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>

     2、創建一個provider_paths.xml 文件在 res文件夾下的 xml 文件夾下。

      res/xml/provider_paths.xml 

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>

     3、在適當的地方去替換它

                Uri uri;
                if (VERSION.SDK_INT <= VERSION_CODES.M){
                    uri = Uri.fromFile(takeImageFile);
                }else{
                    /**
                     * 7.0 調用系統相機拍照不再允許使用Uri方式,應該替換為FileProvider
                     * 並且這樣可以解決MIUI系統上拍照返回size為0的情況
                     */
                    uri = FileProvider.getUriForFile(activity, ProviderUtil.getFileProviderName(activity), takeImageFile);
                }

                Log.e("nanchen",ProviderUtil.getFileProviderName(activity));
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,uri);

五、幾點說明

      對於上面的三步操作,做幾點說明:

      1、在 AndroidManifest.xml 文件中對 provider 的 name 屬性申明為什么是 .magePickerProvider (實際上這是一個繼承自 FileProvider 但什么也沒實現的類) 而不直接把name賦為android.support.v4.content.FileProvider ?

       這是因為 ImagePicker 作為一個圖片選擇框架,而你的 App 中同樣可能會有申明,為了避免 Android Studio 在編譯的時候 merge 各個 Module 導致沖突,這里保險起見的申明了一個不一樣的名字。

 

      2、為什么在 AndroidManifest.xml 文件中申明的 authority 屬性為 ${applicationId}.provider , 而不是固定的名字。

      這是因為在 Android 中,要求 authority 必須是唯一的,如果你在定義一個 provider 的時候為它指定一個唯一的 authority,這里且拿 ImagePicker 做比方,假如你在一個 App 上使用了 ImagePicker 作為圖片選擇框架,而你在另外一個應用中再次使用 ImagePicker 的時候,系統會檢查當然已安裝應用的 authority 是否和你要安裝應用的 authority 相同,如果相同則會彈出下面的警告,並安裝失敗。

     所以我們在定義 authorities 的時候采用 applicationId + provider 的形式,在獲取 authorities 的時候,我們可以通過包名 + provider 的方式獲取。代碼如下:

package com.lzy.imagepicker.util;

import android.content.Context;

/**
 * 用於解決provider沖突的util
 *
 * Author: nanchen
 * Email: liushilin520@foxmail.com
 * Date: 2017-03-22  18:55
 */

public class ProviderUtil {

    public static String getFileProviderName(Context context){
        return context.getPackageName()+".provider";
    }
}

六、寫在最后

     以上便解決了 Android N 的相機崩潰問題,如有寫的不對的地方,歡迎大家在評論區留言。

   圖片框架 ImagePicker 地址:https://github.com/jeasonlzy/ImagePicker 

     該文章同步發表到簡書:http://www.jianshu.com/p/57ba0d1511b9

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM