ARouter系列3:继续学习(手写一个Arouter框架)

本文详细介绍了组件化开发中的路由框架ARouter,探讨了组件化的好处和路由框架的作用。通过实例,作者展示了如何创建项目架构,设计关键类如ARouterTest、IRouterTest、ARouterConstant,并利用APT技术在编译时自动生成类,最后指导读者如何手写并使用ARouter框架。
摘要由CSDN通过智能技术生成

0、相关资料

破解组件化开发的核心密码,窥探阿里ARouter组件化路由框架的原理

B站上的相关视频

目录:

1、什么是组件化?为什么要将项目进行组件化开发?

组件化架构:

好处:

但是模块化开发有一个很大的问题就是,没有依赖关系的两个模块之间无法实现界面(activity或fragment)的跳转,这个时候路由框架就要登场了。

2、组件化开发中路由框架究竟是什么?

简单来讲,路由框架的核心就是一张路由表,在这张路由表中保存了所有activity的类信息(不需要保存fragment的类信息,因为fragment都是依托于activity,跳转到activity后,就可以进入对应的fragment了。),通过路由表我们就可跳转到表中的任意一个activity中。而我们原来无法在没有依赖关系的两个模块之间实现跳转,就是因为我们无法获取跳转界面的类信息。现在有了路由框架,就可以解决这个问题了。

我们先来写个测试项目

2.1、项目架构

这个项目架构,一共四个module:app、login、usercenter、arouter

其中app依赖另外三个module

aroter被其他三个module依赖。

2.2、类

2.2.1、ARouterTest.java

/**
 * 中间人:路由表(所有activity的存储容器)
 * 1、单例模式
 * 2、用 map 存储activity信息
 *
 * @author songzi522
 */
public class ARouterTest {

    private volatile static ARouterTest aRouterTest;

    //路由表
    private Map<String, Class<? extends Activity>> map;

    // 上下文对象
    private Context context;

    public void init(Context context) {
        this.context = context;
    }

    private ARouterTest() {
        map = new HashMap<>();
    }

    public static ARouterTest getInstance() {
        if (aRouterTest == null) {
            synchronized (ARouterTest.class) {
                if (aRouterTest == null) {
                    aRouterTest = new ARouterTest();
                }
            }
        }
        return aRouterTest;
    }

    /**
     * 向路由表中添加信息
     *
     * @param key   键
     * @param clazz 值
     */
    public void addActivity(String key, Class<? extends Activity> clazz) {
        if (key != null && clazz != null && !map.containsKey(key)) {
            map.put(key, clazz);
        }
    }

    /**
     * 跳转窗体的方法
     *
     * @param key    键
     * @param bundle 值
     */
    public void jumpActivity(String key, Bundle bundle) {
        Class<? extends Activity> classActivity = map.get(key);
        if (classActivity != null) {
            Intent intent = new Intent(context, classActivity);
            if (bundle != null) {
                intent.putExtras(bundle);
            }
            context.startActivity(intent);
        }
    }

}

接下来,我们需要把每个module的activity类信息存储到ARouterTest中,写一个接口类。

2.2.2、IRouterTest.java

public interface IRouterTest {
    void putActivity();
}

2.2.3、ARouterConstant.java

public class ARouterConstant {
    public static final String AROUTER_PARAM_LOGIN_ACTIVITY = "login/LoginActivity";
    public static final String AROUTER_PARAM_UCMAIN_ACTIVITY = "uc/UCMainActivity";
}

2.2.4、ActivityUtil.java

public class ActivityUtil implements IRouterTest {
    @Override
    public void putActivity() {
        ARouterTest.getInstance().addActivity(ARouterConstant.AROUTER_PARAM_LOGIN_ACTIVITY, LoginActivity.class);
    }
}

但是这种手动添加的方式并不利于后期的维护。因此我们需要APT技术来帮助我们自动生成这些类。 

3、APT技术

【Android】APT

APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。
简单来说就是在编译期,通过注解生成.java文件。

APT技术的核心有两块:注解和注解处理器。

4、手写一个ARouter框架

接下来我们利用APT技术自己手写一个ARouter框架。

结构目录:

这个项目架构,一共四个android module:app、login、usercenter、arouter,其中app依赖另外三个module,arouter被其他三个module依赖。

apt-annotation 和 apt-processor 是java module,这两个module被app、login和usercenter依赖。如图:

    implementation project(path: ':apt-annotation')
    annotationProcessor project(path: ':apt-processor')

4.1、module:apt-annotation

该module只新建了一个注解类。

// 声明注解的作用域
@Target(ElementType.TYPE)
// 声明注解的生命周期
@Retention(RetentionPolicy.CLASS)
public @interface BindPath {
    String path();
}

