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); } }