ARouter系列3:繼續學習

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>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章