1. 程式人生 > >Android6.0和7.0上遇到的坑以及解決方法

Android6.0和7.0上遇到的坑以及解決方法

android系統的版本已經更新到了8.0了。根據統計版本的分佈已經從過去的2.x推進到4.x以上了。所以開發中已經幾乎可以不考慮2.x等版本了。
然後像6.0以上的份額也越來越多。所以開發中是有必要考慮6.0以上版本的。
現在比較新的版本中,6.0(API23 VERSION_CODES M )和7.0(API24 VERSION_CODES N)的安全性大大提高。對許可權的要求也高了。所以以前4.X、5.X的上執行沒問題的程式,到了6.X,7.X上都可能會出現一些問題。
這是我前些天在我的手機上測試時候發現的。之前用的都是4.4測試,後來換7.0測試了。
第一個坑:6.0以上 特殊的許可權需要動態請求獲取,不再是清單檔案申請就ok的了。
我的程式碼中有在內部儲存中建立目錄的程式碼。建立目錄後接下來的一些檔案操作才能成功。之前在4.4上執行沒有問題。然後到7.0上總是出錯。一開始是摸不著頭腦~咋回事?莫非是手機管家類的禁止了?還是程式碼出現什麼問題?後面發現是建立目錄失敗了。

String path=Environment.getExternalStorageDirectory() + File.separator + "dirPath" + File.separator;
File file=new File(path);
if(!file.exists()){
    //建立目錄
    boolean mkdirs=file.mkdirs();
    Log.e(tag,"----"+mkdirs+"----");
}

log列印false。然後就是查詢資料。發現6.0以上寫檔案的許可權是需要動態請求的,否則預設就是沒有這個許可權的。所以,通過傳送許可權請求的程式碼就可以解決這個問題了。

 public static final int PERMISSIONS_REQUEST_CODE = 1;

    /**
     * 發起一個寫檔案的許可權請求
     */
    public static boolean setApi23(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            /**
             * API23以上版本需要發起寫檔案許可權請求
             */
            ActivityCompat.requestPermissions(activity, new
String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSIONS_REQUEST_CODE); return true; } else { return false; } }

封裝成了一個方法。呼叫這個方法就可以彈出一個請求許可權的dialog了。因為android的dialog都不會阻塞執行緒,如果在使用者沒有同意授權之前就執行了程式碼,仍然會出錯。所以需要判斷一下,而且需要一個回撥介面。
示例程式碼:

//activity onCreate中
if(!setApi23(this)){
    //api23以下!正常執行
}
//使用者操作後的回撥方法
@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        switch (requestCode) {
             //上面請求時候的請求碼
            case FileUtils.PERMISSIONS_REQUEST_CODE:
                if (grantResults.length > 0 && grantResults[0] == PackageManager
                        .PERMISSION_GRANTED) {
                   Log.e(tag,"獲取成功");
                   //正常執行程式碼
                } else {
                    Log.e(tag,"獲取失敗");
                    //乾點啥好?...
                }
                break;
        }
    }

6.0上對使用者的安全考慮,這會讓一些比較涉及使用者隱私的許可權都暴露給使用者知道。是一個比較讚的設計。而且程式碼修改也是比較簡單。不像7.0上的另外一個坑。害我花了好長時間才搞定。

第二個坑:7.0以上 和外部分享檔案必須要通過provider
這個是對應用的安全考慮,因為在7.0之前,比如呼叫系統應用開啟檔案都是通過 Uri.fromFile(file);獲得的。而7.0後如果需要讓外部應用通過Intent訪問自己應用所有的檔案,必須通過一個FileProvider類來獲得Intent。就是通過四大元件的方式來實現。
這個android已經寫好了。
第一步:
在清單檔案中註冊

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.xxx.xxxx.xxxxx"
    android:exported="false"
    android:grantUriPermissions="true">
<meta-data
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/file_paths"/>
</provider>

這個已經是固定模式了。除了android:authorities和android:resource這兩個屬性可以自定義外,其他都得按照這個形式。比如exported要求必須為false,為true的話會報安全異常;grantUriPermissions:true表示授予 URI 臨時訪問許可權。這裡的authorities填的是我的應用包名。
還有一點需要注意。這個provider一定要寫在Application標籤內。如果粗心寫在了外面就會報一個空指標異常。
然後還必須建立這個xml檔案。xml檔案如下:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="xxoo"
        path=""/>
 <!--<files-path/>代表目錄 Context.getFilesDir()
<external-path/>代表目錄: Environment.getExternalStorageDirectory()
<cache-path/>代表的目錄: getCacheDir();-->
</paths>

比較坑人的地方就是這個xml檔案了。一開始不知道name和path屬性代表什麼意思。所以不知道這xml該怎麼寫。網上看文章全都是清一色的複製貼上。連文字都一模一樣。都沒有說清楚這個xml檔案的name和path的意義。究竟是path填什麼,然後name是否代表目錄的名稱啊都懵逼了~最後看到這篇文章 Android7.0須知–應用間共享檔案(FileProvider) 中說的一句話我才明白這個name和path屬性該怎麼用。

“但是,因為有很多時候,圖片來源不確定,而且每款手機的相簿所在的檔名稱也可能不一樣,如果一一新增的話,很麻煩,而且容易遺漏,這裡,我用了一個簡單的方法,很方便。程式碼如下,這樣的話,我可以傳遞外部儲存裝置根目錄下的任意一張圖片了(包括其子目錄)
<external-path path="" name="camera_photos" />

看到這我才明白。其實屬性中的name是可以不用去管的。見名知意即可。然後path就是根據標籤為根目錄,它所在的目錄路徑,而且可以為空。
比如<external-path/>這個標籤代表
Environment.getExternalStorageDirectory()
假如path=”/image”
那麼最終就是指定了
Environment.getExternalStorageDirectory()+/image
所以當path=””
那就表示
Environment.getExternalStorageDirectory()+""
就是這麼回事了。

以上準備好後就可以了。程式碼中修改依舊是很簡單。

/**
     * 獲得呼叫系統應用開啟檔案的intent
     *
     * @param context
     * @param file
     */
    public static Intent getFileIntent(Context context, File file) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Uri contentUri;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //因為註冊的時候authorities填的是包名...
            String authority = context.getPackageName();
            //7.0上必須這樣獲得Intent
            contentUri = FileProvider.getUriForFile(context, authority, file);
        } else {
            //7.0以下使用舊版本的方式
            contentUri = Uri.fromFile(file);
        }
        String MimeType = context.getContentResolver().getType(contentUri);
        intent.setDataAndType(contentUri, MimeType);
        return intent;
    }

同樣是封裝了一個方法。判斷版本分別應付。得到intent後直接startActivity或傳遞給PendingIntent就行了。

以上就是我在7.0版本遇到的坑和解決辦法。當是自己記錄一下。懶得截圖了~幸好沒有懶得發文章。(最坑的是坑人的文章。不說清楚一點~!!吐槽ing→→ [雖然可能我也沒說清楚←←])
總體來講雖然一些原來普通的操作變得需要一些手續才能完成,但這會使Android系統變得越來越好。對開發者和使用者而言都是一個好訊息。畢竟我也是Android的使用者~