安裝 assets 中的apk ,兼容6.0權限 處理7.0的崩潰

好記性不如爛筆頭

前期準備 要安裝的apk放到assets文件,三款手機 系統4.4 6.0 7.0
關於assets文件知識可以參考這裏
準備


步驟1 copy assets 文件到 存儲目錄
1.1 清單權限

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

1.2 調用copyAssets方法

 private void copyAssets(Context context, String filename) {
        AssetManager assetManager = context.getAssets();
        InputStream in;
        OutputStream out;
        try {
            in = assetManager.open(filename);
            String outFileName = Environment.getExternalStorageDirectory().
            getAbsolutePath();//保存到外部存儲,大部分設備是sd卡根目錄
            String copyName = System.currentTimeMillis() + "qq.apk";//copy後具體名稱
            File outFile = new File(outFileName, copyName);
            out = new FileOutputStream(outFile);
            copyFile(in, out);
            in.close();
            out.flush();
            out.close();
        } catch (IOException e) {
            System.out.println("");
        }
    }

    private void copyFile(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[1024];
        int read;
        while ((read = in.read(buffer)) != -1) {
            out.write(buffer, 0, read);
        }
    }

copyAssets() 傳入兩個參數,一個是Context 另一個是 assets 中的文件名(這裏傳“qq.apk”)

1.3 運行結果(僅限6.0以下) 這裏雖然調用.getExternalStorageDirectory()但是系統默認是集成的sd卡,也就是常見的 /storage/emulated/0 俗稱 內部存儲
結果如圖

1.4 安裝
修改 copyAssets 方法如下

 private void copyAssets(Context context, String filename) {
        AssetManager assetManager = context.getAssets();
        InputStream in;
        OutputStream out;
        try {
            in = assetManager.open(filename);
            String outFileName = Environment.getExternalStorageDirectory()
            .getAbsolutePath();//保存到外部存儲,大部分設備是sd卡根目錄
            String copyName = System.currentTimeMillis() + "qq.apk";//copy後具體名稱
            File outFile = new File(outFileName, copyName);
            out = new FileOutputStream(outFile);
            copyFile(in, out);
            in.close();
            out.flush();
            out.close();
            //copy 完畢,直接安裝
            File file = new File(outFileName, copyName);
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.fromFile(file),
                    "application/vnd.android.package-archive");
            context.startActivity(intent);
        } catch (IOException e) {
            System.out.println("");
        }
    }

1.5 運行結果
運行結果


步驟2 >6.0 系統權限問題

當以上代碼在 >6.0 系統運行時,會遇到權限問題 /storage/emulated/0/1493862005311qq.apk: open failed: EACCES (Permission denied)

這是因爲 6.0 系統引入了動態權限管理,針對此問題我們可以用三方的PermissionsDispatcher當然了,這裏不對三方過多依賴,我們自己請求權限, 首先代碼動態請求,然後在系統回調中處理邏輯

//請求權限
    private static final int READ_CONTACTS_REQUEST = 1;
    public void getPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            //發起請求獲得用戶許可,可以在此請求多個權限
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    READ_CONTACTS_REQUEST);
        } else {
            //權限許可過,直接拷貝
            copyAssets(MainActivity.this, "qq.apk");
        }
    }

    //系統方法,從requestPermissions()方法回調結果
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        //確保是我們的請求
        if (requestCode == READ_CONTACTS_REQUEST) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "寫文件權限被授予", Toast.LENGTH_SHORT).show();
                copyAssets(MainActivity.this, "qq.apk");
            } else if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "寫文件權限被拒絕", Toast.LENGTH_SHORT).show();
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

這時我們不直接調用copyAssets 而是調用getPermission 方法,其流程大概如下

Created with Raphaël 2.1.0開 始查看權限是否賦予了?直接拷貝結束請求權限onRequestPermissionsResult結果用戶是否賦予權限?直接拷貝結束用戶沒有給予權限邏輯yesnoyesno

具體情況如圖,第一次運行會動態提示
結果如圖


步驟3 7.0 的文件共享問題

以上代碼運行在 7.0 設備上會報錯 ,報錯位置就是在安裝時
File file = new File(outFileName, copyName);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file),
"application/vnd.android.package-archive");
context.startActivity(intent);

報錯內容如下
file:///storage/emulated/0/1493799271025qq.apk exposed beyond app through Intent.getData()
針對這個問題最常用的就是 FileProvider ,這裏也一樣

3.1 FileProvider 清單配置

provider 作爲四大組件之一,要在application節點下進行配置,就跟 activity 一樣

     <provider
            android:name="android.support.v4.content.FileProvider"
            //這裏是一個口令字符串,等會兒獲取文件時要用,內容可以隨意但前後必須一致
            android:authorities="com.nf.hp.assets.provider"
            android:grantUriPermissions="true"
            android:exported="false">
            <!--元數據-->
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                //這裏配置共享文件路徑,provider_paths只是文件名,可任意取
                android:resource="@xml/provider_paths" />
       </provider>

3.2 創建路徑配置文件
在 res 下創建 xml 文件夾 裏面新建 provider_paths文件 注意與清單中名稱一致
如圖
provider_paths 內容如下

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path path="." name="." />
</paths>

其中 <external-path path="." name="." /> 對應getExternalStorageDirectory()下所有文件及其子文件可以對外共享

3.3 再次修改 copyAssets安裝方法 如下

//copy 完畢,直接安裝
 File file = new File(outFileName, copyName);
 Intent intent = new Intent(Intent.ACTION_VIEW);
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        //>7.0時 用 provider 共享
       intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Uri contentUri = FileProvider.getUriForFile(context, "com.nf.hp.assets.provider",//這裏與provider清單中一致
            new File(outFileName, copyName));
                intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
 } else {
      intent.setDataAndType(Uri.fromFile(file),
        "application/vnd.android.package-archive");
 }
ontext.startActivity(intent);

備註

針對 >6.0 時權限問題,可能有些同學發現並沒有文章中錯誤,其實除了手機系統,還與工程的配置有關 .若 compileSdkVersiontargetSdkVersion 都比較小 比如小於6.0(API23),那麼即使系統是 6.0 也不會出現權限問題


水平有限,如果錯誤望大家不吝指正,謝謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章