大家有沒有發現現在的手機 只要是在自家應用市場下載安裝的應用都不會彈出安裝界面直接就裝好了,今天我們就來實現這種功能
之前在網上查了資料好多都是很早之前的版本了 通過命令 調用系統接口 這些我都去驗證了 9.0 後對權限的管理很嚴了 命令的方式系統已經移除了 調用系統接口 PackageManager.installPackage() 在9.0 也移除了 在8.0 的時候都還沒有移除 有需要做兼容的可以以8.0做一個版本判斷, 但是在8.0 開始就採用了新的安裝方式代替,這種方式就是以會話的形式 通過會話進行通信 傳輸文件流給底層進行安裝。之前在網上查了很多資料都無果,於是去看了源碼 我們普通應用發起顯示安裝 都會通過Intent 這個時候就會被系統的一個應用攔截 這個就是安裝界面系統源碼路徑 packages/apps/PackageInstaller 其中 InstallInstalling.java 這個類裏面主要實現安裝邏輯代碼 我們將其中的關鍵代碼摘要出來。
首先在做好初始化工作
private int mSessionId = -1;
private PackageInstaller.SessionCallback mSessionCallback;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_install_test);
init();
}
public void init() {
mSessionCallback = new InstallSessionCallback();
getPackageManager().getPackageInstaller().registerSessionCallback(mSessionCallback);
}
private class InstallSessionCallback extends PackageInstaller.SessionCallback {
@Override
public void onCreated(int sessionId) {
// empty
Log.d(TAG, "onCreated()" + sessionId);
}
@Override
public void onBadgingChanged(int sessionId) {
// empty
Log.d(TAG, "onBadgingChanged()" + sessionId + "active");
}
@Override
public void onActiveChanged(int sessionId, boolean active) {
// empty
Log.d(TAG, "onActiveChanged()" + sessionId + "active" + active);
}
@Override
public void onProgressChanged(int sessionId, float progress) {
Log.d(TAG, "onProgressChanged()" + sessionId);
if (sessionId == mSessionId) {
int progres = (int) (Integer.MAX_VALUE * progress);
Log.d(TAG, "onProgressChanged" + progres);
}
}
@Override
public void onFinished(int sessionId, boolean success) {
// empty, finish is handled by InstallResultReceiver
Log.d(TAG, "onFinished()" + sessionId + "success" + success);
if (mSessionId == sessionId) {
if (success) {
Log.d(TAG, "onFinished() 安裝成功");
} else {
Log.d(TAG, "onFinished() 安裝失敗");
}
}
}
}
進行安裝的代碼
/**
* 適配android9的安裝方法。
* 全部替換安裝
*/
public void installApp(String apkFilePath) {
Log.d(TAG, "installApp()------->" + apkFilePath);
File apkFile = new File(apkFilePath);
if (!apkFile.exists()) {
Log.d(TAG, "文件不存在");
}
PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(apkFilePath, PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);
if (packageInfo != null) {
String packageName = packageInfo.packageName;
int versionCode = packageInfo.versionCode;
String versionName = packageInfo.versionName;
Log.d("ApkActivity", "packageName=" + packageName + ", versionCode=" + versionCode + ", versionName=" + versionName);
}
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams sessionParams
= new PackageInstaller.SessionParams(PackageInstaller
.SessionParams.MODE_FULL_INSTALL);
Log.d(TAG, "apkFile length" + apkFile.length());
sessionParams.setSize(apkFile.length());
try {
mSessionId = packageInstaller.createSession(sessionParams);
} catch (IOException e) {
e.printStackTrace();
}
Log.d(TAG, "sessionId---->" + mSessionId);
if (mSessionId != -1) {
boolean copySuccess = onTransfesApkFile(apkFilePath);
Log.d(TAG, "copySuccess---->" + copySuccess);
if (copySuccess) {
execInstallAPP();
}
}
}
/**
* 通過文件流傳輸apk
*
* @param apkFilePath
* @return
*/
private boolean onTransfesApkFile(String apkFilePath) {
Log.d(TAG, "---------->onTransfesApkFile()<---------------------");
InputStream in = null;
OutputStream out = null;
PackageInstaller.Session session = null;
boolean success = false;
try {
File apkFile = new File(apkFilePath);
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
out = session.openWrite("base.apk", 0, apkFile.length());
in = new FileInputStream(apkFile);
int total = 0, c;
byte[] buffer = new byte[1024 * 1024];
while ((c = in.read(buffer)) != -1) {
total += c;
out.write(buffer, 0, c);
}
session.fsync(out);
Log.d(TAG, "streamed " + total + " bytes");
success = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != session) {
session.close();
}
try {
if (null != out) {
out.close();
}
if (null != in) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return success;
}
/**
* 執行安裝並通知安裝結果
*
*/
private void execInstallAPP() {
Log.d(TAG, "--------------------->execInstallAPP()<------------------");
PackageInstaller.Session session = null;
try {
session = getPackageManager().getPackageInstaller().openSession(mSessionId);
Intent intent = new Intent(this, InstallResultReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this,
1, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
session.commit(pendingIntent.getIntentSender());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != session) {
session.close();
}
}
}
靜默卸載的程序
/**
* 根據包名卸載應用
*
* @param packageName
*/
public void uninstall(String packageName) {
Intent broadcastIntent = new Intent(this, InstallResultReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1,
broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
packageInstaller.uninstall(packageName, pendingIntent.getIntentSender());
}
其中InstallResultReceiver 使用靜態註冊 要不然你用動態註冊在安裝過程中你的應用已經不存在了所以你會搜不到廣播
<receiver
android:name=".receiver.InstallResultReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.content.pm.extra.STATUS"/>
</intent-filter>
</receiver>
public class InstallResultReceiver extends BroadcastReceiver {
private static final String TAG = "InstallResultReceiver";
@Override
public void onReceive(Context context, Intent intent) {
LogUtil.d(TAG, "收到安裝反饋廣播了");
if (intent != null) {
final int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
// success
LogUtil.d(TAG, "APP Install Success!");
InstallAPP.getInstance().sendInstallSucces();
} else {
String msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
LogUtil.e(TAG, "Install FAILURE status_massage" + msg);
InstallAPP.getInstance().sendFailure(msg);
}
}
}
最後不要忘記添加安裝權限 還有文件讀寫權限
<!--靜默安裝權限-->
<uses-permission
android:name="android.permission.INSTALL_PACKAGES"
tools:ignore="ProtectedPermissions" />
<!--應用卸載權限-->
<uses-permission android:name="permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission
android:name="android.permission.DELETE_PACKAGES"
tools:ignore="ProtectedPermissions" />
<!--讀寫外部存儲權限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--允許裝載和卸載文件系統權限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />