Toast太丑,散落到各处无法统一更改,试试使用类加载器动态替换Toast.class

Android的提示主要使用Toast.makeText().show,方便又快捷,所以大多数时候我们都是在需要弹出对话框的地方直接这样时候。不过后来项目样式改版,发现Toast的提示方式不符合要求了。这个时候通常的做法,是对每一个地方进行替换,不过因为代码散布到各个地方,修改起来太复杂。今天实现另外一种实现,不仅仅可以用于Toast,这个一种思路,可以扩展到其他方面,比如全局关闭Log等。

首先讲解下Android的类加载机制,大体和Java保持一致,采用双亲委派。子类的加载任务先交给父类,父类找不到,再由子类加载。


  protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }


                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);


                    // this is the defining class loader; record the stats
                }
            }
            return c;
    }

以上是类加载器加载的具体细节,子类持有父类的引用,拿到请求后由父类进行加载,如果父类找不到调用自己的findClass进行加载。所以如果可以将自己定义的classLoader,插到系统classLoader的前面,我就可以拦截掉所有的加载,当然就可以中途将Toast给替换掉。


public class ToastInjection implements IInjection {

    @Override
    public void injection(ClassLoader classLoader) {
        ClassLoader userClassLoader = getUserClassLoader(classLoader);
        if (userClassLoader == null) {
            return;
        }
        HoldClassLoader holdClassLoader = new HoldClassLoader();
        replaceParentClassLoader(userClassLoader, holdClassLoader);
    }

    private  ClassLoader getUserClassLoader(ClassLoader classLoader) {
        ClassLoader parent = classLoader.getParent();
        while (parent != null && parent != ClassLoader.getSystemClassLoader().getParent()) {
            ClassLoader superParent = parent.getParent();
            if (superParent == ClassLoader.getSystemClassLoader().getParent()) {
                return parent;
            }
            parent = superParent;
        }

        if (parent == ClassLoader.getSystemClassLoader().getParent()) {
            return classLoader;
        }
        return null;
    }

    private void replaceParentClassLoader(ClassLoader mine, ClassLoader parent) {
        try {
            Field field = ClassLoader.class.getDeclaredField("parent");
            field.setAccessible(true);
            field.set(mine, parent);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}



注入代码,将自定义类加载器HoldClassLoader插到用户类加载器的后面,通常来说每个应用都至少会包裹三层类加载器,大家熟悉的热修复和Instant Run都是自己插入了一层类加载器。现在看下HoldClassLoader:


 class HoldClassLoader extends ClassLoader {

    public HoldClassLoader() {
        super(ClassLoader.getSystemClassLoader().getParent());
    }

    private static Class hodeToastClass = SuperToast.class;

    @Override
    protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        if(name.equals("android.widget.Toast")){
            return hodeToastClass;
        }
        if(name.equals("com.focustech.supertoast.SignToast")){
            name = "android.widget.Toast";
        }
        return super.loadClass(name, resolve);
    }

}


逻辑简单,当发现加载的是Toast.class 将会把提前加载好的SuperToast.class返回。这样我们用到的每一个Toast都将变成SuperToast,当正真需要使用Toast的时候将会出错。所以需要将Toast,转向为SignToast,这样Toast变成了 SuperToast,SignToast变成了Toast。多美好!!!

class SuperToast {

    private static IToastBehavior toastBehavior = new AlertDialogBeharior();

    static void setToastBehavior(IToastBehavior toastBehavior) {
        SuperToast.toastBehavior = toastBehavior;
    }

    public static Toast makeText(final Context context, final CharSequence text, final int duration) {
        SignToast aaaToast = new SignToast(context) {
            @Override
            public void show() {
                toastBehavior.show(context,text,duration);
            }
        };
        return aaaToast;
    }
}


SuperToast 的方法如下, makeText也只能这样写,否则会找不到方法。 SignToast 会转向为Toast,所以最后返回出去的是Toast,外界想要显示必定会调用show方法。这个时候复写方法,通过接口转接出去,将具体实现转移出去。默认有个 AlertDialogBeharior实现大家可以扩展,扩展通过如下类对外暴露:



public class Molina {

    private static Molina molina = new Molina();

    private IInjection injection;


    private Molina(){
        injection = new ToastInjection();
    }

    public static void injection(ClassLoader classLoader){
        molina.injection.injection(classLoader);
    }

    public static void replaceToastBehavior(IToastBehavior behavior){
        SuperToast.setToastBehavior(behavior);
    }

}

Molina 类,为外部使用的类,功能通过它暴露出去。在Application中onCreate中调用injection方法注入。通过replaceToastBehavior将Toast的具体实现替换。具体使用如下:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Molina.injection(getClassLoader());
	Molina.replaceToastBehavior(new IToastBehavior() {
            @Override
            public void show(Context context, CharSequence text, int duration) {
                Log.i("toast",text.toString());
            }
        });
    }
}
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void onClick(View view){
        Toast toast = Toast.makeText(this, "asdasd", Toast.LENGTH_SHORT);
        toast.show();
    }
}


 
  















你可能感兴趣的:(android,类加器,全局替换Toast)