好記性不如爛筆頭
前期準備 要安裝的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
方法,其流程大概如下
具體情況如圖,第一次運行會動態提示
步驟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 時權限問題,可能有些同學發現並沒有文章中錯誤,其實除了手機系統,還與工程的配置有關 .若
compileSdkVersion
和targetSdkVersion
都比較小 比如小於6.0(API23),那麼即使系統是 6.0 也不會出現權限問題
水平有限,如果錯誤望大家不吝指正,謝謝