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 android.util.Log; import androidx.annotation.RequiresApi; import com.aoleyun.sn.bean.Expansions; import com.aoleyun.sn.bean.SplitApks; 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 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.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; /** * @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 { Log.e(TAG, "installXAPK: " + "File not exists"); } } private static String unpackPath; private static long XAPKSize; private void upzipXAPK(File XAPKFile) { Observable.create(new ObservableOnSubscribe() { @Override public void subscribe(ObservableEmitter 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() { @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: "); } }); } 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 configList = new ArrayList<>(); /** * 获取分裂的配置文件 * * @param jsonArray */ private void getSplitConfigs(JsonArray jsonArray) { Type type = new TypeToken>() { }.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 */ private void readOBBConfig(JsonArray jsonArray) { if (TextUtils.isEmpty(unpackPath)) { Log.e(TAG, "readOBBConfig: " + "unpack directory is empty"); return; } Type type = new TypeToken>() { }.getType(); Gson gson = new Gson(); List 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"); } } } } 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; } } } /** * 获取分裂的apk文件 * * @param jsonArray */ private void getSplitApks(JsonArray jsonArray) { Type type = new TypeToken>() { }.getType(); Gson gson = new Gson(); List 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 configList, List apkList) { List 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, 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 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 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(); 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; } }