增加xapk安装,未成功
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
/.idea/
|
||||
/ttutils/jniLibs/
|
||||
|
||||
@@ -168,6 +168,8 @@ dependencies {
|
||||
|
||||
//工具类
|
||||
implementation 'com.blankj:utilcodex:1.31.1'
|
||||
//压缩文件解压
|
||||
implementation 'org.zeroturnaround:zt-zip:1.15'
|
||||
//动态权限框架
|
||||
implementation 'com.github.getActivity:XXPermissions:18.6'
|
||||
// 吐司框架:https://github.com/getActivity/Toaster
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.example.mir4updater">
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.INSTALL_PACKAGE" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
@@ -12,6 +18,7 @@
|
||||
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
|
||||
<application
|
||||
@@ -37,9 +44,14 @@
|
||||
android:name=".activity.upload.UploadActivity"
|
||||
android:launchMode="singleTask" />
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.InstallResultReceiver"
|
||||
android:enabled="true"
|
||||
android:exported="true" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:authorities="${applicationId}.FileProvider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
@@ -22,7 +23,9 @@ import com.example.mir4updater.config.CommonConfig;
|
||||
import com.example.mir4updater.databinding.ActivityMainBinding;
|
||||
import com.example.mir4updater.utils.HashUtils;
|
||||
import com.example.mir4updater.utils.PermissionUtils;
|
||||
import com.example.mir4updater.utils.XapkUtils;
|
||||
import com.hjq.permissions.OnPermissionCallback;
|
||||
import com.hjq.permissions.Permission;
|
||||
import com.hjq.permissions.XXPermissions;
|
||||
import com.hjq.toast.Toaster;
|
||||
import com.trello.rxlifecycle4.RxLifecycle;
|
||||
@@ -53,6 +56,7 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
private static final int REQUEST_CODE = 1234;
|
||||
private static final int ANDROID_REQUEST_CODE = 9819;
|
||||
private static final int REQUEST_CODE_OPEN_DIR = 7890;
|
||||
private static final int REQUEST_INSTALL_CODE = 1111;
|
||||
|
||||
//google play 更新
|
||||
// private AppUpdateManager mAppUpdateManager;
|
||||
@@ -84,7 +88,6 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
@Override
|
||||
protected void initView() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -150,6 +153,8 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
// traverseDirectoryInternal(treeUri, 0);
|
||||
}
|
||||
}
|
||||
} else if (requestCode == REQUEST_INSTALL_CODE) {
|
||||
getInstallPermission();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,6 +479,58 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
});
|
||||
}
|
||||
|
||||
private void getInstallPermission() {
|
||||
XXPermissions.with(this)
|
||||
// 申请单个权限
|
||||
.permission(Permission.REQUEST_INSTALL_PACKAGES)
|
||||
// 申请多个权限
|
||||
// .permission(Permission.Group.STORAGE)
|
||||
// 设置权限请求拦截器(局部设置)
|
||||
//.interceptor(new PermissionInterceptor())
|
||||
// 设置不触发错误检测机制(局部设置)
|
||||
//.unchecked()
|
||||
.request(new OnPermissionCallback() {
|
||||
@Override
|
||||
public void onGranted(@NonNull List<String> permissions, boolean allGranted) {
|
||||
Log.e(TAG, "onGranted: permissions = " + permissions + " allGranted = " + allGranted);
|
||||
if (!allGranted) {
|
||||
// Toaster.show("获取部分权限成功,但部分权限未正常授予");
|
||||
return;
|
||||
}
|
||||
Log.e(TAG, "onGranted: 获取权限成功");
|
||||
installXapk();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDenied(@NonNull List<String> permissions, boolean doNotAskAgain) {
|
||||
Log.e(TAG, "onDenied: permissions = " + permissions + " doNotAskAgain = " + doNotAskAgain);
|
||||
if (doNotAskAgain) {
|
||||
Toaster.show("被永久拒绝授权,请手动授予权限");
|
||||
// 如果是被永久拒绝就跳转到应用权限系统设置页面
|
||||
XXPermissions.startPermissionActivity(MainActivity.this, permissions);
|
||||
} else {
|
||||
Log.e(TAG, "onGranted: 获取权限失败");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void installXapk() {
|
||||
File file = new File("/storage/emulated/0/Download/Google Chrome_135.0.7049.100_APKPure.xapk");
|
||||
XapkUtils.getInstance().installXapk(file.getAbsolutePath());
|
||||
}
|
||||
|
||||
private void requestPackageInstalls() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||
!getPackageManager().canRequestPackageInstalls()) {
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
|
||||
.setData(Uri.parse("package:" + getPackageName()));
|
||||
startActivityForResult(intent, REQUEST_INSTALL_CODE);
|
||||
} else {
|
||||
getInstallPermission();
|
||||
}
|
||||
}
|
||||
|
||||
public class BtnClick {
|
||||
public void upload(View view) {
|
||||
startActivity(new Intent(MainActivity.this, UploadActivity.class));
|
||||
@@ -490,5 +547,9 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
public void getAppStoragePermission(View view) {
|
||||
getPermssions();
|
||||
}
|
||||
|
||||
public void testInstall(View view) {
|
||||
requestPackageInstalls();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ public class MainViewModel extends BaseViewModel<ActivityMainBinding, ActivityEv
|
||||
Log.e(TAG, "subscribe: " + (System.currentTimeMillis() - time) + "ms");
|
||||
emitter.onComplete();
|
||||
}
|
||||
}).subscribeOn(Schedulers.io())
|
||||
})
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Observer<Integer>() {
|
||||
@Override
|
||||
@@ -65,7 +66,7 @@ public class MainViewModel extends BaseViewModel<ActivityMainBinding, ActivityEv
|
||||
|
||||
@Override
|
||||
public void onError(@NonNull Throwable e) {
|
||||
Log.e("patchAPK", "onError: ");
|
||||
Log.e("patchAPK", "onError: " + e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,8 @@ import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.mir4updater.BuildConfig;
|
||||
import com.example.mir4updater.utils.XapkUtils;
|
||||
import com.hjq.toast.Toaster;
|
||||
import com.tencent.mmkv.MMKV;
|
||||
|
||||
public class BaseApplication extends Application {
|
||||
@@ -22,6 +24,9 @@ public class BaseApplication extends Application {
|
||||
|
||||
// CrashReport.initCrashReport(getApplicationContext(), "8db7b63e60", false);
|
||||
xcrash.XCrash.init(this);
|
||||
Toaster.init(this);
|
||||
XapkUtils.init(this);
|
||||
|
||||
}
|
||||
|
||||
private void catchException() {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.example.mir4updater.bean;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Expansions implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -4958527331079431641L;
|
||||
|
||||
private String file;
|
||||
//包内文件地址
|
||||
private String install_location;
|
||||
//安装位置
|
||||
private String install_path;
|
||||
//安装地址
|
||||
|
||||
public String getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void setFile(String file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public String getInstall_location() {
|
||||
return install_location;
|
||||
}
|
||||
|
||||
public void setInstall_location(String install_location) {
|
||||
this.install_location = install_location;
|
||||
}
|
||||
|
||||
public String getInstall_path() {
|
||||
return install_path;
|
||||
}
|
||||
|
||||
public void setInstall_path(String install_path) {
|
||||
this.install_path = install_path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.example.mir4updater.bean;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class SplitApks implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 6908179385637563352L;
|
||||
|
||||
private String file;
|
||||
private String id;
|
||||
|
||||
public String getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void setFile(String file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.example.mir4updater.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.hjq.toast.Toaster;
|
||||
|
||||
public class InstallResultReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "InstallResultReceiver";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1);
|
||||
if (status == PackageInstaller.STATUS_SUCCESS) {
|
||||
Log.d(TAG, "XAPK 安装成功");
|
||||
} else {
|
||||
String errorMsg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
Log.e(TAG, "安装失败: " + errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
// @Override
|
||||
// public void onReceive(Context context, Intent intent) {
|
||||
// if (intent != null) {
|
||||
// int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
|
||||
// Log.e(TAG, "onReceive: " + status);
|
||||
// if (status == PackageInstaller.STATUS_SUCCESS) {
|
||||
// // success
|
||||
// String PACKAGE_NAME = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME);
|
||||
//
|
||||
// Log.e(TAG, "APP Install Success!");
|
||||
// } else {
|
||||
// String msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
// Log.e(TAG, "onReceive: " + msg);
|
||||
// }
|
||||
// }
|
||||
//// String s = intent.getAction();
|
||||
//// Log.e(TAG, s);
|
||||
//// Bundle extras = intent.getExtras();
|
||||
//// Set<String> ks = extras.keySet();
|
||||
//// Iterator<String> iterator = ks.iterator();
|
||||
//// while (iterator.hasNext()) {
|
||||
//// Log.d("KEY", iterator.next());
|
||||
//// }
|
||||
// String STATUS = intent.getStringExtra(PackageInstaller.EXTRA_STATUS);
|
||||
// String PACKAGE_NAME = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME);
|
||||
// String SESSION_ID = intent.getStringExtra(PackageInstaller.EXTRA_SESSION_ID);
|
||||
// String STATUS_MESSAGE = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
// String LEGACY_STATUS = intent.getStringExtra("android.content.pm.extra.LEGACY_STATUS");
|
||||
//
|
||||
// Log.e(TAG, "EXTRA_STATUS: " + STATUS);
|
||||
// Log.e(TAG, "PACKAGE_NAME: " + PACKAGE_NAME);
|
||||
// Log.e(TAG, "SESSION_ID: " + SESSION_ID);
|
||||
// Log.e(TAG, "STATUS_MESSAGE: " + LEGACY_STATUS);
|
||||
// Log.e(TAG, "LEGACY_STATUS: " + STATUS_MESSAGE);
|
||||
//
|
||||
// if ("INSTALL_SUCCEEDED".equals(STATUS_MESSAGE)) {
|
||||
// Toaster.show(PACKAGE_NAME + "安装成功");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -87,4 +87,6 @@ public class ApkUtils {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.example.mir4updater.utils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class FileUtils {
|
||||
public static String getFileNoExName(String filePath) {
|
||||
|
||||
String fileNameEx = filePath.substring(filePath.lastIndexOf(File.separator) + 1);
|
||||
String fileName = fileNameEx.substring(0,fileNameEx.lastIndexOf("."));
|
||||
return fileName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.example.mir4updater.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class JsonUtils {
|
||||
/**
|
||||
* 读取json文件,返回json串
|
||||
* @param fileName
|
||||
* @return
|
||||
*/
|
||||
public static String readJsonFile(String fileName) {
|
||||
String jsonStr = "";
|
||||
try {
|
||||
File jsonFile = new File(fileName);
|
||||
FileReader fileReader = new FileReader(jsonFile);
|
||||
|
||||
Reader reader = new InputStreamReader(new FileInputStream(jsonFile), StandardCharsets.UTF_8);
|
||||
int ch = 0;
|
||||
StringBuffer sb = new StringBuffer();
|
||||
while ((ch = reader.read()) != -1) {
|
||||
sb.append((char) ch);
|
||||
}
|
||||
fileReader.close();
|
||||
reader.close();
|
||||
jsonStr = sb.toString();
|
||||
return jsonStr;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,10 @@ public class PermissionUtils {
|
||||
Permission.MANAGE_EXTERNAL_STORAGE,
|
||||
};
|
||||
|
||||
public static final String[] Install_Permission = new String[]{
|
||||
Permission.REQUEST_INSTALL_PACKAGES,
|
||||
};
|
||||
|
||||
public static boolean checkAppData(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// 请求Android/data目录的权限,Android/obb目录则把data替换成obb即可。
|
||||
|
||||
480
app/src/main/java/com/example/mir4updater/utils/XapkUtils.java
Normal file
480
app/src/main/java/com/example/mir4updater/utils/XapkUtils.java
Normal file
@@ -0,0 +1,480 @@
|
||||
package com.example.mir4updater.utils;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.example.mir4updater.bean.Expansions;
|
||||
import com.example.mir4updater.bean.SplitApks;
|
||||
import com.example.mir4updater.receiver.InstallResultReceiver;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.hjq.toast.Toaster;
|
||||
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.ObservableEmitter;
|
||||
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
|
||||
import io.reactivex.rxjava3.core.Observer;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class XapkUtils {
|
||||
private static final String TAG = "XapkUtils";
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static XapkUtils sInstance;
|
||||
private Context mContext;
|
||||
|
||||
private XapkUtils(Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
// TODO: 2025/4/18 优化为单例模式
|
||||
public static void init(Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new XapkUtils(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static XapkUtils getInstance() {
|
||||
if (sInstance == null) {
|
||||
throw new IllegalStateException("You must be init XapkUtils first");
|
||||
}
|
||||
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 {
|
||||
Log.e(TAG, "installXAPK: " + "File not exists");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
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>() {
|
||||
@Override
|
||||
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
|
||||
if (ZipUtil.containsEntry(XapkFile, "manifest.json")) {
|
||||
//判断json文件是否存在
|
||||
String dirName = FileUtils.getFileNoExName(XapkFile.getAbsolutePath());
|
||||
unpackPath = XapkFile.getParentFile().getAbsolutePath() + File.separator + dirName;
|
||||
File unpackDir = new File(unpackPath);
|
||||
ZipUtil.unpack(XapkFile, unpackDir);
|
||||
String jsonString = JsonUtils.readJsonFile(unpackDir.getAbsolutePath() + File.separator + "manifest.json");
|
||||
emitter.onNext(jsonString);
|
||||
} else {
|
||||
Toaster.show("读取XAPK配置文件失败");
|
||||
emitter.onComplete();
|
||||
}
|
||||
}
|
||||
}).subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Observer<String>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(String s) {
|
||||
JsonObject jsonObject = JsonParser.parseString(s).getAsJsonObject();
|
||||
XapkSize = jsonObject.get("total_size").getAsLong();
|
||||
JsonElement split_configs_em = jsonObject.get("split_configs");
|
||||
//获取分割的配置
|
||||
JsonElement expansions_em = jsonObject.get("expansions");
|
||||
//获取OBB文件配置
|
||||
JsonElement split_apks_em = jsonObject.get("split_apks");
|
||||
readConfig(split_configs_em, expansions_em, split_apks_em);
|
||||
// Log.e(TAG, "installXAPK: " + jsonString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.e(TAG, "onError: " + e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Log.e(TAG, "onComplete: ");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private void readConfig(JsonElement split_configs, JsonElement expansions, JsonElement split_apks) {
|
||||
//没有split_configs只有单个apk,读取split_apks 的base字段的file文件
|
||||
//没有expansions 没有obb文件
|
||||
//split_apks应该是都会有的,包含一个base的json对象
|
||||
if (null != split_configs) {
|
||||
getSplitConfigs(split_configs.getAsJsonArray());
|
||||
} else {
|
||||
Log.e(TAG, "readConfig: " + "not found split_configs json data");
|
||||
}
|
||||
|
||||
if (null != expansions) {
|
||||
readOBBConfig(expansions.getAsJsonArray());
|
||||
} else {
|
||||
Log.e(TAG, "readConfig: " + "not found expansions json data");
|
||||
}
|
||||
|
||||
if (null != split_apks) {
|
||||
getSplitApks(split_apks.getAsJsonArray());
|
||||
} else {
|
||||
Log.e(TAG, "readConfig: " + "not found split_apks json data");
|
||||
}
|
||||
}
|
||||
|
||||
List<String> configList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 获取分裂的配置文件
|
||||
*
|
||||
* @param jsonArray
|
||||
*/
|
||||
private void getSplitConfigs(JsonArray jsonArray) {
|
||||
Type type = new TypeToken<List<String>>() {
|
||||
}.getType();
|
||||
Gson gson = new Gson();
|
||||
configList = gson.fromJson(gson.toJson(jsonArray), type);
|
||||
// StringBuilder configStringBuilder = new StringBuilder();
|
||||
// for (String config : configList) {
|
||||
// if (configStringBuilder.length() > 0) {
|
||||
// configStringBuilder.append(",");
|
||||
// }
|
||||
// configStringBuilder.append(config);
|
||||
// }
|
||||
// Log.e(TAG, "getSplitConfigs: " + configStringBuilder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取obb配置文件
|
||||
*
|
||||
* @param jsonArray
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private void readOBBConfig(JsonArray jsonArray) {
|
||||
if (TextUtils.isEmpty(unpackPath)) {
|
||||
Log.e(TAG, "readOBBConfig: " + "unpack directory is empty");
|
||||
return;
|
||||
}
|
||||
Type type = new TypeToken<List<Expansions>>() {
|
||||
}.getType();
|
||||
Gson gson = new Gson();
|
||||
List<Expansions> expansionsList = gson.fromJson(gson.toJson(jsonArray), type);
|
||||
if (null != expansionsList && expansionsList.size() > 0) {
|
||||
for (Expansions expansions : expansionsList) {
|
||||
if (copyObbFile(expansions)) {
|
||||
Log.e(TAG, "readOBBConfig: " + "success");
|
||||
} else {
|
||||
Log.e(TAG, "readOBBConfig: " + "copy oob File failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分裂的apk文件
|
||||
*
|
||||
* @param jsonArray
|
||||
*/
|
||||
private void getSplitApks(JsonArray jsonArray) {
|
||||
Type type = new TypeToken<List<SplitApks>>() {
|
||||
}.getType();
|
||||
Gson gson = new Gson();
|
||||
List<SplitApks> splitApksList = gson.fromJson(gson.toJson(jsonArray), type);
|
||||
getApkFilePath(configList, splitApksList);
|
||||
// if (null != splitApksList && splitApksList.size() > 0) {
|
||||
// for (SplitApks splitApks : splitApksList) {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param configList
|
||||
* @param apkList configList 可以为空,为空解析split_apks对象中的base
|
||||
* apkList应该不会为空
|
||||
*/
|
||||
private void getApkFilePath(List<String> configList, List<SplitApks> apkList) {
|
||||
List<String> filePath = new ArrayList<>();
|
||||
//应该直接获取SplitApks里面的file
|
||||
if (null != configList && configList.size() > 0) {
|
||||
//split_configs不为空的情况
|
||||
filePath.add(unpackPath + File.separator + apkList.get(getFileFromId(apkList, "base")).getFile());
|
||||
for (String config : configList) {
|
||||
int position = getFileFromId(apkList, config);
|
||||
if (position != -1) {
|
||||
String file = apkList.get(position).getFile();
|
||||
filePath.add(unpackPath + File.separator + file);
|
||||
}
|
||||
}
|
||||
// Log.e(TAG, "installxApk: " + filePath.toString());
|
||||
} else {
|
||||
//split_configs为空的情况
|
||||
int position = getFileFromId(apkList, "base");
|
||||
if (position != -1) {
|
||||
String file = apkList.get(position).getFile();
|
||||
filePath.add(file);
|
||||
Log.e(TAG, "installxApk: " + "base file = " + file);
|
||||
}
|
||||
}
|
||||
|
||||
if (filePath.size() != 0) {
|
||||
installApk(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id获取文件
|
||||
*
|
||||
* @param splitApks
|
||||
* @param id
|
||||
*/
|
||||
private int getFileFromId(List<SplitApks> splitApks, String id) {
|
||||
int position = -1;
|
||||
for (int i = 0; i < splitApks.size(); i++) {
|
||||
if (splitApks.get(i).getId().equals(id)) {
|
||||
position = i;
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
private void installApk(final List<String> paths) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
installAppatPie(paths);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public void installAppatPie(List<String> apkFilePath) {
|
||||
Log.e(TAG, "installApps: 正在安装应用 " + apkFilePath);
|
||||
// File file = new File(apkFilePath);
|
||||
PackageInstaller packageInstaller = mContext.getPackageManager().getPackageInstaller();
|
||||
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller
|
||||
.SessionParams.MODE_FULL_INSTALL);
|
||||
sessionParams.setSize(XapkSize);
|
||||
int sessionId = createSession(packageInstaller, sessionParams);
|
||||
if (sessionId != -1) {
|
||||
for (String apkPath : apkFilePath) {
|
||||
copyApkFile(packageInstaller, sessionId, apkPath);
|
||||
}
|
||||
// boolean copySuccess = ;
|
||||
// if (copySuccess) {
|
||||
Toaster.show("正在安装应用");
|
||||
install(packageInstaller, sessionId, mContext);
|
||||
// }
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
private static void install(PackageInstaller packageInstaller, int sessionId, Context context) {
|
||||
try {
|
||||
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
|
||||
Intent intent = new Intent(context, InstallResultReceiver.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
1, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
session.commit(pendingIntent.getIntentSender());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "install: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
private static int createSession(PackageInstaller packageInstaller, PackageInstaller.SessionParams sessionParams) {
|
||||
int sessionId = -1;
|
||||
try {
|
||||
sessionId = packageInstaller.createSession(sessionParams);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "createSession: " + e.getMessage());
|
||||
}
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
private static boolean copyApkFile(PackageInstaller pi, int sessionId, String apkFilePath) {
|
||||
boolean success = false;
|
||||
File apkFile = new File(apkFilePath);
|
||||
PackageInstaller.Session session = null;
|
||||
try {
|
||||
session = pi.openSession(sessionId);
|
||||
OutputStream out = session.openWrite(FileUtils.getFileNoExName(apkFilePath), 0, apkFile.length());
|
||||
FileInputStream input = new FileInputStream(apkFile);
|
||||
int read = 0;
|
||||
byte[] buffer = new byte[65536];
|
||||
// while (read != -1) {
|
||||
// read = input.read(buffer);
|
||||
// out.write(buffer, 0, read);
|
||||
// }
|
||||
while (true) {
|
||||
read = input.read(buffer);
|
||||
if (read == -1) {
|
||||
session.fsync(out);
|
||||
success = true;
|
||||
out.close();
|
||||
input.close();
|
||||
break;
|
||||
}
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.e("fht", "copyApkFile" + e.getMessage());
|
||||
}
|
||||
Log.e("fht", "copyApkFile" + "success = " + success);
|
||||
return success;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private boolean copyObbFile(Expansions expansions) {
|
||||
String install_location = expansions.getInstall_location();
|
||||
//install_location 不清楚是否还有其他定义
|
||||
|
||||
String file = expansions.getFile();
|
||||
String install_path = expansions.getInstall_path();
|
||||
|
||||
if (TextUtils.isEmpty(file)) {
|
||||
Log.e(TAG, "copyObbFile: " + "file path is empty");
|
||||
return false;
|
||||
} else {
|
||||
File localFile = new File(unpackPath + File.separator + file);
|
||||
if (localFile.exists() && localFile.isFile()) {
|
||||
File installFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + install_path);
|
||||
Log.e(TAG, "copyObbFile: " + "localFile: " + localFile.getAbsolutePath());
|
||||
Log.e(TAG, "copyObbFile: " + "installFile: " + installFile.getAbsolutePath());
|
||||
try {
|
||||
Path path = Paths.get(localFile.getAbsolutePath());
|
||||
Files.copy(path, new FileOutputStream(installFile));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "copyObbFile: " + "IOException" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "copyObbFile: " + "localFile: " + "File not exists");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Deprecated
|
||||
public static File unzipXapk(Context context, File xapkFile) throws IOException {
|
||||
File outputDir = new File(context.getExternalCacheDir(), "xapk_unzip");
|
||||
if (!outputDir.exists()) outputDir.mkdirs();
|
||||
|
||||
try (ZipFile zipFile = new ZipFile(xapkFile)) {
|
||||
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
File entryFile = new File(outputDir, entry.getName());
|
||||
if (entry.isDirectory()) {
|
||||
entryFile.mkdirs();
|
||||
} else {
|
||||
try (InputStream in = zipFile.getInputStream(entry);
|
||||
OutputStream out = new FileOutputStream(entryFile)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = in.read(buffer)) > 0) {
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return outputDir;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void installXapk(Context context, File outputDir) {
|
||||
PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
|
||||
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
||||
);
|
||||
|
||||
try {
|
||||
int sessionId = packageInstaller.createSession(params);
|
||||
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
|
||||
|
||||
File[] apkFiles = outputDir.listFiles(file -> file.getName().endsWith(".apk"));
|
||||
if (apkFiles == null) return;
|
||||
|
||||
for (File apkFile : apkFiles) {
|
||||
try (InputStream in = new FileInputStream(apkFile);
|
||||
OutputStream out = session.openWrite(apkFile.getName(), 0, apkFile.length())) {
|
||||
byte[] buffer = new byte[65536];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
session.fsync(out);
|
||||
}
|
||||
}
|
||||
|
||||
// 提交安装请求
|
||||
Intent callbackIntent = new Intent(context, InstallResultReceiver.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
context, sessionId, callbackIntent, PendingIntent.FLAG_IMMUTABLE
|
||||
);
|
||||
session.commit(pendingIntent.getIntentSender());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,23 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_app_storage_state" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_test_install"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@drawable/bt_update_bg"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:onClick="@{click::testInstall}"
|
||||
android:singleLine="true"
|
||||
android:text="安装测试"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tv_app_storage_permission" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user