/* * * Copyright (c) 2015, alipay.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alipay.euler.andfix.patch; import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; import com.alipay.euler.andfix.AndFixManager; import com.alipay.euler.andfix.util.FileUtil; /** * patch manager * * @author sanping.li@alipay.com * */ public class PatchManager { private static final String TAG = "PatchManager"; // patch extension private static final String SUFFIX = ".apatch"; private static final String DIR = "apatch"; private static final String SP_NAME = "_andfix_"; private static final String SP_VERSION = "version"; /** * context */ private final Context mContext; /** * AndFix manager */ private final AndFixManager mAndFixManager; /** * patch directory */ private final File mPatchDir; /** * patchs */ private final SortedSet<Patch> mPatchs; /** * classloaders */ private final Map<String, ClassLoader> mLoaders; /** * @param context * context */ public PatchManager(Context context) { mContext = context; mAndFixManager = new AndFixManager(mContext); mPatchDir = new File(mContext.getFilesDir(), DIR); mPatchs = new ConcurrentSkipListSet<Patch>(); mLoaders = new ConcurrentHashMap<String, ClassLoader>(); } /** * initialize * * @param appVersion * App version */ public void init(String appVersion) { Log.d("euler", "PatchManager, init"); if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {// make directory fail Log.e(TAG, "patch dir create error."); return; } else if (!mPatchDir.isDirectory()) {// not directory mPatchDir.delete(); return; } SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); String ver = sp.getString(SP_VERSION, null); if (ver == null || !ver.equalsIgnoreCase(appVersion)) { cleanPatch(); sp.edit().putString(SP_VERSION, appVersion).commit(); } else { initPatchs(); } } private void initPatchs() { Log.d("euler", "PatchManager, initPatchs"); File[] files = mPatchDir.listFiles(); for (File file : files) { addPatch(file); } } /** * add patch file * * @param file * @return patch */ private Patch addPatch(File file) { Log.d("euler", "PatchManager, addPatch"); Patch patch = null; if (file.getName().endsWith(SUFFIX)) { try { patch = new Patch(file); mPatchs.add(patch); } catch (IOException e) { Log.e(TAG, "addPatch", e); } } return patch; } private void cleanPatch() { Log.d("euler", "PatchManager, cleanPatch"); File[] files = mPatchDir.listFiles(); for (File file : files) { mAndFixManager.removeOptFile(file); if (!FileUtil.deleteFile(file)) { Log.e(TAG, file.getName() + " delete error."); } } } /** * add patch at runtime * * @param path * patch path * @throws IOException */ public void addPatch(String path) throws IOException { Log.d("euler", "PatchManager, addPatch2"); File src = new File(path); File dest = new File(mPatchDir, src.getName()); if (dest.exists()) { mAndFixManager.removeOptFile(dest); } FileUtil.copyFile(src, dest);// copy to patch's directory Patch patch = addPatch(dest); if (patch != null) { loadPatch(patch); } } /** * remove all patchs */ public void removeAllPatch() { Log.d("euler", "PatchManager, removeAllPatch"); cleanPatch(); SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); sp.edit().clear().commit(); } /** * load patch,call when plugin be loaded. used for plugin architecture.</br> * * need name and classloader of the plugin * * @param patchName * patch name * @param classLoader * classloader */ public void loadPatch(String patchName, ClassLoader classLoader) { Log.d("euler", "PatchManager, loadPatch"); mLoaders.put(patchName, classLoader); Set<String> patchNames; List<String> classes; for (Patch patch : mPatchs) { patchNames = patch.getPatchNames(); if (patchNames.contains(patchName)) { classes = patch.getClasses(patchName); mAndFixManager.fix(patch.getFile(), classLoader, classes); } } } /** * load patch,call when application start * */ public void loadPatch() { Log.d("euler", "PatchManager, loadPatch2"); mLoaders.put("*", mContext.getClassLoader());// wildcard Set<String> patchNames; List<String> classes; for (Patch patch : mPatchs) { patchNames = patch.getPatchNames(); for (String patchName : patchNames) { classes = patch.getClasses(patchName); mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(), classes); } } } /** * load specific patch * * @param patch * patch */ private void loadPatch(Patch patch) { Log.d("euler", "PatchManager, loadPatch3"); Set<String> patchNames = patch.getPatchNames(); ClassLoader cl; List<String> classes; for (String patchName : patchNames) { if (mLoaders.containsKey("*")) { cl = mContext.getClassLoader(); } else { cl = mLoaders.get(patchName); } if (cl != null) { classes = patch.getClasses(patchName); mAndFixManager.fix(patch.getFile(), cl, classes); } } } }