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

来源:互联网 发布:团队办公软件 编辑:程序博客网 时间:2024/04/30 13:22

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


















0 0
原创粉丝点击