285 lines
12 KiB
Java
285 lines
12 KiB
Java
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);
|
||
}
|
||
|
||
}
|