Files
CubeAoleyunSN/app/src/main/java/com/aoleyun/sn/utils/XAPKUtils.java
fanhuitong 3018660216 version:1.0
update:2021-10-13 18:52:13
fix:去除okgo,rxAndroid1,优化依赖
add:切换到奥乐云平台
2021-10-13 18:54:20 +08:00

407 lines
14 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}