1. 程式人生 > >Android 版本更新之開啟apk檔案的前生今世

Android 版本更新之開啟apk檔案的前生今世

現在APP都少不了的一個功能就是版本更新,檢測到有新版,從伺服器下載下來APK,然後安裝,今天就來聊一聊它。

原始碼地址:github

[Android6.0之前]

首先是許可權

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

然後是下載apk,這裡用assets 檔案模擬


    private
void copyFile() { InputStream inputStream = null; FileOutputStream outputStream = null; try { inputStream = context.getAssets().open("app-debug.apk"); String state = Environment.getExternalStorageState(); if (state.equals(Environment.MEDIA_MOUNTED)) {// 檢查是否有儲存卡
dir = Environment.getExternalStorageDirectory() + "/ceshi/"; File dirFile = new File(dir); if (!dirFile.exists()) { dirFile.mkdirs(); } File apk = new File(dir + "app-debug.apk"); if (!apk.exists()) { apk.createNewFile(); } outputStream = new
FileOutputStream(apk); byte[] buffer = new byte[1024]; int byteCount = 0; while ((byteCount = inputStream.read(buffer)) != -1) {// 迴圈從輸入流讀取 // buffer位元組 outputStream.write(buffer, 0, byteCount);// 將讀取的輸入流寫入到輸出流 } outputStream.flush();// 重新整理緩衝區 } } catch (IOException e) { e.printStackTrace(); } finally { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } }

然後是開啟apk

 /**
     * 開啟apk
     *
     * @param context
     * @param pathName 檔案路徑
     */
    public static void openFile(Context context, String pathName) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        context.startActivity(intent);
    }

[Android6.0]

由於Android6.0加入了執行時許可權,所以需要請求讀、寫記憶體卡許可權

compile 'com.yanzhenjie:permission:1.0.5'

TIP:目前開源的請求許可權庫比較多,選擇其一就好。

 if (AndPermission.hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE
                )) {
                    // 有許可權,直接do anything.
                    copyFile();

                } else {
                    // 申請許可權。
                    AndPermission.with(MainActivity.this)
                            .requestCode(101)
                            .permission(Manifest.permission.READ_EXTERNAL_STORAGE,
                                    Manifest.permission.WRITE_EXTERNAL_STORAGE)
                            .send();
                }
@Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions,
                                           int[] grantResults) {
        // 只需要呼叫這一句,其它的交給AndPermission吧,最後一個引數是PermissionListener。
        AndPermission.onRequestPermissionsResult(requestCode, permissions, grantResults, listener);
    }
private PermissionListener listener = new PermissionListener() {
        @Override
        public void onSucceed(int requestCode, List<String> grantedPermissions) {
            // 許可權申請成功回撥。
           if (requestCode == 101) {
                copyFile();
            }
        }

        @Override
        public void onFailed(int requestCode, List<String> deniedPermissions) {
            // 許可權申請失敗回撥。

            // 使用者否勾選了不再提示並且拒絕了許可權,那麼提示使用者到設定中授權。
            if (AndPermission.hasAlwaysDeniedPermission(MainActivity.this, deniedPermissions)) {
                // 第一種:用預設的提示語。
                AndPermission.defaultSettingDialog(MainActivity.this, 300).show();

            }
        }
    };

[Android7.0]

Android7.0加入了更嚴格的檔案共享許可權

首先是AndroidManifest.xml

 <!-- 建議使用包名加fileprovider,同一手機不能安裝一樣的authorities應用 -->
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.tianer.ch.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

然後是res目錄下加入xml資料夾,file_paths.xml

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

其中,path可以為空,表示指定目錄下的所有檔案、資料夾都可以被共享。

external-path 相當於Environment.getExternalStorageDirectory() + /path/。

然後是開啟apk時,需要判斷版本

 /**
     * 開啟apk
     *
     * @param context
     * @param pathName 檔案路徑
     */
    public static void openFile(Context context, String pathName, String authority) {
        if (pathName == null) {
            return;
        }
        File file = new File(pathName);
        if (file == null || !file.exists()) {
            return;
        }
        Intent intent = new Intent(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setDataAndType(FileProvider.getUriForFile(context, authority, file), "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        }
        context.startActivity(intent);
    }

各位看官,以為到這裡就結束了嗎?沒有,還有8.0!!!

[Android8.0]

Android8.0的變化是,未知應用安裝許可權的開關被除掉,取而代之的是未知來源應用的管理列表,需要在裡面開啟每個應用的未知來源的安裝許可權。Google這麼做是為了防止一開始正經的應用後來開始通過升級來做一些不合法的事情,侵犯使用者權益。

首先是許可權

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

開啟apk之前要做許可權判斷

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (getPackageManager().canRequestPackageInstalls()) {
                FileUtil.openFile(context, dir + "app-debug.apk", "com.tianer.ch.fileprovider");
            } else {
                // 申請許可權。
                startInstallPermissionSettingActivity();
            }
        }
@RequiresApi(api = Build.VERSION_CODES.O)
    private void startInstallPermissionSettingActivity() {
        //注意這個是8.0新API
        Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
        startActivityForResult(intent, 10086);
    }

然後是回撥

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 10086) {
            FileUtil.openFile(context, dir + "app-debug.apk", "com.tianer.ch.fileprovider");
        }
    }

到這裡,開啟apk算是告一段落。

你的認可,是我堅持更新部落格的動力,如果覺得有用,就請點個贊,謝謝