测试xapk安装,优化xapkutils

This commit is contained in:
2025-04-21 18:04:09 +08:00
parent 8a7616c7cc
commit 39a3cb4eb9
4 changed files with 66 additions and 67 deletions

View File

@@ -21,6 +21,7 @@ public class InstallActivity extends AppCompatActivity {
setContentView(R.layout.activity_install); setContentView(R.layout.activity_install);
} }
@Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
super.onNewIntent(intent); super.onNewIntent(intent);
Log.e(TAG, "onNewIntent: "); Log.e(TAG, "onNewIntent: ");

View File

@@ -560,14 +560,25 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
} }
private void installXapk() { private void installXapk() {
// File file = new File("/storage/emulated/0/Download/Google Chrome_135.0.7049.100_APKPure.xapk"); File file = new File("/storage/emulated/0/Download/X_10.89.1-release.0_APKPure.xapk");
File file = new File("/sdcard/Download/GoogleChrome_135.0.7049.100_APKPure.xapk"); // File file = new File("/sdcard/Download/X_10.89.1-release.0_APKPure.xapk");
if (!file.exists()) {
// XapkUtils.getInstance().installXapk(file.getAbsolutePath()); Toaster.show("文件不存在");
XapkInstaller xapkInstaller = XapkInstallerFactoryKt.createXapkInstaller(file.getAbsolutePath()); return;
if (xapkInstaller!=null) {
xapkInstaller.installXapk(MainActivity.this);
} }
XapkUtils xapkUtils =new XapkUtils(MainActivity.this);
xapkUtils.setApkFilePath(file.getAbsolutePath());
xapkUtils.installXapk();
// TODO: 2025/4/21 google chrome 需要google框架
//Install failed! 7, INSTALL_FAILED_MISSING_SHARED_LIBRARY: Reconciliation failed...: Reconcile failed:
// Package com.android.chrome requires unavailable shared library com.google.android.trichromelibrary; failing!
//可以安装成功
// XapkInstaller xapkInstaller = XapkInstallerFactoryKt.createXapkInstaller(file.getAbsolutePath());
// if (xapkInstaller != null) {
// xapkInstaller.installXapk(MainActivity.this);
// }
} }
private void requestPackageInstalls() { private void requestPackageInstalls() {

View File

@@ -6,7 +6,6 @@ import android.os.Looper;
import android.util.Log; import android.util.Log;
import com.example.mir4updater.BuildConfig; import com.example.mir4updater.BuildConfig;
import com.example.mir4updater.utils.XapkUtils;
import com.hjq.toast.Toaster; import com.hjq.toast.Toaster;
import com.tencent.mmkv.MMKV; import com.tencent.mmkv.MMKV;
@@ -25,7 +24,6 @@ public class BaseApplication extends Application {
// CrashReport.initCrashReport(getApplicationContext(), "8db7b63e60", false); // CrashReport.initCrashReport(getApplicationContext(), "8db7b63e60", false);
xcrash.XCrash.init(this); xcrash.XCrash.init(this);
Toaster.init(this); Toaster.init(this);
XapkUtils.init(this);
} }

View File

@@ -1,10 +1,8 @@
package com.example.mir4updater.utils; package com.example.mir4updater.utils;
import android.annotation.SuppressLint;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
@@ -54,63 +52,59 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
public class XapkUtils { public class XapkUtils {
private static final String TAG = "XapkUtils"; private static final String TAG = "XapkUtils";
@SuppressLint("StaticFieldLeak")
private static XapkUtils sInstance;
private Context mContext; private Context mContext;
private String mApkFilePath;
private File mApkFile;
@Deprecated
private String mUnpackPath;
@Deprecated
private long mXapkSize;
public static final String PACKAGE_INSTALLED_ACTION = "SESSION_API_PACKAGE_INSTALLED"; public static final String PACKAGE_INSTALLED_ACTION = "SESSION_API_PACKAGE_INSTALLED";
private XapkUtils(Context context) { public XapkUtils(Context context) {
if (context == null) {
throw new RuntimeException("Context can not be null");
}
this.mContext = context; this.mContext = context;
} }
// TODO: 2025/4/18 优化为单例模式
public static void init(Context context) { public void setApkFilePath(String apkFilePath) {
if (sInstance == null) { this.mApkFilePath = apkFilePath;
sInstance = new XapkUtils(context); this.mApkFile = new File(mApkFilePath);
if (mApkFile.exists() && mApkFile.isFile()) {
Log.e(TAG, "setApkFilePath: " + "File exists and isFile");
} else {
Log.e(TAG, "setApkFilePath: " + "File not exists");
} }
} }
public static XapkUtils getInstance() { public void installXapk() {
if (sInstance == null) { if (mApkFile.exists() && mApkFile.isFile()) {
throw new IllegalStateException("You must be init XapkUtils first"); upzipXapk();
}
return sInstance;
}
@RequiresApi(api = Build.VERSION_CODES.O)
public void installXapk(String filePath) {
File file = new File(filePath);
if (file.exists() && file.isFile()) {
upzipXapk(file);
} else { } else {
Log.e(TAG, "installXAPK: " + "File not exists"); Log.e(TAG, "installXAPK: " + "File not exists");
} }
} }
@Deprecated private void upzipXapk() {
private static String unpackPath;
@Deprecated
private static long XapkSize;
@RequiresApi(api = Build.VERSION_CODES.O)
private void upzipXapk(File XapkFile) {
Observable.create(new ObservableOnSubscribe<String>() { Observable.create(new ObservableOnSubscribe<String>() {
@Override @Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception { public void subscribe(ObservableEmitter<String> emitter) throws Exception {
if (ZipUtil.containsEntry(XapkFile, "manifest.json")) { if (ZipUtil.containsEntry(mApkFile, "manifest.json")) {
//判断json文件是否存在 //判断json文件是否存在
String dirName = FileUtils.getFileNoExName(XapkFile.getAbsolutePath()); String dirName = FileUtils.getFileNoExName(mApkFile.getAbsolutePath());
unpackPath = XapkFile.getParentFile().getAbsolutePath() + File.separator + dirName; mUnpackPath = mContext.getExternalCacheDir()+ File.separator + "xapk_unzip" + File.separator + dirName;
File unpackDir = new File(unpackPath); File unpackDir = new File(mUnpackPath);
ZipUtil.unpack(XapkFile, unpackDir); ZipUtil.unpack(mApkFile, unpackDir);
String jsonString = JsonUtils.readJsonFile(unpackDir.getAbsolutePath() + File.separator + "manifest.json"); String jsonString = JsonUtils.readJsonFile(unpackDir.getAbsolutePath() + File.separator + "manifest.json");
emitter.onNext(jsonString); emitter.onNext(jsonString);
} else { } else {
Toaster.show("读取XAPK配置文件失败"); Toaster.show("读取XAPK配置文件失败");
emitter.onComplete();
} }
emitter.onComplete();
} }
}).subscribeOn(Schedulers.io()) }).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@@ -123,7 +117,7 @@ public class XapkUtils {
@Override @Override
public void onNext(String s) { public void onNext(String s) {
JsonObject jsonObject = JsonParser.parseString(s).getAsJsonObject(); JsonObject jsonObject = JsonParser.parseString(s).getAsJsonObject();
XapkSize = jsonObject.get("total_size").getAsLong(); mXapkSize = jsonObject.get("total_size").getAsLong();
JsonElement split_configs_em = jsonObject.get("split_configs"); JsonElement split_configs_em = jsonObject.get("split_configs");
//获取分割的配置 //获取分割的配置
JsonElement expansions_em = jsonObject.get("expansions"); JsonElement expansions_em = jsonObject.get("expansions");
@@ -145,28 +139,28 @@ public class XapkUtils {
}); });
} }
@RequiresApi(api = Build.VERSION_CODES.O) private void readConfig(JsonElement splitConfigs, JsonElement expansions, JsonElement splitApks) {
private void readConfig(JsonElement split_configs, JsonElement expansions, JsonElement split_apks) {
//没有split_configs只有单个apk,读取split_apks 的base字段的file文件 //没有split_configs只有单个apk,读取split_apks 的base字段的file文件
//没有expansions 没有obb文件 //没有expansions 没有obb文件
//split_apks应该是都会有的包含一个base的json对象 //split_apks应该是都会有的包含一个base的json对象
if (null != split_configs) { if (null != splitConfigs) {
getSplitConfigs(split_configs.getAsJsonArray()); getSplitConfigs(splitConfigs.getAsJsonArray());
} else { } else {
Log.e(TAG, "readConfig: " + "not found split_configs json data"); Log.e(TAG, "readConfig: " + "not found split_configs json data");
} }
if (null != expansions) { if (null != expansions) {
readOBBConfig(expansions.getAsJsonArray()); readObbConfig(expansions.getAsJsonArray());
} else { } else {
Log.e(TAG, "readConfig: " + "not found expansions json data"); Log.e(TAG, "readConfig: " + "not found expansions json data");
} }
if (null != split_apks) { if (null != splitApks) {
getSplitApks(split_apks.getAsJsonArray()); getSplitApks(splitApks.getAsJsonArray());
} else { } else {
Log.e(TAG, "readConfig: " + "not found split_apks json data"); Log.e(TAG, "readConfig: " + "not found split_apks json data");
} }
} }
List<String> configList = new ArrayList<>(); List<String> configList = new ArrayList<>();
@@ -196,9 +190,8 @@ public class XapkUtils {
* *
* @param jsonArray * @param jsonArray
*/ */
@RequiresApi(api = Build.VERSION_CODES.O) private void readObbConfig(JsonArray jsonArray) {
private void readOBBConfig(JsonArray jsonArray) { if (TextUtils.isEmpty(mUnpackPath)) {
if (TextUtils.isEmpty(unpackPath)) {
Log.e(TAG, "readOBBConfig: " + "unpack directory is empty"); Log.e(TAG, "readOBBConfig: " + "unpack directory is empty");
return; return;
} }
@@ -245,12 +238,12 @@ public class XapkUtils {
//应该直接获取SplitApks里面的file //应该直接获取SplitApks里面的file
if (null != configList && configList.size() > 0) { if (null != configList && configList.size() > 0) {
//split_configs不为空的情况 //split_configs不为空的情况
filePath.add(unpackPath + File.separator + apkList.get(getFileFromId(apkList, "base")).getFile()); filePath.add(mUnpackPath + File.separator + apkList.get(getFileFromId(apkList, "base")).getFile());
for (String config : configList) { for (String config : configList) {
int position = getFileFromId(apkList, config); int position = getFileFromId(apkList, config);
if (position != -1) { if (position != -1) {
String file = apkList.get(position).getFile(); String file = apkList.get(position).getFile();
filePath.add(unpackPath + File.separator + file); filePath.add(mUnpackPath + File.separator + file);
} }
} }
// Log.e(TAG, "installxApk: " + filePath.toString()); // Log.e(TAG, "installxApk: " + filePath.toString());
@@ -294,14 +287,13 @@ public class XapkUtils {
}).start(); }).start();
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void installAppatPie(List<String> apkFilePath) { public void installAppatPie(List<String> apkFilePath) {
Log.e(TAG, "installApps: 正在安装应用 " + apkFilePath); Log.e(TAG, "installApps: 正在安装应用 " + apkFilePath);
// File file = new File(apkFilePath); // File file = new File(apkFilePath);
PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller(); PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller
.SessionParams.MODE_FULL_INSTALL); .SessionParams.MODE_FULL_INSTALL);
sessionParams.setSize(XapkSize); sessionParams.setSize(mXapkSize);
int sessionId = createSession(packageInstaller, sessionParams); int sessionId = createSession(packageInstaller, sessionParams);
if (sessionId != -1) { if (sessionId != -1) {
for (String apkPath : apkFilePath) { for (String apkPath : apkFilePath) {
@@ -339,8 +331,7 @@ public class XapkUtils {
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private int createSession(PackageInstaller packageInstaller, PackageInstaller.SessionParams sessionParams) {
private static int createSession(PackageInstaller packageInstaller, PackageInstaller.SessionParams sessionParams) {
int sessionId = -1; int sessionId = -1;
try { try {
sessionId = packageInstaller.createSession(sessionParams); sessionId = packageInstaller.createSession(sessionParams);
@@ -351,8 +342,7 @@ public class XapkUtils {
return sessionId; return sessionId;
} }
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private boolean copyApkFile(PackageInstaller pi, int sessionId, String apkFilePath) {
private static boolean copyApkFile(PackageInstaller pi, int sessionId, String apkFilePath) {
boolean success = false; boolean success = false;
File apkFile = new File(apkFilePath); File apkFile = new File(apkFilePath);
PackageInstaller.Session session = null; PackageInstaller.Session session = null;
@@ -385,7 +375,6 @@ public class XapkUtils {
return success; return success;
} }
@RequiresApi(api = Build.VERSION_CODES.O)
private boolean copyObbFile(Expansions expansions) { private boolean copyObbFile(Expansions expansions) {
String install_location = expansions.getInstall_location(); String install_location = expansions.getInstall_location();
//install_location 不清楚是否还有其他定义 //install_location 不清楚是否还有其他定义
@@ -397,7 +386,7 @@ public class XapkUtils {
Log.e(TAG, "copyObbFile: " + "file path is empty"); Log.e(TAG, "copyObbFile: " + "file path is empty");
return false; return false;
} else { } else {
File localFile = new File(unpackPath + File.separator + file); File localFile = new File(mUnpackPath + File.separator + file);
if (localFile.exists() && localFile.isFile()) { if (localFile.exists() && localFile.isFile()) {
File installFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + install_path); File installFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + install_path);
Log.e(TAG, "copyObbFile: " + "localFile: " + localFile.getAbsolutePath()); Log.e(TAG, "copyObbFile: " + "localFile: " + localFile.getAbsolutePath());