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