Files
MoGoAiCloudSdk/libraries/mogo-skin/src/main/java/com/mogo/skin/SkinManager.java
xyz 99a93c2f9f 「Fix Skin」
1、修复皮肤包多次下载同一个皮肤包导致下载的产物与云端产物md5不一致。
2024-12-24 18:16:18 +08:00

285 lines
12 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.mogo.skin;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.mogo.skin.net.DownloadManager;
import com.mogo.skin.utils.FileUtils;
import com.mogo.skin.utils.SkinPreference;
import com.mogo.skin.utils.SkinResources;
import com.mogo.skin.utils.SkinUtils;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Observable;
/**
* donghongyu
*/
public class SkinManager extends Observable {
private static String TAG = "SkinManager";
private static Application mApplication;
private static SkinManager instance;
/**
* 皮肤加载回调监听
*/
private SkinLoadListener mSkinLoadListener;
/**
* Activity生命周期回调
*/
private SkinActivityLifecycle skinActivityLifecycle;
private Application application;
/**
* 注意首次初始化时需要从配置文件读取使用哪个皮肤,然后应用
*
* @param application
*/
public static void init(Application application) {
synchronized (SkinManager.class) {
if (null == instance) {
Log.d(TAG, "SkinManager init …………");
mApplication = application;
instance = new SkinManager(application);
}
}
}
public static SkinManager getInstance() {
return instance;
}
private SkinManager(Application application) {
this.application = application;
//记录当前使用的皮肤
SkinPreference.init(application);
//资源管理类 用于从 app/皮肤 中加载资源
SkinResources.init(application);
//注册Activity生命周期,并设置被观察者
skinActivityLifecycle = new SkinActivityLifecycle();
application.registerActivityLifecycleCallbacks(skinActivityLifecycle);
// 加载皮肤如果为空则为APP内默认皮肤
loadSkin(SkinPreference.getInstance().getSkin());
}
/**
* 记载皮肤并应用
*
* @param skinPath 皮肤路径 如果为空则使用默认皮肤
*/
public void loadSkin(String skinPath) {
Log.d(TAG, "加载皮肤路径:" + skinPath);
if (TextUtils.isEmpty(skinPath)) {
Log.d(TAG, "皮肤包路径为空,还原默认皮肤包,开始还原换肤。");
//还原默认皮肤包
SkinPreference.getInstance().setSkin("");
SkinResources.getInstance().reset();
//应用皮肤包
setChanged();
//通知观察者
notifyObservers();
}
// 加载指定目录下的皮肤
else {
// 判断是否已经加载同样皮肤如果已经加载则不再重复加载防止因为重复加载导致ImageView之类的设置了src、background的控件被重制
try {
Log.d(TAG, "皮肤路径非空,开始换肤。");
//反射创建AssetManager 与 Resource
AssetManager assetManager = AssetManager.class.newInstance();
// 资源路径设置 目录或压缩包
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.setAccessible(true);
int result = (int) addAssetPath.invoke(assetManager, skinPath);
if (result == 0) {
Log.e(TAG, "加载皮肤APK失败" + skinPath);
} else {
Log.d(TAG, "加载皮肤APK成功" + skinPath);
// 获取当前Resources
Resources resources = application.getResources();
//根据当前的设备显示器信息 与 配置(横竖屏、语言等) 创建Resources
Resources skinResource = new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
// 获取外部Apk(皮肤包) 包名
PackageManager mPm = application.getPackageManager();
PackageInfo info = mPm.getPackageArchiveInfo(skinPath, PackageManager.GET_ACTIVITIES);
String packageName = info.packageName;
SkinResources.getInstance().applySkin(skinResource, packageName);
//保存当前使用的皮肤包
SkinPreference.getInstance().setSkin(skinPath);
//应用皮肤包
setChanged();
//通知观察者
notifyObservers();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void updateSkin(Activity activity) {
skinActivityLifecycle.updateSkin(activity);
}
/**
* 下载皮肤包
*/
public void downloadSkin(Context context, Skin skin, SkinLoadListener skinLoadListener) {
File theme = new File(FileUtils.getExternalAppDirectory(context), "theme");
if (theme.exists() && theme.isFile()) {
Log.w(TAG, "皮肤包 目录已存在,但是文件类型,删除。");
theme.delete();
theme.mkdirs();
}
if (!theme.exists()) {
Log.w(TAG, "皮肤包 存储目录不存在,创建 " + theme.getAbsolutePath());
theme.mkdirs();
}
// 打印文件夹内的文件
FileUtils.listFilesForFolder(theme);
// 获取皮肤插件文件对象
File skinFile = skin.getSkinFile(theme);
// 判断是否存在
if (FileUtils.fileIsExists(theme, skin.getSkinFileName())) {
// 检验皮肤包完整性即MD5是否一致不一致则删除
String localSkinFileMd5 = SkinUtils.getSkinMD5(skinFile);
if (!TextUtils.equals(localSkinFileMd5, skin.md5)) {
Log.w(TAG, "皮肤包 皮肤文件 MD5校验出错, 本地文件 MD5 = " + localSkinFileMd5 + " 与云端文件 MD5 = " + skin.md5 + " 不一致。删除本地皮肤包,重新下载。");
skinFile.delete();
} else {
Log.d(TAG, "皮肤包 皮肤文件 MD5校验成功, 本地文件 MD5 =" + localSkinFileMd5 + " 与云端文件 MD5 = " + skin.md5 + " 开始换肤……skin=" + skin);
SkinManager.getInstance().loadSkin(skin.getPathLocal());
return;
}
}
Log.w(TAG, "皮肤不存在,开始下载……skin=" + skin);
DownloadManager manager = getDownloadManager(skin, theme, skinLoadListener);
// 启动文件下载
manager.startDownload();
}
private static DownloadManager getDownloadManager(Skin skin, File themeDirectory, SkinLoadListener skinLoadListener) {
DownloadManager.DownloadListener downloadListener = new DownloadManager.DownloadListener() {
@Override
public void onProgressUpdate(int progress) {
// 更新UI或日志
Log.d(TAG, "皮肤包下载进度 onProgressUpdate progress = " + progress);
}
@Override
public void onDownloadSuccess() {
// 下载成功后的处理
Log.d(TAG, "皮肤包 下载成功 onDownloadSuccess");
File skinFile = skin.getSkinFile(themeDirectory);
try {
//下载成功将皮肤包信息insert已下载数据库
Log.d(TAG, "皮肤包下载完成开始校验……");
//皮肤包的md5校验 防止下载文件损坏(但是会减慢速度。从数据库查询已下载皮肤表数据库中保留md5字段)
String localSkinFileMd5 = SkinUtils.getSkinMD5(skinFile);
if (TextUtils.equals(localSkinFileMd5, skin.md5)) {
Log.d(TAG, "皮肤包 MD5 校验成功,本地文件 MD5 = " + localSkinFileMd5 + " 与云端文件 MD5 = " + skin.md5 + " ,删除其它皮肤包。");
// 加载指定皮肤包
SkinManager.getInstance().loadSkin(skin.getPathLocal());
// 校验文件签名没问题后删除其他主题
deleteFilesExcept(themeDirectory, skin.getSkinFileName());
// 回调给使用方成功
skinLoadListener.onLoadSuccess();
} else {
Log.e(TAG, "皮肤包 皮肤文件 MD5校验出错, 本地文件 MD5 = " + localSkinFileMd5 + " 与云端文件 MD5 = " + skin.md5 + " 不一致,删除皮肤包。");
Toast.makeText(mApplication, "皮肤文件校验出错,本地文件 MD5 = " + localSkinFileMd5 + " 与云端文件 MD5 = " + skin.md5 + " 不一致,删除皮肤包。", Toast.LENGTH_LONG).show();
// TODO 删除本地皮肤文件并重新下载这个操作比较危险需要严格保证配置文件中的md5与实际皮肤包一致否则将是灾难
skinFile.delete();
// 回调给使用方成功
skinLoadListener.onLoadFailed(new Exception("皮肤文件校验出错,本地文件MD5 与云端文件MD5 不一致。"));
}
} catch (Exception e) {
skinLoadListener.onLoadFailed(e);
e.printStackTrace();
}
}
@Override
public void onDownloadFailed(Exception e) {
// 下载失败后的处理
Log.e(TAG, "皮肤包下载出错 onDownloadFailed");
skinLoadListener.onLoadFailed(e);
e.printStackTrace();
}
@Override
public void onAlreadyDownloading(String url) {
// 处理重复下载的情况
Log.w(TAG, "皮肤包 onAlreadyDownloading url=" + url);
skinLoadListener.onLoadFailed(new Exception("DownloadManager onAlreadyDownloading url=" + url));
}
};
return new DownloadManager(skin.url, skin.getSkinFileName(), themeDirectory, downloadListener);
}
public static void deleteFilesExcept(File directory, String fileNameToKeep) {
if (directory == null || !directory.exists() || !directory.isDirectory()) {
Log.e(TAG, "Provided path is not a valid directory: " + directory);
}
// 获取目录下的所有文件和子目录
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
// 如果文件名不等于要保留的文件名,并且不是目录,则删除
if (!file.getName().equals(fileNameToKeep) && !file.isDirectory()) {
if (file.delete()) {
Log.d(TAG, "Deleted file=" + file.getAbsolutePath());
} else {
Log.e(TAG, "Failed to delete file=" + file.getAbsolutePath());
}
}
}
}
}
public static boolean renameFile(File oldFile, String newFilePath) {
File newFile = new File(newFilePath);
// 如果旧文件不存在,则直接返回 false
if (!oldFile.exists()) {
return false;
}
// 尝试重命名文件
return oldFile.renameTo(newFile);
}
/**
* 皮肤加载回调
*/
public interface SkinLoadListener {
void onLoadSuccess();
void onLoadFailed(Exception e);
}
}