AndFix的使用(詳解)

一、Andfix的使用範圍(與其他的比較)

圖片參考:http://m.blog.csdn.net/alpha58/article/details/74854680
也就是說AndFix存在以下的缺陷:
① 不支持YunOS
② 無法添加新類和新的字段
③ 需要使用加固前的apk製作補丁,但是補丁文件很容易被反編譯,也就是修改過的類源碼容易泄露。
④ 使用加固平臺可能會使熱補丁功能失效(看到有人在360加固提了這個問題,自己還未驗證)。
⑤ andfix不支持佈局資源等的修改
⑥ 官網:AndFix supports Android version from 2.3 to 7.0, both ARM and X86 architecture, both Dalvik and ART runtime, both 32bit and 64bit.
⑦ 應用patch不需要重啓。但由於從實現上直接跳過了類初始化,設置爲初始化完畢,所以像是靜態函數、靜態成員、構造函數都會出現問題,複雜點的類Class.forname很可能直接就會掛掉。
⑧ AndFix的一個潛在問題:
    加載一次補丁後,out.apatch文件會copy到getFilesDir目錄下的/apatch文件夾中,在下次補丁更新時,會檢測補丁是否已經添加在apatch文件夾下,已存在就不會複製加載sdcard的out.apatch。(後面會解決的)

二、自定義簽名
參考:http://blog.csdn.net/nimasike/article/details/51457229
三、集成AndFix
1.在app的build.gradle 添加
compile ‘com.alipay.euler:andfix:0.5.0’
2.所需要自定義一個PacthManger.
因爲上述存在的問題:加載一次補丁後,out.apatch文件會copy到getFilesDir目錄下的/apatch文件夾中,在下次補丁更新時,會檢測補丁是否已經添加在apatch文件夾下,已存在就不會複製加載sdcard的out.apatch。所需要自定義一個PacthManger.


public class MyPatchManager {
    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 MyPatchManager(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) {
        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() {
        File[] files = mPatchDir.listFiles();
        for (File file : files) {
            addPatch(file);
        }
    }

    /**
     * add patch file
     *
     * @param file
     * @return patch
     */
    private Patch addPatch(File file) {
        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() {
        File[] files = mPatchDir.listFiles();
        for (File file : files) {
            mAndFixManager.removeOptFile(file);
            if (!FileUtil.deleteFile(file)) {
                Log.e(TAG, file.getName() + " delete error.");
            }
        }
    }



    /**
     * remove all patchs
     */
    public void 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) {
        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() {
        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) {
        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);
            }
        }
    }


    public void addPatch(String path) throws IOException {
        /*
        *@Description :addPatch,重寫這個方法,那是因爲源碼中的addPatch()方法,
        * 在gradle裏導入andfix會有個問題,是在原來的項目中,加載一次補丁後,
        * out.apatch文件會copy到getFilesDir目錄下的/apatch文件夾中,
        * 在下次補丁更新時,會檢測補丁是否已經添加在apatch文件夾下,
        * 已存在就不會複製加載sdcard的out.apatch,
        * 所以我們需要對框架中patch文件下的PatchManager類中的addPatch()方法進行修改
        *@Author: gaogang6
        *@Date :  2017/8/29 15:34
        *@Params:  [path]
        *@Return: void
        */
        File src = new File(path);
        File dest = new File(mPatchDir, src.getName());
        if (!src.exists()) {
            throw new FileNotFoundException(path);
        }
        if (dest.exists()) {
            Log.d(TAG, "patch [" + src.getName() + "] has be loaded.");
            boolean deleteResult = dest.delete();
            if (deleteResult)
                Log.e(TAG, "patch [" + dest.getPath() + "] has be delete.");
            else {
                Log.e(TAG, "patch [" + dest.getPath() + "] delete error");
                return;
            }
        }
        FileUtil.copyFile(src, dest);// copy to patch's directory
        Patch patch = addPatch(dest);
        if (patch != null) {
            loadPatch(patch);
        }
    }
}

3、自定義Application


import java.io.File;
import java.io.IOException;

import android.app.Application;
import android.os.Environment;
import android.util.Log;

/**
 * sample application
 * 
 * @author [email protected]
 * 
 */
public class MainApplication extends Application {
    private static final String TAG = "euler";

    private static final String APATCH_PATH = "/out.apatch";//補丁的文件
    /**
     * patch manager
     */
    private MyPatchManager mPatchManager;

    @Override
    public void onCreate() {
        super.onCreate();
        // initialize
        mPatchManager = new MyPatchManager(this);
        mPatchManager.init("1.0");
        Log.d(TAG, "inited.");

        // load patch
        mPatchManager.loadPatch();
        Log.d(TAG, "apatch loaded.");

        // add patch at runtime
        try {
            // 自己在sdcard中存放.apatch文件的位置
            File file=new File(Environment.getExternalStorageDirectory().getAbsoluteFile()
                    +File.separator+"gaogang"+File.separator);
            if (!file.exists()){
                file.mkdir();
            }
            // 自己在sdcard中存放.apatch文件的位置
            String patchFileString = Environment.getExternalStorageDirectory()
                    .getAbsolutePath()+File.separator+"gaogang"+ APATCH_PATH;
            mPatchManager.addPatch(patchFileString);
            Log.d(TAG, "apatch:" + patchFileString + " added.");
        } catch (IOException e) {
            Log.e(TAG, "打補丁出錯了", e);
        }

    }
}

記住在AndroidManifest.xml文件中添加Application
四、如何使用
在實際中,.apatch文件最好是在Loading界面就通過網絡下載補丁文件,然後存儲到sdcard自己存放的那麼目錄下面。(Andoid6.0之後需要動態申請存儲權限)。
(1)首先:編輯一個
然後使用Build->Build APK。將apk的名字命名爲bug.apk
這裏寫圖片描述
再隨便修改一下
這裏寫圖片描述

同理將名字命名爲nobug.apk
(2)下載一個文件apkpatch.把之前生成的bug.apk和nobug.apk,還有打包所使用的keystore文件放到apkpatch-1.0.3目錄下
打開cmd,進入到apkpatch-1.0.3目錄下,輸入如下指令
apkpatch.bat -f nobug.apk -t bug.apk -o out -k andfix.jks -p 111111 -a gaogang -e 111111
每個參數含義如下
-f 新版本的apk
-t 舊版本的apk
-o 輸出apatch文件的文件夾,可以隨意命名
-k 打包的keystore文件名
-p keystore的密碼
-a keystore 用戶別名
-e keystore 用戶別名的密碼
這裏寫圖片描述

(3)安裝有bug.apk
(4)點擊顯示:

這裏寫圖片描述
(5)將out.apatch文件放入服務器
關閉了,再代開
這裏寫圖片描述

大兄弟,項目下載地址在這裏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章