Android 热修复案例

1.MainActivity

import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.xinrui.hotfix.utils.FixBug;
import com.xinrui.hotfix.utils.HotFix;

public class MainActivity extends Activity implements View.OnClickListener{
    private TextView fix_txt,show_txt;
    private FixBug mFixMe;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }
    public void init(){
        fix_txt=(TextView)findViewById(R.id.fixtxt);
        show_txt=(TextView)findViewById(R.id.show);
        fix_txt.setOnClickListener(this);
        show_txt.setOnClickListener(this);
        mFixMe = new FixBug(MainActivity.this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.fixtxt:
                checkFix();
                break;
            case R.id.show:
                mFixMe.showWhat();
                break;
        }
    }
    private void checkFix(){
        try {
            String dexPath = Environment.getExternalStorageDirectory() + "/classes2.dex";
            HotFix.fixDexFile(MainActivity.this, dexPath);
            Toast.makeText(MainActivity.this, "修复成功", Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            Toast.makeText(MainActivity.this, "修复失败" + e.getMessage(), Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }
}

2.activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:id="@+id/fixtxt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="25sp"
        android:text="修复"
        android:layout_marginTop="100px"
        android:layout_centerHorizontal="true"
        android:layout_alignParentTop="true"/>
    <TextView
        android:id="@+id/notgril"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Not Gril!"
        android:layout_centerInParent="true"
        android:visibility="gone"/>
    <ImageView
        android:id="@+id/gril"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:visibility="gone"/>
    <TextView
        android:id="@+id/show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="显示"
        android:textSize="25sp"
        android:layout_marginBottom="100px"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"/>
</RelativeLayout>

3.HotFix类

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;

import android.content.Context;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

/**
 * 作者:created by yufenfen on 2019/3/21:12:13
 * 邮箱: [email protected]
 */
public class HotFix {
    /**
     * 修复指定的类
     *
     * @param context        上下文对象
     * @param fixDexFilePath   修复的dex文件路径
     */
    public static void fixDexFile(Context context, String fixDexFilePath) {
        if (fixDexFilePath != null && new File(fixDexFilePath).exists()) {
            try {
                injectDexToClassLoader(context, fixDexFilePath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @param context
     * @param fixDexFilePath 修复文件的路径
     * @throws ClassNotFoundException
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static void injectDexToClassLoader(Context context, String fixDexFilePath)
            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        //读取 baseElements
        PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
        Object basePathList = getPathList(pathClassLoader);
        Object baseElements = getDexElements(basePathList);

        //读取 fixElements
        String baseDexAbsolutePath = context.getDir("dex", 0).getAbsolutePath();
        DexClassLoader fixDexClassLoader = new DexClassLoader(
                fixDexFilePath, baseDexAbsolutePath, fixDexFilePath, context.getClassLoader());
        Object fixPathList = getPathList(fixDexClassLoader);
        Object fixElements = getDexElements(fixPathList);

        //合并两份Elements
        Object newElements = combineArray(baseElements, fixElements);

        //一定要重新获取,不要用basePathList,会报错
        Object basePathList2 = getPathList(pathClassLoader);

        //新的dexElements对象重新设置回去
        setField(basePathList2, basePathList2.getClass(), "dexElements", newElements);
    }

    /**
     * 通过反射先获取到pathList对象
     *
     * @param obj
     * @return
     * @throws ClassNotFoundException
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static Object getPathList(Object obj) throws ClassNotFoundException, NoSuchFieldException,
            IllegalAccessException {
        return getField(obj, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList");
    }

    /**
     * 从上面获取到的PathList对象中,进一步反射获得dexElements对象
     *
     * @param obj
     * @return
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private static Object getDexElements(Object obj) throws NoSuchFieldException, IllegalAccessException {
        return getField(obj, obj.getClass(), "dexElements");
    }

    private static Object getField(Object obj, Class cls, String str)
            throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cls.getDeclaredField(str);
        declaredField.setAccessible(true);//设置为可访问
        return declaredField.get(obj);
    }

    private static void setField(Object obj, Class cls, String str, Object obj2)
            throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cls.getDeclaredField(str);
        declaredField.setAccessible(true);//设置为可访问
        declaredField.set(obj, obj2);
    }

    /**
     * 合拼dexElements ,并确保 fixElements 在 baseElements 之前
     *
     * @param baseElements
     * @param fixElements
     * @return
     */
    private static Object combineArray(Object baseElements, Object fixElements) {
        Class componentType = fixElements.getClass().getComponentType();
        int length = Array.getLength(fixElements);
        int length2 = Array.getLength(baseElements) + length;
        Object newInstance = Array.newInstance(componentType, length2);
        for (int i = 0; i < length2; i++) {
            if (i < length) {
                Array.set(newInstance, i, Array.get(fixElements, i));
            } else {
                Array.set(newInstance, i, Array.get(baseElements, i - length));
            }
        }
        return newInstance;
    }
}

4.FixBug 类

import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.xinrui.hotfix.R;

public class FixBug {
    private final String TAG = "FixBug";
    private ImageView mBelle;
    private TextView mNotShow;

    private Context mContext;

    //false: bug, true: fix
    private boolean fix = false;

    public FixBug(Activity context) {
        mContext = context;
        mBelle = (ImageView) context.findViewById(R.id.gril);
        mNotShow = (TextView) context.findViewById(R.id.notgril);
    }

    public void showWhat() {
        if (fix) {
            fixBug();
            Log.d(TAG, "fix bug!");
        } else {
            mBelle.setVisibility(View.GONE);
            mNotShow.setVisibility(View.VISIBLE);
            Log.d(TAG, "this is a bug!");
        }
    }

    private void fixBug() {
        try {
            mBelle.setVisibility(View.VISIBLE);
            mNotShow.setVisibility(View.GONE);
            mBelle.setBackgroundResource(R.drawable.timg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

你可能感兴趣的:(android)