version:1.0
update:2021-10-13 18:52:13 fix:去除okgo,rxAndroid1,优化依赖 add:切换到奥乐云平台
This commit is contained in:
406
app/src/main/java/com/aoleyun/sn/utils/XAPKUtils.java
Normal file
406
app/src/main/java/com/aoleyun/sn/utils/XAPKUtils.java
Normal file
@@ -0,0 +1,406 @@
|
||||
package com.aoleyun.sn.utils;
|
||||
|
||||
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 androidx.annotation.RequiresApi;
|
||||
|
||||
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.aoleyun.sn.bean.Expansions;
|
||||
import com.aoleyun.sn.bean.SplitApks;
|
||||
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
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.List;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.ObservableEmitter;
|
||||
import io.reactivex.ObservableOnSubscribe;
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
public class XAPKUtils {
|
||||
//https://my.oschina.net/fxc0719/blog/4445198
|
||||
//https://www.jianshu.com/p/cd10d5278ebf?utm_campaign=hugo
|
||||
//https://www.jianshu.com/p/580b61ee7aee
|
||||
|
||||
private static final String TAG = XAPKUtils.class.getSimpleName();
|
||||
private static XAPKUtils sInstance;
|
||||
private Context mContext;
|
||||
|
||||
private XAPKUtils(Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void installXAPK(String filePath) {
|
||||
File file = new File(filePath);
|
||||
if (file.exists() && file.isFile()) {
|
||||
upzipXAPK(file);
|
||||
} else {
|
||||
Logutils.e(TAG, "installXAPK: " + "File not exists");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static String unpackPath;
|
||||
private static long XAPKSize;
|
||||
|
||||
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 {
|
||||
ToastUtil.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);
|
||||
// Logutils.e(TAG, "installXAPK: " + jsonString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Logutils.e(TAG, "onError: " + e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Logutils.e(TAG, "onComplete: ");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 {
|
||||
Logutils.e(TAG, "readConfig: " + "not found split_configs json data");
|
||||
}
|
||||
|
||||
if (null != expansions) {
|
||||
readOBBConfig(expansions.getAsJsonArray());
|
||||
} else {
|
||||
Logutils.e(TAG, "readConfig: " + "not found expansions json data");
|
||||
}
|
||||
|
||||
if (null != split_apks) {
|
||||
getSplitApks(split_apks.getAsJsonArray());
|
||||
} else {
|
||||
Logutils.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);
|
||||
// }
|
||||
// Logutils.e(TAG, "getSplitConfigs: " + configStringBuilder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取obb配置文件
|
||||
*
|
||||
* @param jsonArray
|
||||
*/
|
||||
private void readOBBConfig(JsonArray jsonArray) {
|
||||
if (TextUtils.isEmpty(unpackPath)) {
|
||||
Logutils.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)) {
|
||||
Logutils.e(TAG, "readOBBConfig: " + "success");
|
||||
} else {
|
||||
Logutils.e(TAG, "readOBBConfig: " + "copy oob File failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)) {
|
||||
Logutils.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);
|
||||
Logutils.e(TAG, "copyObbFile: " + "localFile: " + localFile.getAbsolutePath());
|
||||
Logutils.e(TAG, "copyObbFile: " + "installFile: " + installFile.getAbsolutePath());
|
||||
try {
|
||||
Path path = Paths.get(localFile.getAbsolutePath());
|
||||
Files.copy(path, new FileOutputStream(installFile));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Logutils.e(TAG, "copyObbFile: " + "IOException" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
Logutils.e(TAG, "copyObbFile: " + "localFile: " + "File not exists");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取分裂的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);
|
||||
}
|
||||
}
|
||||
// Logutils.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);
|
||||
Logutils.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(mContext, paths);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public static void installAppatPie(Context context, List<String> apkFilePath) {
|
||||
// File file = new File(apkFilePath);
|
||||
PackageInstaller packageInstaller = context.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) {
|
||||
ToastUtil.show("正在安装应用");
|
||||
install(packageInstaller, sessionId, context);
|
||||
// }
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
Logutils.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();
|
||||
Logutils.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();
|
||||
Logutils.e("fht", "copyApkFile" + e.getMessage());
|
||||
}
|
||||
Logutils.e("fht", "copyApkFile" + "success = " + success);
|
||||
return success;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user