4.2、module:apt-processor

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation project(path: ':apt-annotation')
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    compileOnly 'com.google.auto.service:auto-service:1.0-rc3'
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"
@AutoService(Processor.class)
public class Test1Processor extends AbstractProcessor {

    Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types=new HashSet<>();
        types.add(BindPath.class.getCanonicalName());
        return types;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 获取到当前模块中用到了BindPath注解的activity的类对象(类节点) (有几个模块中依赖了apt-processor,该方法就会执行几次)
        // 类节点 TypeElement 方法节点 ExecutableElement 方法节点 VariableElement
        Set<? extends Element> elementAnnotationWith = roundEnv.getElementsAnnotatedWith(BindPath.class);
        Map<String, String> map = new HashMap<>();
        // 遍历整个模块中用到了BindPath注解的节点
        for (Element element : elementAnnotationWith) {
            TypeElement typeElement = (TypeElement) element;
            // 获取到activity上面的 BindPath 的注解
            BindPath annotation = typeElement.getAnnotation(BindPath.class);
            // 获取到注解里面带的值 中间容器 map 的activity所对应的 key
            String key = annotation.path();
            // 获取到包名和类名
            Name activityName = typeElement.getQualifiedName();
            map.put(key, activityName + ".class");
        }
        // 写文件
        if (map.size() > 0) {
            Writer writer = null;
            // 需要生成的文件名 让类名不重复
            String activityName = "ActivityUtil" + System.currentTimeMillis();
            try {
                // 生成一个Java文件
                JavaFileObject sourceFile = filer.createSourceFile("com.gs.util." + activityName);
                // 从生成的这个文件开始写
                writer = sourceFile.openWriter();
                StringBuffer stringBuffer = new StringBuffer();
                stringBuffer.append("package com.gs.util;\n");
                stringBuffer.append("import com.gs.arouter.ARouterTest;\n" +
                        "import com.gs.arouter.IRouterTest;\n" +
                        "\n" +
                        "public class " + activityName + " implements IRouterTest{\n" +
                        "    @Override\n" +
                        "    public void putActivity(){\n");
                Iterator<String> iterator = map.keySet().iterator();
                while (iterator.hasNext()) {
                    String key = iterator.next();
                    String className = map.get(key);
                    stringBuffer.append("       ARouterTest.getInstance().addActivity(\"" + key + "\", " +
                            className + ");");
                }
                stringBuffer.append("\n  }\n}");
                writer.write(stringBuffer.toString());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        return false;
    }

}

这个很重要,不要搞错了。

4.3、module:arouter

4.3.1、IRouterTest.java

public interface IRouterTest {
    void putActivity();
}

4.3.2、ARouterConstant.java

public class ARouterConstant {
    public static final String AROUTER_PARAM_LOGIN_ACTIVITY = "login/LoginActivity";
    public static final String AROUTER_PARAM_UCMAIN_ACTIVITY = "uc/UCMainActivity";
}

4.3.3、 ARouterTest.java

public class ARouterTest {

    public volatile static ARouterTest aRouterTest;
    private Context context;
    private Map<String, Class<? extends Activity>> map;

    private ARouterTest() {
        map = new HashMap<>();
    }

    public static ARouterTest getInstance() {
        if (aRouterTest == null) {
            synchronized (ARouterTest.class) {
                if (aRouterTest == null) {
                    aRouterTest = new ARouterTest();
                }
            }
        }
        return aRouterTest;
    }

    public void init(Context context) {
        this.context = context;
        List<String> classNames = getClassName("com.gs.util");
        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                if (IRouterTest.class.isAssignableFrom(clazz)) {
                    IRouterTest iRouterTest = (IRouterTest) clazz.newInstance();
                    iRouterTest.putActivity();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void addActivity(String key, Class<? extends Activity> clazz) {
        if (key != null && clazz != null && !map.containsKey(key)) {
            map.put(key, clazz);
        }
    }

    public void jumpActivity(String key, Bundle bundle) {
        Class<? extends Activity> classActivity = map.get(key);
        if (classActivity != null) {
            Intent intent = new Intent(context, classActivity);
            if (bundle != null) {
                intent.putExtras(bundle);
            }
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(intent);
        }
    }

    /**
     * 通过包名获取这个包下面的所有类名
     *
     * @param packageName 包名
     * @return a list of class
     */
    private List<String> getClassName(String packageName) {
        List<String> classList = new ArrayList<>();
        String path = null;
        try {
            path = context.getPackageManager()
                    .getApplicationInfo(context.getPackageName(), 0)
                    .sourceDir;
            DexFile dexFile = new DexFile(path);
            Enumeration entries = dexFile.entries();
            while (entries.hasMoreElements()) {
                String name = (String) entries.nextElement();
                if (name.contains(packageName)) {
                    classList.add(name);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classList;
    }


}

4.4、使用

4.4.1、module:app

App.java 

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ARouterTest.getInstance().init(this);
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

        findViewById(R.id.btn1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ARouterTest.getInstance().jumpActivity(ARouterConstant.AROUTER_PARAM_LOGIN_ACTIVITY, null);
            }
        });
    }
}

 activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn1"
        style="@style/BtnCommon"
        android:text="1、跳转到LoginActivity" />

</LinearLayout>

4.4.2、module:login

LoginActivity.java

@BindPath(path = ARouterConstant.AROUTER_PARAM_LOGIN_ACTIVITY)
public class LoginActivity extends AppCompatActivity {

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

        findViewById(R.id.btn1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ARouterTest.getInstance()
                        .jumpActivity(ARouterConstant.AROUTER_PARAM_UCMAIN_ACTIVITY, null);
            }
        });
    }
}

activity_login.xml 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is LoginActivity."
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="跳转到 UCMainActivity"
        android:textAllCaps="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.418" />

</androidx.constraintlayout.widget.ConstraintLayout>

4.4.3、module:usercenter

UCMainActivity.java

@BindPath(path = ARouterConstant.AROUTER_PARAM_UCMAIN_ACTIVITY)
public class UCMainActivity extends AppCompatActivity {

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

activity_u_c_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".UCMainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is UCMainActivity."
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值