├── router
├── .gitignore
├── src
│ └── main
│ │ ├── java
│ │ └── com
│ │ │ └── chenenyu
│ │ │ └── router
│ │ │ ├── RouteStatus.java
│ │ │ ├── template
│ │ │ ├── ParamInjector.java
│ │ │ ├── InterceptorTable.java
│ │ │ ├── RouteTable.java
│ │ │ └── TargetInterceptorsTable.java
│ │ │ ├── RouteCallback.java
│ │ │ ├── matcher
│ │ │ ├── AbsImplicitMatcher.java
│ │ │ ├── DirectMatcher.java
│ │ │ ├── BrowserMatcher.java
│ │ │ ├── package-info.java
│ │ │ ├── AbsExplicitMatcher.java
│ │ │ ├── Matcher.java
│ │ │ ├── ImplicitMatcher.java
│ │ │ ├── AbsMatcher.java
│ │ │ └── SchemeMatcher.java
│ │ │ ├── chain
│ │ │ ├── package-info.java
│ │ │ ├── FragmentValidator.java
│ │ │ ├── IntentValidator.java
│ │ │ ├── BaseValidator.java
│ │ │ ├── AttrsProcessor.java
│ │ │ ├── FragmentProcessor.java
│ │ │ ├── AppInterceptorsHandler.java
│ │ │ └── IntentProcessor.java
│ │ │ ├── util
│ │ │ └── RLog.java
│ │ │ ├── RouteResponse.java
│ │ │ ├── RouteInterceptor.java
│ │ │ ├── Router.java
│ │ │ ├── AptHub.java
│ │ │ ├── MatcherRegistry.java
│ │ │ ├── RealInterceptorChain.java
│ │ │ ├── IRouter.java
│ │ │ ├── RealRouter.java
│ │ │ ├── RouterInitializer.java
│ │ │ ├── RouteRequest.java
│ │ │ └── AbsRouter.java
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── Sample
├── app
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values-zh
│ │ │ │ └── strings.xml
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ └── layout
│ │ │ │ ├── activity_intercepted.xml
│ │ │ │ ├── activity_test.xml
│ │ │ │ ├── activity_dynamic.xml
│ │ │ │ ├── activity_for_result.xml
│ │ │ │ ├── activity_scheme.xml
│ │ │ │ └── activity_main.xml
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── chenenyu
│ │ │ │ └── router
│ │ │ │ └── app
│ │ │ │ ├── Model.java
│ │ │ │ ├── App.java
│ │ │ │ ├── DynamicActivity.java
│ │ │ │ ├── InterceptedActivity.java
│ │ │ │ ├── WebActivity.java
│ │ │ │ ├── GlobalInterceptor.java
│ │ │ │ ├── BInterceptor.java
│ │ │ │ ├── AInterceptor.java
│ │ │ │ ├── ImplicitActivity.java
│ │ │ │ ├── SchemeFilterActivity.java
│ │ │ │ ├── ForResultActivity.java
│ │ │ │ ├── TestActivity.java
│ │ │ │ └── MainActivity.java
│ │ │ ├── assets
│ │ │ └── scheme.html
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle
│ └── proguard-rules.pro
├── module1
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ ├── res
│ │ │ ├── values
│ │ │ │ └── strings.xml
│ │ │ └── layout
│ │ │ │ ├── activity_module1.xml
│ │ │ │ └── fragment_module1.xml
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── chenenyu
│ │ │ │ └── router
│ │ │ │ └── module
│ │ │ │ └── Module1Activity.kt
│ │ │ └── java
│ │ │ └── com
│ │ │ └── chenenyu
│ │ │ └── router
│ │ │ └── module
│ │ │ └── Module1Fragment.java
│ ├── proguard-rules.pro
│ └── build.gradle
└── module2
│ ├── .gitignore
│ ├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ └── strings.xml
│ │ └── layout
│ │ │ ├── activity_module2.xml
│ │ │ └── fragment_module2.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── chenenyu
│ │ └── router
│ │ └── module
│ │ ├── Module2Activity.java
│ │ └── Module2Fragment.java
│ ├── proguard-rules.pro
│ └── build.gradle
├── annotation
├── .gitignore
├── build.gradle
└── src
│ └── main
│ └── java
│ └── com
│ └── chenenyu
│ └── router
│ └── annotation
│ ├── Interceptor.java
│ ├── InjectParam.java
│ └── Route.java
├── compiler
├── .gitignore
├── src
│ └── main
│ │ ├── resources
│ │ └── META-INF
│ │ │ ├── services
│ │ │ └── javax.annotation.processing.Processor
│ │ │ └── gradle
│ │ │ └── incremental.annotation.processors
│ │ └── java
│ │ └── com
│ │ └── chenenyu
│ │ └── router
│ │ └── compiler
│ │ ├── util
│ │ ├── Logger.java
│ │ └── Constants.java
│ │ └── processor
│ │ └── InterceptorProcessor.java
└── build.gradle
├── router-plugin
├── .gitignore
├── README.md
├── settings.gradle.kts
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── l3gacy
│ │ └── plugin
│ │ ├── router
│ │ ├── asm
│ │ │ ├── RouterClassVisitor.kt
│ │ │ └── RouterMethodVisitor.kt
│ │ ├── internal
│ │ │ └── Extensions.kt
│ │ ├── RouterPlugin.kt
│ │ └── task
│ │ │ └── RouterClassesTask.kt
│ │ └── internal
│ │ ├── Stopwatch.kt
│ │ ├── Extensions.kt
│ │ └── Log.kt
└── build.gradle.kts
├── gradle-plugin
├── .gitignore
├── src
│ └── main
│ │ ├── resources
│ │ └── META-INF
│ │ │ └── gradle-plugins
│ │ │ └── com.chenenyu.router.properties
│ │ └── groovy
│ │ └── com
│ │ └── chenenyu
│ │ └── router
│ │ ├── ManifestTransformerTask.groovy
│ │ └── ManifestTransformer.groovy
└── build.gradle
├── static
├── screenshot.gif
└── donate_wechat.png
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── publish.gradle
├── .gitignore
├── settings.gradle
├── LICENSE
├── release.sh
├── gradle.properties
├── .travis.yml
├── gradlew.bat
├── README.md
└── gradlew
/router/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Sample/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/annotation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/compiler/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/router-plugin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/router-plugin/README.md:
--------------------------------------------------------------------------------
1 | ## 路由插件工具
--------------------------------------------------------------------------------
/router-plugin/settings.gradle.kts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Sample/module1/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Sample/module2/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/gradle-plugin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/static/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/static/screenshot.gif
--------------------------------------------------------------------------------
/static/donate_wechat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/static/donate_wechat.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Sample/module1/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | module1
3 |
4 |
--------------------------------------------------------------------------------
/Sample/module2/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | module2
3 |
4 |
--------------------------------------------------------------------------------
/gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.chenenyu.router.properties:
--------------------------------------------------------------------------------
1 | implementation-class=com.chenenyu.router.RouterPlugin
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/Sample/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/Sample/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/Sample/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/Sample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/Sample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .gradle
3 | local.properties
4 | .idea
5 | build
6 | *.iml
7 | /captures
8 | .externalNativeBuild
9 |
10 | #publish.gradle
11 | /repo
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/Sample/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/Sample/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/Sample/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/Sample/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/5peak2me/Router/HEAD/Sample/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Sample/app/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 跳转到 %s
4 | 动态路由
5 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Router
3 | go to %s
4 | dynamic route
5 |
6 |
--------------------------------------------------------------------------------
/compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor:
--------------------------------------------------------------------------------
1 | com.chenenyu.router.compiler.processor.RouteProcessor
2 | com.chenenyu.router.compiler.processor.InterceptorProcessor
3 | com.chenenyu.router.compiler.processor.InjectParamProcessor
--------------------------------------------------------------------------------
/Sample/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/compiler/src/main/resources/META-INF/gradle/incremental.annotation.processors:
--------------------------------------------------------------------------------
1 | com.chenenyu.router.compiler.processor.RouteProcessor,aggregating
2 | com.chenenyu.router.compiler.processor.InterceptorProcessor,aggregating
3 | com.chenenyu.router.compiler.processor.InjectParamProcessor,aggregating
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jul 12 18:00:44 CST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/Model.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Test model.
7 | *
8 | * Created by Enyu Chen on 2017/6/28.
9 | */
10 | public class Model implements Serializable {
11 | // empty
12 | }
13 |
--------------------------------------------------------------------------------
/annotation/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | sourceCompatibility = JavaVersion.VERSION_1_8
4 | targetCompatibility = JavaVersion.VERSION_1_8
5 |
6 | ext {
7 | GROUP = 'com.chenenyu.router'
8 | ARTIFACT = 'annotation'
9 | VERSION = ANNOTATION_VERSION
10 | }
11 |
12 | //apply from: '../gradle/publish.gradle'
13 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | // core
2 | include ':router', ':compiler', ':annotation'/*, ':gradle-plugin'*/
3 | // demo
4 | include ':app', ':module1', ':module2'
5 | project(':app').projectDir = new File("Sample/app")
6 | project(':module1').projectDir = new File("Sample/module1")
7 | project(':module2').projectDir = new File("Sample/module2")
8 |
9 | includeBuild("router-plugin")
10 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/RouteStatus.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | /**
4 | * Result for each route.
5 | *
6 | * Created by chenenyu on 2017/3/9.
7 | */
8 | public enum RouteStatus {
9 | PROCESSING,
10 | SUCCEED,
11 | INTERCEPTED,
12 | NOT_FOUND,
13 | FAILED;
14 |
15 | public boolean isSuccessful() {
16 | return this == SUCCEED;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/template/ParamInjector.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.template;
2 |
3 | /**
4 | * Interface that help to generate param class.
5 | *
6 | * Created by chenenyu on 2017/6/15.
7 | */
8 | public interface ParamInjector {
9 | /**
10 | * Inject params.
11 | *
12 | * @param obj Activity or fragment instance.
13 | */
14 | void inject(Object obj);
15 | }
16 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/App.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.app.Application;
4 |
5 | import com.chenenyu.router.util.RLog;
6 |
7 | /**
8 | *
9 | * Created by Cheney on 2017/1/12.
10 | */
11 | public class App extends Application {
12 | @Override
13 | public void onCreate() {
14 | super.onCreate();
15 |
16 | RLog.showLog(true);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Sample/module1/src/main/res/layout/activity_module1.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/Sample/module2/src/main/res/layout/activity_module2.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/DynamicActivity.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 |
7 | public class DynamicActivity extends AppCompatActivity {
8 |
9 | @Override
10 | protected void onCreate(Bundle savedInstanceState) {
11 | super.onCreate(savedInstanceState);
12 | setContentView(R.layout.activity_dynamic);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Sample/module1/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Sample/module2/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/RouteCallback.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import android.net.Uri;
4 |
5 | import java.io.Serializable;
6 |
7 | /**
8 | *
9 | * Created by chenenyu on 2016/12/20.
10 | */
11 | public interface RouteCallback extends Serializable {
12 | /**
13 | * Callback
14 | *
15 | * @param status {@link RouteStatus}
16 | * @param uri uri
17 | * @param message notice msg
18 | */
19 | void callback(RouteStatus status, Uri uri, String message);
20 | }
21 |
--------------------------------------------------------------------------------
/annotation/src/main/java/com/chenenyu/router/annotation/Interceptor.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Annotation for interceptor class.
10 | *
11 | * Created by Cheney on 2017/3/6.
12 | */
13 | @Target(ElementType.TYPE)
14 | @Retention(RetentionPolicy.CLASS)
15 | public @interface Interceptor {
16 | String value();
17 | }
18 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/template/InterceptorTable.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.template;
2 |
3 | import com.chenenyu.router.RouteInterceptor;
4 |
5 | import java.util.Map;
6 |
7 | /**
8 | * Interceptor table mapping.
9 | *
10 | * Created by chenenyu on 2017/6/30.
11 | */
12 | public interface InterceptorTable {
13 | /**
14 | * Mapping between name and interceptor.
15 | *
16 | * @param map name -> interceptor.
17 | */
18 | void handle(Map> map);
19 | }
20 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/template/RouteTable.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.template;
2 |
3 | import java.util.Map;
4 |
5 | /**
6 | * Route table mapping.
7 | *
8 | * Created by chenenyu on 2016/12/22.
9 | */
10 | public interface RouteTable {
11 | /**
12 | * Mapping between uri and target, the target class may be an {@link android.app.Activity},
13 | * or {@link androidx.fragment.app.Fragment}.
14 | *
15 | * @param map uri -> target.
16 | */
17 | void handle(Map> map);
18 | }
19 |
--------------------------------------------------------------------------------
/annotation/src/main/java/com/chenenyu/router/annotation/InjectParam.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Annotation for injected params.
10 | *
11 | * Created by chenenyu on 2017/6/12.
12 | */
13 | @Target({ElementType.FIELD})
14 | @Retention(RetentionPolicy.CLASS)
15 | public @interface InjectParam {
16 | String key() default "";
17 | }
18 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/layout/activity_intercepted.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/annotation/src/main/java/com/chenenyu/router/annotation/Route.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Annotation for route.
10 | *
11 | * Created by chenenyu on 2016/12/20.
12 | */
13 | @Target({ElementType.TYPE})
14 | @Retention(RetentionPolicy.CLASS)
15 | public @interface Route {
16 | String[] value();
17 |
18 | String[] interceptors() default {};
19 | }
20 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/layout/activity_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/template/TargetInterceptorsTable.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.template;
2 |
3 | import java.util.Map;
4 |
5 | /**
6 | * Target interceptor mapping.
7 | *
8 | * Created by chenenyu on 2017/6/29.
9 | */
10 | public interface TargetInterceptorsTable {
11 | /**
12 | * Mapping between target and interceptors, the target class may be an {@link android.app.Activity},
13 | * or {@link androidx.fragment.app.Fragment}.
14 | *
15 | * @param map target -> interceptors array.
16 | */
17 | void handle(Map, String[]> map);
18 | }
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright {2019} {chenenyu}
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/gradle-plugin/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'groovy'
2 |
3 | sourceCompatibility = JavaVersion.VERSION_1_8
4 | targetCompatibility = JavaVersion.VERSION_1_8
5 |
6 | repositories {
7 | google()
8 | }
9 |
10 | dependencies {
11 | implementation gradleApi()
12 | implementation localGroovy()
13 | compileOnly 'com.android.tools.build:gradle:7.2.1'
14 | compileOnly 'com.android.tools:common:30.2.1'
15 | compileOnly 'com.android.tools:repository:30.2.1'
16 | }
17 |
18 | ext {
19 | GROUP = 'com.chenenyu.router'
20 | ARTIFACT = 'gradle-plugin'
21 | VERSION = PLUGIN_VERSION
22 | }
23 |
24 | apply from: '../gradle/publish.gradle'
--------------------------------------------------------------------------------
/Sample/app/src/main/res/layout/activity_dynamic.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
--------------------------------------------------------------------------------
/compiler/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | tasks.withType(JavaCompile) {
4 | options.encoding = "UTF-8"
5 | }
6 |
7 | sourceCompatibility = JavaVersion.VERSION_1_8
8 | targetCompatibility = JavaVersion.VERSION_1_8
9 |
10 | dependencies {
11 | implementation fileTree(include: ['*.jar'], dir: 'libs')
12 | implementation 'com.squareup:javapoet:1.13.0'
13 | // implementation project(':annotation')
14 | implementation "com.chenenyu.router:annotation:${ANNOTATION_VERSION}"
15 | }
16 |
17 | ext {
18 | GROUP = 'com.chenenyu.router'
19 | ARTIFACT = 'compiler'
20 | VERSION = COMPILER_VERSION
21 | }
22 |
23 | //apply from: '../gradle/publish.gradle'
24 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/InterceptedActivity.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 |
7 | import com.chenenyu.router.annotation.Route;
8 |
9 | @Route(value = "intercepted", interceptors = {"AInterceptor", "BInterceptor"})
10 | //@Route(value = "intercepted", interceptors = {"BInterceptor", "AInterceptor"})
11 | public class InterceptedActivity extends AppCompatActivity {
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_intercepted);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | # annotation
2 | ./gradlew clean -p annotation
3 | ./gradlew publishReleasePublicationToMavenRepository -p annotation
4 |
5 | # compiler
6 | ./gradlew clean -p compiler
7 | #./gradlew publishReleasePublicationToMavenLocal -p compiler # for test
8 | ./gradlew publishReleasePublicationToMavenRepository -p compiler
9 |
10 | # router
11 | ./gradlew clean -p router
12 | #./gradlew publishReleasePublicationToMavenLocal -p router # for test
13 | ./gradlew publishReleasePublicationToMavenRepository -p router
14 |
15 | # gradle-plugin
16 | ./gradlew clean -p gradle-plugin
17 | #./gradlew publishReleasePublicationToMavenLocal -p gradle-plugin # for test
18 | ./gradlew publishReleasePublicationToMavenRepository -p gradle-plugin
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/matcher/AbsImplicitMatcher.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.matcher;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 |
7 | import androidx.annotation.Nullable;
8 |
9 | /**
10 | * Base mather for implicit intent.
11 | *
12 | * Created by chenenyu on 2017/3/12.
13 | */
14 | public abstract class AbsImplicitMatcher extends AbsMatcher {
15 |
16 | public AbsImplicitMatcher(int priority) {
17 | super(priority);
18 | }
19 |
20 | @Override
21 | public Object generate(Context context, Uri uri, @Nullable Class> target) {
22 | return new Intent(Intent.ACTION_VIEW, uri);
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/router/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
12 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/matcher/DirectMatcher.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.matcher;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 |
6 | import androidx.annotation.Nullable;
7 |
8 | import com.chenenyu.router.RouteRequest;
9 |
10 | /**
11 | * Absolutely matcher.
12 | *
13 | * Created by chenenyu on 2016/12/23.
14 | */
15 | public class DirectMatcher extends AbsExplicitMatcher {
16 |
17 | public DirectMatcher(int priority) {
18 | super(priority);
19 | }
20 |
21 | @Override
22 | public boolean match(Context context, Uri uri, @Nullable String route, RouteRequest routeRequest) {
23 | return !isEmpty(route) && uri.toString().equals(route);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/WebActivity.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.os.Bundle;
4 | import android.webkit.WebView;
5 |
6 | import androidx.appcompat.app.AppCompatActivity;
7 |
8 | public class WebActivity extends AppCompatActivity {
9 | WebView mWebView;
10 |
11 | @Override
12 | protected void onCreate(Bundle savedInstanceState) {
13 | super.onCreate(savedInstanceState);
14 |
15 | mWebView = new WebView(this);
16 | setContentView(mWebView);
17 | mWebView.loadUrl("file:///android_asset/scheme.html");
18 | }
19 |
20 | @Override
21 | protected void onDestroy() {
22 | super.onDestroy();
23 | mWebView.destroy();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/GlobalInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.util.Log;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import com.chenenyu.router.RouteInterceptor;
8 | import com.chenenyu.router.RouteResponse;
9 |
10 | /**
11 | * Global interceptor.
12 | *
13 | * Created by chenenyu on 2017/9/11.
14 | */
15 | public class GlobalInterceptor implements RouteInterceptor {
16 | @NonNull
17 | @Override
18 | public RouteResponse intercept(Chain chain) {
19 | Log.d("GlobalInterceptor", String.format("{uri: %s, interceptor: %s}",
20 | chain.getRequest().getUri().toString(), GlobalInterceptor.class.getName()));
21 | return chain.process();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sample/app/src/main/assets/scheme.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 | Router
8 |
9 |
10 | This's a WebView.
11 |
25 |
26 |
--------------------------------------------------------------------------------
/Sample/module1/src/main/res/layout/fragment_module1.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
12 |
13 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Sample/module2/src/main/res/layout/fragment_module2.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
12 |
13 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/chain/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Intent:
3 | * {@link com.chenenyu.router.chain.BaseValidator} ->
4 | * {@link com.chenenyu.router.chain.IntentValidator} ->
5 | * {@link com.chenenyu.router.chain.IntentProcessor} ->
6 | * {@link com.chenenyu.router.chain.AppInterceptorsHandler}
7 | * {@link com.chenenyu.router.chain.AttrsProcessor}
8 | *
9 | *
10 | *
11 | * Fragment:
12 | * {@link com.chenenyu.router.chain.BaseValidator} ->
13 | * {@link com.chenenyu.router.chain.FragmentValidator} ->
14 | * {@link com.chenenyu.router.chain.FragmentProcessor} ->
15 | * {@link com.chenenyu.router.chain.AppInterceptorsHandler}
16 | * {@link com.chenenyu.router.chain.AttrsProcessor}
17 | *
18 | * @since 1.5.0
19 | */
20 | package com.chenenyu.router.chain;
21 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/layout/activity_for_result.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/BInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.widget.Toast;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import com.chenenyu.router.RouteInterceptor;
8 | import com.chenenyu.router.RouteResponse;
9 | import com.chenenyu.router.annotation.Interceptor;
10 |
11 |
12 | /**
13 | * Created by chenenyu on 2018/5/18.
14 | */
15 | @Interceptor("BInterceptor")
16 | public class BInterceptor implements RouteInterceptor {
17 | @NonNull
18 | @Override
19 | public RouteResponse intercept(Chain chain) {
20 | Toast.makeText(chain.getContext(), String.format("Intercepted: {uri: %s, interceptor: %s}",
21 | chain.getRequest().getUri().toString(), BInterceptor.class.getName()),
22 | Toast.LENGTH_LONG).show();
23 | return chain.intercept();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/matcher/BrowserMatcher.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.matcher;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 |
6 | import androidx.annotation.Nullable;
7 |
8 | import com.chenenyu.router.RouteRequest;
9 |
10 | /**
11 | * This matcher will generate an intent with an {@link android.content.Intent#ACTION_VIEW} action
12 | * and open a browser.
13 | *
14 | * Created by chenenyu on 2017/1/5.
15 | */
16 | public class BrowserMatcher extends AbsImplicitMatcher {
17 | public BrowserMatcher(int priority) {
18 | super(priority);
19 | }
20 |
21 | @Override
22 | public boolean match(Context context, Uri uri, @Nullable String route, RouteRequest routeRequest) {
23 | return (uri.toString().toLowerCase().startsWith("http://")
24 | || uri.toString().toLowerCase().startsWith("https://"));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/AInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.widget.Toast;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import com.chenenyu.router.RouteInterceptor;
8 | import com.chenenyu.router.RouteResponse;
9 | import com.chenenyu.router.annotation.Interceptor;
10 |
11 | /**
12 | * 自定义拦截器,通过注解指定name,就可以在Route中引用
13 | *
14 | * Created by Cheney on 2017/3/6.
15 | */
16 | @Interceptor("AInterceptor")
17 | public class AInterceptor implements RouteInterceptor {
18 | @NonNull
19 | @Override
20 | public RouteResponse intercept(Chain chain) {
21 | Toast.makeText(chain.getContext(), String.format("Intercepted: {uri: %s, interceptor: %s}",
22 | chain.getRequest().getUri().toString(), AInterceptor.class.getName()),
23 | Toast.LENGTH_LONG).show();
24 | return chain.intercept();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/matcher/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * {@link com.chenenyu.router.matcher.AbsExplicitMatcher}:
3 | *
4 | * - {@link com.chenenyu.router.matcher.DirectMatcher}
5 | * - {@link com.chenenyu.router.matcher.SchemeMatcher}
6 | *
7 | * {@link com.chenenyu.router.matcher.AbsImplicitMatcher}:
8 | *
9 | * - {@link com.chenenyu.router.matcher.ImplicitMatcher}
10 | * - {@link com.chenenyu.router.matcher.BrowserMatcher}
11 | *
12 | *
13 | * Default matcher priority in router:
14 | * {@link com.chenenyu.router.matcher.DirectMatcher} ->
15 | * {@link com.chenenyu.router.matcher.SchemeMatcher} ->
16 | * {@link com.chenenyu.router.matcher.ImplicitMatcher} ->
17 | * {@link com.chenenyu.router.matcher.BrowserMatcher}
18 | *
19 | * See {@link com.chenenyu.router.MatcherRegistry} for more info.
20 | */
21 | package com.chenenyu.router.matcher;
--------------------------------------------------------------------------------
/Sample/app/src/main/res/layout/activity_scheme.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sample/module1/src/main/kotlin/com/chenenyu/router/module/Module1Activity.kt:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.module
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.fragment.app.Fragment
6 | import com.chenenyu.router.Router
7 | import com.chenenyu.router.annotation.Route
8 | import com.chenenyu.router.module1.databinding.ActivityModule1Binding
9 |
10 | @Route("module1", "router://filter/module1")
11 | class Module1Activity : AppCompatActivity() {
12 |
13 | private lateinit var binding: ActivityModule1Binding
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | binding = ActivityModule1Binding.inflate(layoutInflater)
18 | setContentView(binding.root)
19 | val fragment = Router.build("fragment2").getFragment(this) as Fragment
20 | supportFragmentManager.beginTransaction().add(binding.activityModule1.id, fragment).commit()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/ImplicitActivity.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.os.Bundle;
4 | import android.widget.TextView;
5 |
6 | import androidx.appcompat.app.AppCompatActivity;
7 |
8 | public class ImplicitActivity extends AppCompatActivity {
9 | @Override
10 | protected void onCreate(Bundle savedInstanceState) {
11 | super.onCreate(savedInstanceState);
12 | setContentView(R.layout.activity_scheme);
13 |
14 | TextView text = findViewById(R.id.text_test);
15 | Bundle bundle = getIntent().getExtras();
16 | if (bundle != null && !bundle.isEmpty()) {
17 | StringBuilder sb = new StringBuilder();
18 | sb.append("id:")
19 | .append(bundle.getString("id"))
20 | .append("\n")
21 | .append("status:")
22 | .append(bundle.getString("status"));
23 | text.setText(sb.toString());
24 | }
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sample/module1/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/Cheney/workspace/AndroidSDK/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/Sample/module2/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/Cheney/workspace/AndroidSDK/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/Sample/module2/src/main/java/com/chenenyu/router/module/Module2Activity.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.module;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 | import androidx.fragment.app.Fragment;
7 |
8 | import com.chenenyu.router.Router;
9 | import com.chenenyu.router.annotation.Route;
10 | import com.chenenyu.router.module2.databinding.ActivityModule2Binding;
11 |
12 | @Route("module2")
13 | public class Module2Activity extends AppCompatActivity {
14 | private ActivityModule2Binding binding;
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | binding = ActivityModule2Binding.inflate(getLayoutInflater());
20 | setContentView(binding.getRoot());
21 |
22 | Fragment fragment = Router.build("fragment1").getFragment(this);
23 | if (fragment != null) {
24 | getSupportFragmentManager().beginTransaction().add(binding.activityModule2.getId(), fragment).commit();
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/SchemeFilterActivity.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.app.Activity;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.util.Log;
7 |
8 | import androidx.annotation.Nullable;
9 |
10 | import com.chenenyu.router.Router;
11 |
12 | /**
13 | * How to handle route from browser.
14 | * Created by chen on 17-5-9.
15 | */
16 | public class SchemeFilterActivity extends Activity {
17 | @Override
18 | protected void onCreate(@Nullable Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 |
21 | Uri uri = getIntent().getData();
22 | if (uri != null) {
23 | Log.d("SchemeFilterActivity", "uri: " + uri.toString());
24 | // if (!"router://filter".equals(uri.toString())) {
25 | // Router.build(uri).go(this);
26 | // }
27 | // 调用skipImplicitMatcher() 防止隐式启动死循环
28 | Router.build(uri).skipImplicitMatcher().go(this);
29 | finish();
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | android.useAndroidX=true
15 | android.enableJetifier=true
16 |
17 | # apply router plugin
18 | applyRemotePlugin=false
19 | # router gradle plugin version
20 | PLUGIN_VERSION=1.8.2
21 | # router library version
22 | ROUTER_VERSION=1.8.0
23 | # compiler library version
24 | COMPILER_VERSION=1.8.0
25 | # annotation library version
26 | ANNOTATION_VERSION=0.5.0
27 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 |
3 | jdk:
4 | - oraclejdk8
5 |
6 | notifications:
7 | email: false
8 |
9 | sudo: false
10 |
11 | android:
12 | components:
13 | - tools
14 | - platform-tools
15 | - build-tools-28.0.2
16 | - android-28
17 | - extra-android-m2repository
18 | - extra-android-support
19 |
20 | # licenses:
21 | # - 'android-sdk-license-.+'
22 | # - '.+'
23 |
24 | before_install:
25 | - chmod +x gradlew
26 |
27 | # workaround for "http://stackoverflow.com/questions/37615379/travis-ci-build-doesnt-work-with-android-constraint-layout"
28 | - mkdir "$ANDROID_HOME/licenses" || true
29 | - echo -e "\n24333f8a63b6825ea9c5514f83c2829b004d1fee" > "$ANDROID_HOME/licenses/android-sdk-license"
30 | - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license"
31 |
32 | script:
33 | - ./gradlew :app:assembleRelease
34 |
35 | before_cache:
36 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
37 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/
38 |
39 | cache:
40 | directories:
41 | - $HOME/.gradle/caches/
42 | - $HOME/.gradle/wrapper/
--------------------------------------------------------------------------------
/gradle-plugin/src/main/groovy/com/chenenyu/router/ManifestTransformerTask.groovy:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router
2 |
3 | import org.gradle.api.DefaultTask
4 | import org.gradle.api.Project
5 | import org.gradle.api.file.RegularFileProperty
6 | import org.gradle.api.tasks.InputFile
7 | import org.gradle.api.tasks.OutputFile
8 | import org.gradle.api.tasks.TaskAction
9 |
10 | import javax.inject.Inject
11 |
12 | abstract class ManifestTransformerTask extends DefaultTask {
13 | private Project project
14 |
15 | @Inject
16 | ManifestTransformerTask(Project project) {
17 | this.project = project
18 | }
19 |
20 | @InputFile
21 | abstract RegularFileProperty getMergedManifest()
22 |
23 | @OutputFile
24 | abstract RegularFileProperty getUpdatedManifest()
25 |
26 | @TaskAction
27 | void taskAction() {
28 | File input = getMergedManifest().get().asFile
29 | File output = getUpdatedManifest().get().asFile
30 | // project.logger.warn("input: ${input.absolutePath}")
31 | // project.logger.warn("output: ${output.absolutePath}")
32 |
33 | ManifestTransformer.transform(project, input, output)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/chain/FragmentValidator.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.chain;
2 |
3 |
4 | import androidx.annotation.NonNull;
5 |
6 | import com.chenenyu.router.AptHub;
7 | import com.chenenyu.router.MatcherRegistry;
8 | import com.chenenyu.router.RouteInterceptor;
9 | import com.chenenyu.router.RouteResponse;
10 | import com.chenenyu.router.RouteStatus;
11 | import com.chenenyu.router.matcher.AbsExplicitMatcher;
12 |
13 | import java.util.List;
14 |
15 | /**
16 | * Created by chenenyu on 2018/6/15.
17 | */
18 | public class FragmentValidator implements RouteInterceptor {
19 | @NonNull
20 | @Override
21 | public RouteResponse intercept(Chain chain) {
22 | // Fragment只能匹配显式Matcher
23 | List matcherList = MatcherRegistry.getExplicitMatcher();
24 | if (matcherList.isEmpty()) {
25 | return RouteResponse.assemble(RouteStatus.FAILED, "The MatcherRegistry contains no explicit matcher.");
26 | }
27 | if (AptHub.routeTable.isEmpty()) {
28 | return RouteResponse.assemble(RouteStatus.FAILED, "The RouteTable is empty.");
29 | }
30 | return chain.process();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/ForResultActivity.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.widget.Button;
7 | import android.widget.TextView;
8 |
9 | import androidx.appcompat.app.AppCompatActivity;
10 |
11 | import com.chenenyu.router.annotation.Route;
12 |
13 | @Route("result")
14 | public class ForResultActivity extends AppCompatActivity {
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_for_result);
20 |
21 | String extra = getIntent().getStringExtra("extra");
22 | TextView textExtra = findViewById(R.id.text_extra);
23 | textExtra.setText(extra);
24 |
25 | Button result = findViewById(R.id.btn_result);
26 | result.setOnClickListener(new View.OnClickListener() {
27 | @Override
28 | public void onClick(View v) {
29 | setResult(RESULT_OK, new Intent().putExtra("extra", "Result from ForResultActivity"));
30 | finish();
31 | }
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/util/RLog.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.util;
2 |
3 | import android.util.Log;
4 |
5 | /**
6 | * Internal simple log.
7 | *
8 | * Created by chenenyu on 2016/12/27.
9 | */
10 | public class RLog {
11 | private static final String TAG = "Router";
12 | private static boolean sLoggable = false;
13 |
14 | public static void showLog(boolean loggable) {
15 | sLoggable = loggable;
16 | }
17 |
18 | public static void i(String msg) {
19 | if (sLoggable && msg != null) {
20 | Log.i(TAG, msg);
21 | }
22 | }
23 |
24 | public static void i(String tag, String msg) {
25 | if (sLoggable && msg != null) {
26 | Log.i(tag, msg);
27 | }
28 | }
29 |
30 | public static void w(String msg) {
31 | if (sLoggable && msg != null) {
32 | Log.w(TAG, msg);
33 | }
34 | }
35 |
36 | public static void e(String msg) {
37 | if (sLoggable && msg != null) {
38 | Log.e(TAG, msg);
39 | }
40 | }
41 |
42 | public static void e(String msg, Throwable tr) {
43 | if (sLoggable) {
44 | Log.e(TAG, msg, tr);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/matcher/AbsExplicitMatcher.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.matcher;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.net.Uri;
7 |
8 | import androidx.annotation.Nullable;
9 | import androidx.fragment.app.Fragment;
10 |
11 | /**
12 | * Base mather for explicit intent and fragment.
13 | *
14 | * Created by chenenyu on 2017/3/12.
15 | */
16 | public abstract class AbsExplicitMatcher extends AbsMatcher {
17 |
18 | public AbsExplicitMatcher(int priority) {
19 | super(priority);
20 | }
21 |
22 | @Override
23 | public Object generate(Context context, Uri uri, @Nullable Class> target) {
24 | if (target == null) {
25 | return null;
26 | }
27 | Object result = null;
28 | if (Activity.class.isAssignableFrom(target)) {
29 | result = new Intent(context, target);
30 | } else if (Fragment.class.isAssignableFrom(target)) {
31 | try {
32 | result = target.newInstance();
33 | } catch (Exception e) {
34 | e.printStackTrace();
35 | }
36 | }
37 | return result;
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/RouteResponse.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import androidx.annotation.NonNull;
4 | import androidx.annotation.Nullable;
5 |
6 | /**
7 | * Created by chenenyu on 2018/6/15.
8 | */
9 | public final class RouteResponse {
10 | @NonNull
11 | private RouteStatus status = RouteStatus.PROCESSING;
12 | private String message;
13 | @Nullable
14 | private Object result;
15 |
16 | private RouteResponse() {
17 | }
18 |
19 | public static RouteResponse assemble(@NonNull RouteStatus status, @Nullable String msg) {
20 | RouteResponse response = new RouteResponse();
21 | response.status = status;
22 | response.message = msg;
23 | return response;
24 | }
25 |
26 | @NonNull
27 | public RouteStatus getStatus() {
28 | return status;
29 | }
30 |
31 | public void setStatus(@NonNull RouteStatus status) {
32 | this.status = status;
33 | }
34 |
35 | public String getMessage() {
36 | return message;
37 | }
38 |
39 | @Nullable
40 | public Object getResult() {
41 | return result;
42 | }
43 |
44 | public void setResult(@Nullable Object result) {
45 | this.result = result;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/matcher/Matcher.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.matcher;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 |
6 | import androidx.annotation.Nullable;
7 |
8 | import com.chenenyu.router.RouteRequest;
9 |
10 | /**
11 | * Match rule.
12 | *
13 | * Created by chenenyu on 2017/3/7.
14 | */
15 | interface Matcher extends Comparable {
16 | /**
17 | * Determines if the given uri matches current route.
18 | *
19 | * @param context Context.
20 | * @param uri the given uri.
21 | * @param route path in route table.
22 | * @param routeRequest {@link RouteRequest}.
23 | * @return True if matched, false otherwise.
24 | */
25 | boolean match(Context context, Uri uri, @Nullable String route, RouteRequest routeRequest);
26 |
27 | /**
28 | * Called when {@link #match(Context, Uri, String, RouteRequest)} returns true.
29 | *
30 | * @param context Context.
31 | * @param uri The given uri.
32 | * @param target Route target. Activity or Fragment.
33 | * @return An object(intent/fragment) that the matcher generated.
34 | */
35 | Object generate(Context context, Uri uri, @Nullable Class> target);
36 | }
37 |
--------------------------------------------------------------------------------
/Sample/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'com.chenenyu.router'
3 |
4 | android {
5 | namespace = "com.chenenyu.router.app"
6 |
7 | compileSdkVersion rootProject.ext.compileSdkVersion
8 |
9 | defaultConfig {
10 | applicationId "com.chenenyu.router.app"
11 | minSdkVersion rootProject.ext.minSdkVersion
12 | targetSdkVersion rootProject.ext.targetSdkVersion
13 | versionCode 1
14 | versionName "1.0"
15 | }
16 |
17 | buildFeatures {
18 | viewBinding true
19 | }
20 |
21 | buildTypes {
22 | debug {
23 | // 测试混淆
24 | minifyEnabled true
25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 |
29 | lintOptions {
30 | checkReleaseBuilds false
31 | abortOnError false
32 | }
33 |
34 | compileOptions {
35 | sourceCompatibility JavaVersion.VERSION_1_8
36 | targetCompatibility JavaVersion.VERSION_1_8
37 | }
38 | }
39 |
40 | dependencies {
41 | implementation fileTree(include: ['*.jar'], dir: 'libs')
42 | implementation "androidx.appcompat:appcompat:1.3.0"
43 | implementation project(':module1')
44 | implementation project(':module2')
45 | }
46 |
--------------------------------------------------------------------------------
/router-plugin/src/main/java/com/l3gacy/plugin/router/asm/RouterClassVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.l3gacy.plugin.router.asm
2 |
3 | import org.objectweb.asm.ClassVisitor
4 | import org.objectweb.asm.MethodVisitor
5 | import org.objectweb.asm.Opcodes
6 |
7 | /**
8 | *
9 | * Created by J!nl!n on 2022/10/18.
10 | *
11 | * Copyright © 2022 J!nl!n™ Inc. All rights reserved.
12 | *
13 | */
14 | internal class RouterClassVisitor(
15 | classVisitor: ClassVisitor,
16 | private val records: Map>,
17 | ) : ClassVisitor(Opcodes.ASM9, classVisitor) {
18 |
19 | /**
20 | * [Special Methods](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.9)
21 | */
22 | override fun visitMethod(
23 | access: Int,
24 | name: String?,
25 | descriptor: String?,
26 | signature: String?,
27 | exceptions: Array?
28 | ): MethodVisitor {
29 | val oldVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)
30 | // 字节码层面,方法名称是 静态代码块 static { } 的标识
31 | // 在 JVM 第一次加载class文件时调用,包括静态变量初始化语句和静态块的执行
32 | return if (name == "") RouterMethodVisitor(
33 | oldVisitor, records, access, name, descriptor
34 | ) else oldVisitor
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/RouteInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.annotation.Nullable;
7 | import androidx.fragment.app.Fragment;
8 |
9 | /**
10 | * Interceptor before route.
11 | *
12 | * Created by chenenyu on 2016/12/20.
13 | */
14 | public interface RouteInterceptor {
15 | @NonNull
16 | RouteResponse intercept(Chain chain);
17 |
18 | /**
19 | * Interceptor chain processor.
20 | */
21 | interface Chain {
22 | /**
23 | * Get current RouteRequest object.
24 | */
25 | @NonNull
26 | RouteRequest getRequest();
27 |
28 | /**
29 | * Get source object, activity or fragment instance.
30 | */
31 | @NonNull
32 | Object getSource();
33 |
34 | @NonNull
35 | Context getContext();
36 |
37 | @Nullable
38 | Fragment getFragment();
39 |
40 | /**
41 | * Continue to process this route request.
42 | */
43 | @NonNull
44 | RouteResponse process();
45 |
46 | /**
47 | * Intercept this route request.
48 | */
49 | @NonNull
50 | RouteResponse intercept();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/gradle-plugin/src/main/groovy/com/chenenyu/router/ManifestTransformer.groovy:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router
2 |
3 | //import groovy.xml.XmlParser // Added in gradle7.0(groovy 3.0.0)
4 | import org.gradle.api.Project
5 |
6 | class ManifestTransformer {
7 | static void transform(Project project, File input, File output) {
8 | Node manifest = new XmlParser().parse(input)
9 | Node applicationNode = null
10 | Object application = manifest.get('application')
11 | if (application instanceof NodeList) {
12 | if (application.isEmpty()) { // There is no `application` node in AndroidManifest.xml
13 | applicationNode = manifest.appendNode("application", ['xmlns:android': 'http://schemas.android.com/apk/res/android'])
14 | } else {
15 | applicationNode = application.first()
16 | }
17 | applicationNode.appendNode('meta-data', ['android:name': project.name, 'android:value': 'com.chenenyu.router.moduleName'])
18 | }
19 | if (applicationNode != null) {
20 | FileWriter fileWriter = new FileWriter(output)
21 | XmlNodePrinter nodePrinter = new XmlNodePrinter(new PrintWriter(fileWriter))
22 | nodePrinter.setPreserveWhitespace(true)
23 | nodePrinter.print(manifest)
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/chain/IntentValidator.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.chain;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import com.chenenyu.router.AptHub;
6 | import com.chenenyu.router.MatcherRegistry;
7 | import com.chenenyu.router.RouteInterceptor;
8 | import com.chenenyu.router.RouteRequest;
9 | import com.chenenyu.router.RouteResponse;
10 | import com.chenenyu.router.RouteStatus;
11 |
12 | /**
13 | * Created by chenenyu on 2018/6/15.
14 | */
15 | public class IntentValidator implements RouteInterceptor {
16 | @NonNull
17 | @Override
18 | public RouteResponse intercept(Chain chain) {
19 | RouteRequest request = chain.getRequest();
20 | if (MatcherRegistry.getMatcher().isEmpty()) {
21 | return RouteResponse.assemble(RouteStatus.FAILED, "The MatcherRegistry contains no matcher.");
22 | }
23 | if (request.isSkipImplicitMatcher()) {
24 | if (MatcherRegistry.getExplicitMatcher().isEmpty()) {
25 | return RouteResponse.assemble(RouteStatus.FAILED, "The MatcherRegistry contains no explicit matcher.");
26 | }
27 | if (AptHub.routeTable.isEmpty()) {
28 | return RouteResponse.assemble(RouteStatus.FAILED, "The route table is empty.");
29 | }
30 | }
31 | return chain.process();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sample/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/Cheney/workspace/AndroidSDK/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
27 | # Router
28 | #-keep class * implements com.chenenyu.router.template.InterceptorTable
29 | #-keep class * implements com.chenenyu.router.template.ParamInjector
30 | #-keep class * implements com.chenenyu.router.template.RouteTable
31 | #-keep class * implements com.chenenyu.router.template.TargetInterceptorsTable
32 |
33 |
--------------------------------------------------------------------------------
/router/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/Cheney/workspace/AndroidSDK/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
27 | # Router
28 | -keep class * implements com.chenenyu.router.template.InterceptorTable
29 | -keep class * implements com.chenenyu.router.template.ParamInjector
30 | -keep class * implements com.chenenyu.router.template.RouteTable
31 | -keep class * implements com.chenenyu.router.template.TargetInterceptorsTable
32 | -keepnames class * extends androidx.fragment.app.Fragment
33 |
--------------------------------------------------------------------------------
/router/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | namespace = "com.chenenyu.router"
5 |
6 | compileSdkVersion rootProject.ext.compileSdkVersion
7 |
8 | defaultConfig {
9 | minSdkVersion rootProject.ext.minSdkVersion
10 | targetSdkVersion rootProject.ext.targetSdkVersion
11 | versionCode 1
12 | versionName ROUTER_VERSION
13 | }
14 |
15 | buildTypes {
16 | debug {
17 | consumerProguardFile('proguard-rules.pro')
18 | }
19 | release {
20 | consumerProguardFile('proguard-rules.pro')
21 | }
22 | }
23 |
24 | lintOptions {
25 | checkReleaseBuilds false
26 | abortOnError false
27 | }
28 |
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 | }
34 |
35 | dependencies {
36 | implementation fileTree(include: ['*.jar'], dir: 'libs')
37 | compileOnly 'androidx.annotation:annotation:1.1.0'
38 | compileOnly "androidx.fragment:fragment:1.3.5"
39 | api "androidx.startup:startup-runtime:1.1.0"
40 | // api project(':annotation')
41 | api "com.chenenyu.router:annotation:${ANNOTATION_VERSION}"
42 | }
43 |
44 | ext {
45 | GROUP = 'com.chenenyu.router'
46 | ARTIFACT = 'router'
47 | VERSION = ROUTER_VERSION
48 | }
49 |
50 | //apply from: '../gradle/publish.gradle'
51 |
--------------------------------------------------------------------------------
/router-plugin/src/main/java/com/l3gacy/plugin/internal/Stopwatch.kt:
--------------------------------------------------------------------------------
1 | package com.l3gacy.plugin.internal
2 |
3 | import java.util.concurrent.TimeUnit
4 |
5 | class Stopwatch {
6 |
7 | private var start: Long = -1L
8 | private var lastSplit: Long = -1L
9 | private lateinit var label: String
10 |
11 | /**
12 | * Start the stopwatch.
13 | */
14 | fun start(label: String) {
15 | check(start == -1L) {
16 | "Stopwatch was already started"
17 | }
18 | this.label = label
19 | start = System.nanoTime()
20 | lastSplit = start
21 | }
22 |
23 | /**
24 | * Reports the split time.
25 | *
26 | * @param label Label to use when printing split time
27 | * @param reportDiffFromLastSplit if `true` report the time from last split instead of the start
28 | */
29 | fun splitTime(label: String, reportDiffFromLastSplit: Boolean = true) {
30 | val split = System.nanoTime()
31 | val diff = if (reportDiffFromLastSplit) { split - lastSplit } else { split - start }
32 | lastSplit = split
33 | Log.v("$label: ${TimeUnit.NANOSECONDS.toMillis(diff)} ms.")
34 | }
35 |
36 | /**
37 | * Stops the timer and report the result.
38 | */
39 | fun stop() {
40 | val stop = System.nanoTime()
41 | val diff = stop - start
42 | Log.v("$label: ${TimeUnit.NANOSECONDS.toMillis(diff)} ms.")
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Sample/module1/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'com.chenenyu.router'
4 |
5 | android {
6 | namespace = "com.chenenyu.router.module1"
7 |
8 | compileSdkVersion = rootProject.ext.compileSdkVersion
9 |
10 | defaultConfig {
11 | minSdkVersion rootProject.ext.minSdkVersion
12 | targetSdkVersion rootProject.ext.targetSdkVersion
13 | versionCode 1
14 | versionName "1.0"
15 | }
16 | // viewBinding.enabled = true
17 | buildFeatures {
18 | viewBinding true
19 | }
20 |
21 | buildTypes {
22 | debug {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 |
28 | lintOptions {
29 | checkReleaseBuilds false
30 | abortOnError false
31 | }
32 |
33 | sourceSets {
34 | main.java.srcDirs += 'src/main/kotlin'
35 | }
36 |
37 | compileOptions {
38 | sourceCompatibility JavaVersion.VERSION_1_8
39 | targetCompatibility JavaVersion.VERSION_1_8
40 | }
41 |
42 | compileOptions {
43 | sourceCompatibility = JavaVersion.VERSION_17
44 | targetCompatibility = JavaVersion.VERSION_17
45 | }
46 | }
47 |
48 | dependencies {
49 | implementation fileTree(include: ['*.jar'], dir: 'libs')
50 | implementation "androidx.appcompat:appcompat:1.3.0"
51 | }
52 |
--------------------------------------------------------------------------------
/Sample/module2/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'com.chenenyu.router'
4 |
5 | android {
6 | namespace = "com.chenenyu.router.module2"
7 |
8 | compileSdkVersion = rootProject.ext.compileSdkVersion
9 |
10 | defaultConfig {
11 | minSdkVersion rootProject.ext.minSdkVersion
12 | targetSdkVersion rootProject.ext.targetSdkVersion
13 | versionCode 1
14 | versionName "1.0"
15 | }
16 | // viewBinding.enabled = true
17 | buildFeatures {
18 | viewBinding true
19 | }
20 |
21 | buildTypes {
22 | debug {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 |
28 | lintOptions {
29 | checkReleaseBuilds false
30 | abortOnError false
31 | }
32 |
33 | sourceSets {
34 | main.java.srcDirs += 'src/main/kotlin'
35 | }
36 |
37 | compileOptions {
38 | sourceCompatibility JavaVersion.VERSION_1_8
39 | targetCompatibility JavaVersion.VERSION_1_8
40 | }
41 |
42 | compileOptions {
43 | sourceCompatibility = JavaVersion.VERSION_17
44 | targetCompatibility = JavaVersion.VERSION_17
45 | }
46 | }
47 |
48 | dependencies {
49 | implementation fileTree(include: ['*.jar'], dir: 'libs')
50 | implementation "androidx.appcompat:appcompat:1.3.0"
51 | }
52 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/chain/BaseValidator.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.chain;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 |
6 | import androidx.annotation.NonNull;
7 | import androidx.fragment.app.Fragment;
8 |
9 | import com.chenenyu.router.RouteInterceptor;
10 | import com.chenenyu.router.RouteRequest;
11 | import com.chenenyu.router.RouteResponse;
12 | import com.chenenyu.router.RouteStatus;
13 |
14 | /**
15 | * Created by chenenyu on 2018/6/14.
16 | */
17 | public class BaseValidator implements RouteInterceptor {
18 | @NonNull
19 | @Override
20 | public RouteResponse intercept(Chain chain) {
21 | RouteRequest request = chain.getRequest();
22 | if (request.getUri() == null) {
23 | return RouteResponse.assemble(RouteStatus.FAILED, "uri == null.");
24 | }
25 |
26 | Context context = null;
27 | if (chain.getSource() instanceof Context) {
28 | context = (Context) chain.getSource();
29 | } else if (chain.getSource() instanceof Fragment) {
30 | if (Build.VERSION.SDK_INT >= 23) {
31 | context = ((Fragment) chain.getSource()).getContext();
32 | } else {
33 | context = ((Fragment) chain.getSource()).getActivity();
34 | }
35 | }
36 | if (context == null) {
37 | return RouteResponse.assemble(RouteStatus.FAILED, "Can't retrieve context from source.");
38 | }
39 |
40 | return chain.process();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/matcher/ImplicitMatcher.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.matcher;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.content.pm.PackageManager;
6 | import android.content.pm.ResolveInfo;
7 | import android.net.Uri;
8 |
9 | import androidx.annotation.Nullable;
10 |
11 | import com.chenenyu.router.RouteRequest;
12 |
13 | /**
14 | * Support for implicit intent exclude scheme "http(s)",
15 | * cause we may want to resolve them in custom matcher, such as {@link SchemeMatcher},
16 | * or {@link BrowserMatcher}.
17 | *
18 | * Created by chenenyu on 2017/01/08.
19 | */
20 | public class ImplicitMatcher extends AbsImplicitMatcher {
21 | public ImplicitMatcher(int priority) {
22 | super(priority);
23 | }
24 |
25 | @Override
26 | public boolean match(Context context, Uri uri, @Nullable String route, RouteRequest routeRequest) {
27 | if (uri.toString().toLowerCase().startsWith("http://")
28 | || uri.toString().toLowerCase().startsWith("https://")) {
29 | return false;
30 | }
31 | ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(
32 | new Intent(Intent.ACTION_VIEW, uri), PackageManager.MATCH_DEFAULT_ONLY);
33 | if (resolveInfo != null) {
34 | // bundle parser
35 | if (uri.getQuery() != null) {
36 | parseParams(uri, routeRequest);
37 | }
38 | return true;
39 | }
40 | return false;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sample/module2/src/main/java/com/chenenyu/router/module/Module2Fragment.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.module;
2 |
3 | import android.os.Bundle;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.Nullable;
10 | import androidx.fragment.app.Fragment;
11 |
12 | import com.chenenyu.router.Router;
13 | import com.chenenyu.router.annotation.Route;
14 | import com.chenenyu.router.module2.databinding.FragmentModule2Binding;
15 |
16 | import org.jetbrains.annotations.NotNull;
17 |
18 | /**
19 | * A simple {@link Fragment} subclass.
20 | */
21 | @Route("fragment2")
22 | public class Module2Fragment extends Fragment {
23 | private FragmentModule2Binding binding;
24 |
25 | public Module2Fragment() {
26 | // Required empty public constructor
27 | }
28 |
29 | @Override
30 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
31 | Bundle savedInstanceState) {
32 | binding = FragmentModule2Binding.inflate(inflater, container, false);
33 | return binding.getRoot();
34 | }
35 |
36 | @Override
37 | public void onViewCreated(@NotNull View view, @Nullable Bundle savedInstanceState) {
38 | binding.btnGo.setOnClickListener(v -> {
39 | Router.build("module2").go(Module2Fragment.this);
40 | requireActivity().finish();
41 | });
42 | }
43 |
44 | @Override
45 | public void onDestroyView() {
46 | super.onDestroyView();
47 | binding = null;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/compiler/src/main/java/com/chenenyu/router/compiler/util/Logger.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.compiler.util;
2 |
3 | import javax.annotation.processing.Messager;
4 | import javax.lang.model.element.Element;
5 | import javax.tools.Diagnostic;
6 |
7 | /**
8 | * {@link Messager} wrapper.
9 | *
10 | * Created by Enyu Chen on 2017/6/13.
11 | */
12 | public class Logger {
13 | private Messager messager;
14 | private boolean loggable;
15 |
16 | public Logger(Messager messager, boolean loggable) {
17 | this.messager = messager;
18 | this.loggable = loggable;
19 | }
20 |
21 | public void info(CharSequence info) {
22 | if (loggable) {
23 | messager.printMessage(Diagnostic.Kind.NOTE, info);
24 | }
25 | }
26 |
27 | public void info(Element element, CharSequence info) {
28 | if (loggable) {
29 | messager.printMessage(Diagnostic.Kind.NOTE, info, element);
30 | }
31 | }
32 |
33 | public void warn(CharSequence info) {
34 | if (loggable) {
35 | messager.printMessage(Diagnostic.Kind.WARNING, info);
36 | }
37 | }
38 |
39 | public void warn(Element element, CharSequence info) {
40 | if (loggable) {
41 | messager.printMessage(Diagnostic.Kind.WARNING, info, element);
42 | }
43 | }
44 |
45 | public void error(CharSequence info) {
46 | if (loggable) {
47 | messager.printMessage(Diagnostic.Kind.ERROR, info);
48 | }
49 | }
50 |
51 | public void error(Element element, CharSequence info) {
52 | if (loggable) {
53 | messager.printMessage(Diagnostic.Kind.ERROR, info, element);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/TestActivity.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 |
7 | import com.chenenyu.router.Router;
8 | import com.chenenyu.router.annotation.InjectParam;
9 | import com.chenenyu.router.annotation.Route;
10 | import com.chenenyu.router.app.databinding.ActivityTestBinding;
11 |
12 | @Route({"test", "http://example.com/user", "router://filter/test"})
13 | public class TestActivity extends AppCompatActivity {
14 | @InjectParam
15 | String id = "0000";
16 | @InjectParam(key = "status")
17 | String sts = "default";
18 |
19 | @InjectParam
20 | short test1;
21 | @InjectParam
22 | byte[] test2;
23 | @InjectParam
24 | Model test3;
25 | @InjectParam
26 | Model test4;
27 |
28 | private ActivityTestBinding binding;
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | binding = ActivityTestBinding.inflate(getLayoutInflater());
34 | setContentView(binding.getRoot());
35 |
36 | Router.injectParams(this);
37 |
38 | Bundle mExtras = getIntent().getExtras();
39 | id = mExtras.getString("id", id);
40 |
41 | Bundle bundle = getIntent().getExtras();
42 | if (bundle != null && !bundle.isEmpty()) {
43 | StringBuilder sb = new StringBuilder();
44 | sb.append("id:")
45 | .append(id)
46 | .append("\n")
47 | .append("status:")
48 | .append(sts);
49 | binding.textTest.setText(sb.toString());
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/router-plugin/src/main/java/com/l3gacy/plugin/router/internal/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.l3gacy.plugin.router.internal
2 |
3 | /**
4 | *
5 | * Created by J!nl!n on 2022/10/2.
6 | *
7 | * Copyright © 2022 J!nl!n™ Inc. All rights reserved.
8 | *
9 | */
10 |
11 | import com.android.build.api.dsl.CommonExtension
12 | import org.gradle.api.Project
13 | import org.jetbrains.kotlin.gradle.plugin.KaptExtension
14 | import java.io.File
15 |
16 | private const val APT_OPTION_MODULE_NAME = "moduleName"
17 | private const val APT_OPTION_LOGGABLE = "loggable"
18 |
19 | internal fun Project.apt(loggable: Boolean) {
20 | // val android = extensions.findByName("android") as? BaseExtension
21 | val android = extensions.findByType(CommonExtension::class.java)
22 | android?.apply {
23 | val options = mapOf(
24 | APT_OPTION_MODULE_NAME to name,
25 | APT_OPTION_LOGGABLE to loggable.toString()
26 | )
27 | defaultConfig.javaCompileOptions.annotationProcessorOptions.arguments(options)
28 | productFlavors.forEach { flavor ->
29 | flavor.javaCompileOptions.annotationProcessorOptions.arguments(options)
30 | }
31 | }
32 | }
33 |
34 | internal fun Project.kapt(loggable: Boolean) {
35 | // https://github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KaptExtension.kt
36 | val kapt = extensions.findByType(KaptExtension::class.java)
37 | kapt?.apply {
38 | arguments {
39 | arg(APT_OPTION_MODULE_NAME, name)
40 | arg(APT_OPTION_LOGGABLE, loggable)
41 | }
42 | }
43 | }
44 |
45 | internal fun String.separator(): String {
46 | return replace('.', File.separatorChar).replace('/', File.separatorChar)
47 | }
48 |
--------------------------------------------------------------------------------
/router-plugin/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("java-library")
3 | id("java-gradle-plugin")
4 | // id("org.jetbrains.kotlin.jvm")
5 | // id("org.jetbrains.kotlin.jvm") version "1.7.20"
6 | `kotlin-dsl`
7 | }
8 |
9 | repositories {
10 | mavenCentral()
11 | google()
12 | gradlePluginPortal()
13 | }
14 |
15 | java {
16 | sourceCompatibility = JavaVersion.VERSION_17
17 | targetCompatibility = JavaVersion.VERSION_17
18 | }
19 |
20 | group = "com.l3gacy.plugin"
21 | version = "0.0.1"
22 |
23 | gradlePlugin {
24 | plugins {
25 | create("simplePlugin") {
26 | id = "com.chenenyu.router"
27 | implementationClass = "com.l3gacy.plugin.router.RouterPlugin"
28 | }
29 | }
30 | }
31 |
32 | dependencies {
33 | implementation(gradleApi())
34 |
35 | implementation("com.joom.grip:grip:0.9.1")
36 |
37 | compileOnly("com.android.tools.build:gradle:8.1.1")
38 | compileOnly("com.android.tools:common:31.1.1")
39 | // compileOnly("com.android.tools.build:gradle-api:8.1.1")
40 | compileOnly("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
41 |
42 | // https://central.sonatype.com/artifact/org.ow2.asm/asm/9.4
43 | // implementation("org.ow2.asm:asm:9.4")
44 | // https://central.sonatype.com/artifact/org.ow2.asm/asm-commons/9.4
45 | implementation("org.ow2.asm:asm-commons:9.4") // 依赖 asm 以及 asm-tree
46 | // https://central.sonatype.com/artifact/org.ow2.asm/asm-util/9.4
47 | // implementation("org.ow2.asm:asm-util:9.4") // 依赖 asm 以及 asm-tree、asm-analysis
48 | // https://central.sonatype.com/artifact/org.ow2.asm/asm-tree/9.4
49 | // compileOnly("org.ow2.asm:asm-tree:9.4") // 依赖 asm
50 |
51 | testImplementation("junit:junit:4.13.2")
52 | testImplementation(gradleTestKit())
53 | // testImplementation("com.android.tools.build:gradle:8.1.1")
54 | // testImplementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
55 | }
56 |
--------------------------------------------------------------------------------
/Sample/module1/src/main/java/com/chenenyu/router/module/Module1Fragment.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.module;
2 |
3 | import android.os.Bundle;
4 | import android.util.Log;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.annotation.Nullable;
11 | import androidx.fragment.app.Fragment;
12 |
13 | import com.chenenyu.router.Router;
14 | import com.chenenyu.router.annotation.InjectParam;
15 | import com.chenenyu.router.annotation.Route;
16 | import com.chenenyu.router.module1.databinding.FragmentModule1Binding;
17 |
18 |
19 | /**
20 | * A simple {@link Fragment} subclass.
21 | */
22 | @Route("fragment1")
23 | public class Module1Fragment extends Fragment {
24 | // Test param inject, not been used.
25 | @InjectParam
26 | int test1 = 123; // test default value
27 | @InjectParam(key = "test22")
28 | char[] test2;
29 |
30 | private FragmentModule1Binding binding;
31 |
32 | public Module1Fragment() {
33 | // Required empty public constructor
34 | }
35 |
36 | @Override
37 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
38 | Bundle savedInstanceState) {
39 | binding = FragmentModule1Binding.inflate(inflater, container, false);
40 | return binding.getRoot();
41 | }
42 |
43 | @Override
44 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
45 | binding.btnGo.setOnClickListener(v -> {
46 | Router.build("module1").go(Module1Fragment.this);
47 | requireActivity().finish();
48 | });
49 |
50 | Router.injectParams(this);
51 |
52 | Log.d(Module1Fragment.class.getSimpleName(), "test1=" + test1);
53 | }
54 |
55 | @Override
56 | public void onDestroyView() {
57 | super.onDestroyView();
58 | binding = null;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/compiler/src/main/java/com/chenenyu/router/compiler/util/Constants.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.compiler.util;
2 |
3 | /**
4 | *
5 | * Created by Cheney on 2017/1/12.
6 | */
7 | public class Constants {
8 | public static final String OPTION_MODULE_NAME = "moduleName";
9 | public static final String OPTION_LOGGABLE = "loggable";
10 | private static final String BASE_PACKAGE_NAME = "com.chenenyu.router";
11 | public static final String CLASS_JAVA_DOC = "Generated by Router. Do not edit it!\n";
12 |
13 | public static final String ACTIVITY_FULL_NAME = "android.app.Activity";
14 | public static final String FRAGMENT_X_FULL_NAME = "androidx.fragment.app.Fragment";
15 |
16 | public static final String ROUTE_ANNOTATION_TYPE = "com.chenenyu.router.annotation.Route";
17 | public static final String INTERCEPTOR_ANNOTATION_TYPE = "com.chenenyu.router.annotation.Interceptor";
18 | public static final String PARAM_ANNOTATION_TYPE = "com.chenenyu.router.annotation.InjectParam";
19 |
20 | public static final String APT_PACKAGE_NAME = "com.chenenyu.router.apt";
21 |
22 | public static final String METHOD_HANDLE = "handle";
23 | public static final String METHOD_INJECT = "inject";
24 |
25 | public static final String ROUTE_TABLE = "RouteTable";
26 | public static final String ROUTE_TABLE_FULL_NAME = BASE_PACKAGE_NAME + ".template." + ROUTE_TABLE;
27 |
28 | public static final String INTERCEPTOR_FULL_NAME = BASE_PACKAGE_NAME + ".RouteInterceptor";
29 |
30 | public static final String INTERCEPTOR_TABLE = "InterceptorTable";
31 | public static final String INTERCEPTOR_TABLE_FULL_NAME = BASE_PACKAGE_NAME + ".template." + INTERCEPTOR_TABLE;
32 |
33 | public static final String TARGET_INTERCEPTORS_TABLE = "TargetInterceptorsTable";
34 | public static final String TARGET_INTERCEPTORS_FULL_NAME = BASE_PACKAGE_NAME + ".template." + TARGET_INTERCEPTORS_TABLE;
35 |
36 | private static final String PARAM_INJECTOR = "ParamInjector";
37 | public static final String PARAM_INJECTOR_FULL_NAME = BASE_PACKAGE_NAME + ".template." + PARAM_INJECTOR;
38 | public static final String PARAM_CLASS_SUFFIX = "$$Router$$" + PARAM_INJECTOR; // XXXActivity$$Router$$Params
39 | }
40 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/matcher/AbsMatcher.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.matcher;
2 |
3 | import android.net.Uri;
4 | import android.os.Bundle;
5 |
6 | import androidx.annotation.NonNull;
7 |
8 | import com.chenenyu.router.RouteRequest;
9 |
10 | import java.util.Iterator;
11 | import java.util.List;
12 | import java.util.Set;
13 |
14 | /**
15 | * Abstract matcher.
16 | *
17 | * Created by chenenyu on 2016/12/23.
18 | */
19 | public abstract class AbsMatcher implements Matcher {
20 | /**
21 | * Priority in matcher list.
22 | */
23 | private int priority;
24 |
25 | public AbsMatcher(int priority) {
26 | this.priority = priority;
27 | }
28 |
29 | protected void parseParams(Uri uri, RouteRequest routeRequest) {
30 | if (uri.getQuery() != null) {
31 | Bundle bundle = routeRequest.getExtras();
32 | if (bundle == null) {
33 | bundle = new Bundle();
34 | routeRequest.setExtras(bundle);
35 | }
36 |
37 | Set keys = uri.getQueryParameterNames();
38 | Iterator keyIterator = keys.iterator();
39 | while (keyIterator.hasNext()) {
40 | String key = keyIterator.next();
41 | List values = uri.getQueryParameters(key);
42 | if (values.size() > 1) {
43 | bundle.putStringArray(key, values.toArray(new String[0]));
44 | } else if (values.size() == 1) {
45 | bundle.putString(key, values.get(0));
46 | }
47 | }
48 | }
49 | }
50 |
51 | /**
52 | * {@link android.text.TextUtils#isEmpty(CharSequence)}.
53 | */
54 | protected boolean isEmpty(CharSequence str) {
55 | return str == null || str.length() == 0;
56 | }
57 |
58 | @Override
59 | public int compareTo(@NonNull Matcher matcher) {
60 | if (this == matcher) {
61 | return 0;
62 | }
63 | if (matcher instanceof AbsMatcher) {
64 | if (this.priority > ((AbsMatcher) matcher).priority) {
65 | return -1;
66 | } else {
67 | return 1;
68 | }
69 | }
70 | return matcher.compareTo(this);
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/Router.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import android.net.Uri;
4 |
5 | import com.chenenyu.router.matcher.AbsMatcher;
6 | import com.chenenyu.router.template.RouteTable;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | /**
12 | * Entry class.
13 | *
14 | * Created by chenenyu on 2016/12/20.
15 | */
16 | public class Router {
17 | /**
18 | * You can get the raw uri in target page by call intent.getStringExtra(Router.RAW_URI).
19 | */
20 | public static final String RAW_URI = "raw_uri";
21 |
22 | private static final List sGlobalInterceptors = new ArrayList<>();
23 |
24 |
25 | public static IRouter build(String path) {
26 | return build(path == null ? null : Uri.parse(path));
27 | }
28 |
29 | public static IRouter build(Uri uri) {
30 | return RealRouter.getInstance().build(uri);
31 | }
32 |
33 | public static IRouter build(RouteRequest request) {
34 | return RealRouter.getInstance().build(request);
35 | }
36 |
37 | /**
38 | * Custom route table.
39 | */
40 | public static void handleRouteTable(RouteTable routeTable) {
41 | if (routeTable != null) {
42 | routeTable.handle(AptHub.routeTable);
43 | }
44 | }
45 |
46 | /**
47 | * Auto inject params from bundle.
48 | *
49 | * @param obj Instance of Activity or Fragment.
50 | */
51 | public static void injectParams(Object obj) {
52 | AptHub.injectParams(obj);
53 | }
54 |
55 | /**
56 | * Global interceptor.
57 | */
58 | public static void addGlobalInterceptor(RouteInterceptor routeInterceptor) {
59 | sGlobalInterceptors.add(routeInterceptor);
60 | }
61 |
62 | public static List getGlobalInterceptors() {
63 | return sGlobalInterceptors;
64 | }
65 |
66 | /**
67 | * Register your own matcher.
68 | *
69 | * @see com.chenenyu.router.matcher.AbsExplicitMatcher
70 | * @see com.chenenyu.router.matcher.AbsImplicitMatcher
71 | */
72 | public static void registerMatcher(AbsMatcher matcher) {
73 | MatcherRegistry.register(matcher);
74 | }
75 |
76 | public static void clearMatcher() {
77 | MatcherRegistry.clear();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/chain/AttrsProcessor.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.chain;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.fragment.app.Fragment;
9 |
10 | import com.chenenyu.router.RealInterceptorChain;
11 | import com.chenenyu.router.RouteInterceptor;
12 | import com.chenenyu.router.RouteRequest;
13 | import com.chenenyu.router.RouteResponse;
14 | import com.chenenyu.router.RouteStatus;
15 |
16 | /**
17 | * Process final attrs, such as bundle.
18 | *
19 | * Created by chenenyu on 2019/5/17.
20 | */
21 | public class AttrsProcessor implements RouteInterceptor {
22 | @NonNull
23 | @Override
24 | public RouteResponse intercept(Chain chain) {
25 | RealInterceptorChain realChain = (RealInterceptorChain) chain;
26 | Object targetInstance = realChain.getTargetInstance();
27 | if (targetInstance instanceof Intent) {
28 | RouteRequest request = chain.getRequest();
29 | assembleIntent((Intent) targetInstance, request);
30 | } else if (targetInstance instanceof Fragment) {
31 | RouteRequest request = chain.getRequest();
32 | Bundle bundle = request.getExtras();
33 | if (bundle != null && !bundle.isEmpty()) {
34 | ((Fragment) targetInstance).setArguments(bundle);
35 | }
36 | }
37 |
38 | RouteResponse response = RouteResponse.assemble(RouteStatus.SUCCEED, null);
39 | if (targetInstance != null) {
40 | response.setResult(targetInstance);
41 | } else {
42 | response.setStatus(RouteStatus.FAILED);
43 | }
44 | return response;
45 | }
46 |
47 | @SuppressLint("WrongConstant")
48 | private void assembleIntent(Intent intent, RouteRequest request) {
49 | if (request.getExtras() != null && !request.getExtras().isEmpty()) {
50 | intent.putExtras(request.getExtras());
51 | }
52 | if (request.getFlags() != 0) {
53 | intent.addFlags(request.getFlags());
54 | }
55 | if (request.getData() != null) {
56 | intent.setData(request.getData());
57 | }
58 | if (request.getType() != null) {
59 | intent.setType(request.getType());
60 | }
61 | if (request.getAction() != null) {
62 | intent.setAction(request.getAction());
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/chain/FragmentProcessor.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.chain;
2 |
3 | import androidx.annotation.NonNull;
4 | import androidx.fragment.app.Fragment;
5 |
6 | import com.chenenyu.router.AptHub;
7 | import com.chenenyu.router.MatcherRegistry;
8 | import com.chenenyu.router.RealInterceptorChain;
9 | import com.chenenyu.router.RouteInterceptor;
10 | import com.chenenyu.router.RouteResponse;
11 | import com.chenenyu.router.RouteStatus;
12 | import com.chenenyu.router.matcher.AbsExplicitMatcher;
13 | import com.chenenyu.router.util.RLog;
14 |
15 | import java.util.List;
16 | import java.util.Map;
17 | import java.util.Set;
18 |
19 | /**
20 | * Created by chenenyu on 2018/6/15.
21 | */
22 | public class FragmentProcessor implements RouteInterceptor {
23 | @NonNull
24 | @Override
25 | public RouteResponse intercept(Chain chain) {
26 | RealInterceptorChain realChain = (RealInterceptorChain) chain;
27 | Set>> entries = AptHub.routeTable.entrySet();
28 | List matcherList = MatcherRegistry.getExplicitMatcher();
29 |
30 | for (AbsExplicitMatcher matcher : matcherList) {
31 | for (Map.Entry> entry : entries) {
32 | if (matcher.match(chain.getContext(), chain.getRequest().getUri(), entry.getKey(), chain.getRequest())) {
33 | RLog.i(String.format("{uri=%s, matcher=%s}",
34 | chain.getRequest().getUri(), matcher.getClass().getCanonicalName()));
35 | realChain.setTargetClass(entry.getValue());
36 | Object result = matcher.generate(chain.getContext(), chain.getRequest().getUri(), entry.getValue());
37 | if (result instanceof Fragment) {
38 | realChain.setTargetInstance(result);
39 | } else {
40 | return RouteResponse.assemble(RouteStatus.FAILED, String.format(
41 | "The matcher can't generate a fragment instance for uri: %s",
42 | chain.getRequest().getUri().toString()));
43 | }
44 | return chain.process();
45 | }
46 | }
47 | }
48 |
49 | return RouteResponse.assemble(RouteStatus.NOT_FOUND, String.format(
50 | "Can't find a fragment that matches the given uri: %s",
51 | chain.getRequest().getUri().toString()));
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/AptHub.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import android.app.Activity;
4 |
5 | import androidx.fragment.app.Fragment;
6 |
7 | import com.chenenyu.router.template.ParamInjector;
8 | import com.chenenyu.router.util.RLog;
9 |
10 | import java.util.HashMap;
11 | import java.util.LinkedHashMap;
12 | import java.util.Map;
13 |
14 | /**
15 | * Hub for 'apt' classes.
16 | *
17 | * Created by chenenyu on 2017/3/13.
18 | */
19 | public final class AptHub {
20 | private static final String PARAM_CLASS_SUFFIX = "$$Router$$ParamInjector";
21 |
22 | // injector's name -> injector
23 | private static final Map> injectors = new HashMap<>();
24 |
25 | // Uri -> Activity/Fragment
26 | public final static Map> routeTable = new HashMap<>();
27 |
28 | // interceptor's name -> interceptor
29 | public final static Map> interceptorTable = new HashMap<>();
30 | // interceptor instance
31 | public final static Map interceptorInstances = new HashMap<>();
32 |
33 | // Activity/Fragment -> interceptors' name
34 | // Note: 这里用LinkedHashMap保证有序
35 | public final static Map, String[]> targetInterceptorsTable = new LinkedHashMap<>();
36 |
37 |
38 | /**
39 | * Auto inject params from bundle.
40 | *
41 | * @param obj Activity or Fragment.
42 | */
43 | @SuppressWarnings("unchecked")
44 | static void injectParams(Object obj) {
45 | if (obj instanceof Activity || obj instanceof Fragment) {
46 | String key = obj.getClass().getCanonicalName();
47 | Class clz;
48 | if (!injectors.containsKey(key)) {
49 | try {
50 | clz = (Class) Class.forName(key + PARAM_CLASS_SUFFIX);
51 | injectors.put(key, clz);
52 | } catch (ClassNotFoundException e) {
53 | RLog.e("Inject params failed.", e);
54 | return;
55 | }
56 | } else {
57 | clz = injectors.get(key);
58 | }
59 | try {
60 | ParamInjector injector = clz.newInstance();
61 | injector.inject(obj);
62 | } catch (Exception e) {
63 | RLog.e("Inject params failed.", e);
64 | }
65 | } else {
66 | RLog.e("The obj you passed must be an instance of Activity or Fragment.");
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Sample/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
40 |
43 |
46 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
59 |
60 |
61 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/MatcherRegistry.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import com.chenenyu.router.matcher.AbsExplicitMatcher;
4 | import com.chenenyu.router.matcher.AbsImplicitMatcher;
5 | import com.chenenyu.router.matcher.AbsMatcher;
6 | import com.chenenyu.router.matcher.BrowserMatcher;
7 | import com.chenenyu.router.matcher.DirectMatcher;
8 | import com.chenenyu.router.matcher.ImplicitMatcher;
9 | import com.chenenyu.router.matcher.SchemeMatcher;
10 | import com.chenenyu.router.util.RLog;
11 |
12 | import java.util.ArrayList;
13 | import java.util.Collections;
14 | import java.util.List;
15 |
16 | /**
17 | * Matcher registry.
18 | *
19 | * Created by chenenyu on 2017/1/5.
20 | */
21 | public final class MatcherRegistry {
22 |
23 | private static final List ALL = new ArrayList<>();
24 | private static final List explicitMatcher = new ArrayList<>();
25 | private static final List implicitMatcher = new ArrayList<>();
26 |
27 | static {
28 | ALL.add(new DirectMatcher(0x1000));
29 | ALL.add(new SchemeMatcher(0x0100));
30 | ALL.add(new ImplicitMatcher(0x0010));
31 | ALL.add(new BrowserMatcher(0x0000));
32 | Collections.sort(ALL);
33 | classifyMatcher();
34 | }
35 |
36 | public static void register(AbsMatcher matcher) {
37 | if (matcher instanceof AbsExplicitMatcher || matcher instanceof AbsImplicitMatcher) {
38 | ALL.add(matcher);
39 | Collections.sort(ALL);
40 | classifyMatcher();
41 | } else {
42 | RLog.e(String.format("%s must be a subclass of AbsExplicitMatcher or AbsImplicitMatcher",
43 | matcher.getClass().getSimpleName()));
44 | }
45 | }
46 |
47 | public static List getMatcher() {
48 | return ALL;
49 | }
50 |
51 | public static List getExplicitMatcher() {
52 | return explicitMatcher;
53 | }
54 |
55 | public static List getImplicitMatcher() {
56 | return implicitMatcher;
57 | }
58 |
59 | public static void clear() {
60 | ALL.clear();
61 | explicitMatcher.clear();
62 | implicitMatcher.clear();
63 | }
64 |
65 | private static void classifyMatcher() {
66 | explicitMatcher.clear();
67 | implicitMatcher.clear();
68 | for (AbsMatcher absMatcher : ALL) {
69 | if (absMatcher instanceof AbsExplicitMatcher) {
70 | explicitMatcher.add((AbsExplicitMatcher) absMatcher);
71 | } else if (absMatcher instanceof AbsImplicitMatcher) {
72 | implicitMatcher.add((AbsImplicitMatcher) absMatcher);
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/matcher/SchemeMatcher.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.matcher;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 |
6 | import androidx.annotation.Nullable;
7 |
8 | import com.chenenyu.router.RouteRequest;
9 |
10 | /**
11 | * Standard scheme matcher. It matches scheme, authority(host, port) and path(if offered),
12 | * then transfers the query part(if offered) to bundle/arguments.
13 | *
14 | * If you configured a route like this:
15 | *
16 | *
17 | * -> @Route("http://example.com/user")
18 | *
19 | *
20 | * Then http://example.com/user will match this route,
21 | * http://example.com/user?id=9527&status=0 also does and puts bundles for you:
22 | *
23 | *
24 | * bundle.putString("id", "9527");
25 | *
26 | * bundle.putString("status", "0");
27 | *
28 | * intent.putExtras(bundle); or fragment.setArguments(bundle);
29 | *
30 | *
31 | *
32 | * Created by chenenyu on 2016/12/30.
33 | */
34 | public class SchemeMatcher extends AbsExplicitMatcher {
35 | public SchemeMatcher(int priority) {
36 | super(priority);
37 | }
38 |
39 | @Override
40 | public boolean match(Context context, Uri uri, @Nullable String route, RouteRequest routeRequest) {
41 | if (isEmpty(route)) {
42 | return false;
43 | }
44 | Uri routeUri = Uri.parse(route);
45 | if (uri.isAbsolute() && routeUri.isAbsolute()) { // scheme != null
46 | if (!uri.getScheme().equals(routeUri.getScheme())) {
47 | // http != https
48 | return false;
49 | }
50 | if (isEmpty(uri.getAuthority()) && isEmpty(routeUri.getAuthority())) {
51 | // host1 == host2 == empty
52 | return true;
53 | }
54 | // google.com == google.com:443 (include port)
55 | if (!isEmpty(uri.getAuthority()) && !isEmpty(routeUri.getAuthority())
56 | && uri.getAuthority().equals(routeUri.getAuthority())) {
57 | if (!cutSlash(uri.getPath()).equals(cutSlash(routeUri.getPath()))) {
58 | return false;
59 | }
60 |
61 | // bundle parser
62 | if (uri.getQuery() != null) {
63 | parseParams(uri, routeRequest);
64 | }
65 | return true;
66 | }
67 | }
68 | return false;
69 | }
70 |
71 | /**
72 | * 剔除path头部和尾部的斜杠/
73 | *
74 | * @param path 路径
75 | * @return 无/的路径
76 | */
77 | private String cutSlash(String path) {
78 | if (path.startsWith("/")) {
79 | return cutSlash(path.substring(1));
80 | }
81 | if (path.endsWith("/")) {
82 | return cutSlash(path.substring(0, path.length() - 1));
83 | }
84 | return path;
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/router-plugin/src/main/java/com/l3gacy/plugin/internal/Extensions.kt:
--------------------------------------------------------------------------------
1 | package com.l3gacy.plugin.internal
2 |
3 | /**
4 | *
5 | * Created by J!nl!n on 2022/10/2.
6 | *
7 | * Copyright © 2022 J!nl!n™ Inc. All rights reserved.
8 | *
9 | */
10 |
11 | import com.android.build.gradle.api.AndroidBasePlugin
12 | import org.gradle.api.Project
13 | import org.w3c.dom.NamedNodeMap
14 | import org.w3c.dom.Node
15 | import org.w3c.dom.NodeList
16 | import java.io.Closeable
17 | import java.io.File
18 | import java.io.IOException
19 | import java.util.Locale
20 | import java.util.jar.JarEntry
21 | import java.util.jar.JarOutputStream
22 | import java.util.zip.ZipException
23 |
24 | internal fun Closeable.closeQuietly() {
25 | try {
26 | close()
27 | } catch (exception: IOException) {
28 | // Ignore the exception.
29 | }
30 | }
31 |
32 | internal fun JarOutputStream.createFile(name: String, data: ByteArray) {
33 | try {
34 | putNextEntry(JarEntry(name.replace(File.separatorChar, '/')))
35 | write(data)
36 | closeEntry()
37 | } catch (e: ZipException) {
38 | // it's normal to have duplicated files in META-INF
39 | if (!name.startsWith("META-INF")) throw e
40 | }
41 | }
42 |
43 | internal fun JarOutputStream.createDirectory(name: String) {
44 | try {
45 | putNextEntry(JarEntry(name.replace(File.separatorChar, '/')))
46 | closeEntry()
47 | } catch (ignored: ZipException) {
48 | // it's normal that the directory already exists
49 | }
50 | }
51 |
52 | inline val String.capitalize: String
53 | get() = replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
54 |
55 | /**
56 | * 获取 Properties 属性
57 | *
58 | * @param key 键值
59 | * @param default 默认值
60 | *
61 | * @since 0.0.1
62 | */
63 | //internal inline fun Project.property(key: String, default: T): T =
64 | // project.findProperty(key) as? T ?: default
65 | internal fun Project.property(key: String, default: Any): String =
66 | project.properties[key]?.toString() ?: default.toString()
67 |
68 | @Suppress("NOTHING_TO_INLINE")
69 | internal inline fun Project.isAndroid(): Boolean {
70 | return plugins.filterIsInstance(AndroidBasePlugin::class.java).isNotEmpty()
71 | // return project.plugins.toList().any { plugin -> plugin is AndroidBasePlugin }
72 | }
73 |
74 | inline fun NodeList.map(f: (Node) -> Unit) {
75 | // It's sad, but since we're modifying the Nodes in the list, we need to keep a copy to make
76 | // sure we actually visit all of them.
77 | val copy = ArrayList(length)
78 | for (i in 0 until length) copy.add(item(i))
79 | copy.forEach { f(it) }
80 | }
81 |
82 | inline fun NamedNodeMap.map(f: (Node) -> Unit) {
83 | // It's sad, but since we're modifying the Nodes in the map, we need to keep a copy to make
84 | // sure we actually visit all of them.
85 | val copy = ArrayList(length)
86 | for (i in 0 until length) copy.add(item(i))
87 | copy.forEach { f(it) }
88 | }
89 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/chain/AppInterceptorsHandler.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.chain;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import com.chenenyu.router.AptHub;
6 | import com.chenenyu.router.RealInterceptorChain;
7 | import com.chenenyu.router.RouteInterceptor;
8 | import com.chenenyu.router.RouteRequest;
9 | import com.chenenyu.router.RouteResponse;
10 | import com.chenenyu.router.Router;
11 | import com.chenenyu.router.util.RLog;
12 |
13 | import java.util.Collections;
14 | import java.util.LinkedHashSet;
15 | import java.util.Map;
16 | import java.util.Set;
17 |
18 | /**
19 | * Collect app interceptors and insert into chain queue to process.
20 | *
21 | * Created by chenenyu on 2018/6/15.
22 | */
23 | public class AppInterceptorsHandler implements RouteInterceptor {
24 | @NonNull
25 | @Override
26 | public RouteResponse intercept(Chain chain) {
27 | if (chain.getRequest().isSkipInterceptors()) {
28 | return chain.process();
29 | }
30 |
31 | RealInterceptorChain realChain = (RealInterceptorChain) chain;
32 | RouteRequest request = chain.getRequest();
33 |
34 | // record the position of the list at where the interceptors should be insert
35 | int index = 0;
36 |
37 | // insert global interceptors in front of the queue
38 | if (!Router.getGlobalInterceptors().isEmpty()) {
39 | realChain.getInterceptors().addAll(index, Router.getGlobalInterceptors());
40 | index = Router.getGlobalInterceptors().size();
41 | }
42 |
43 | Set finalInterceptors = new LinkedHashSet<>();
44 | // add annotated interceptors
45 | if (realChain.getTargetClass() != null) {
46 | String[] baseInterceptors = AptHub.targetInterceptorsTable.get(realChain.getTargetClass());
47 | if (baseInterceptors != null && baseInterceptors.length > 0) {
48 | Collections.addAll(finalInterceptors, baseInterceptors);
49 | }
50 | }
51 |
52 | // add/remove temp interceptors
53 | if (request.getTempInterceptors() != null) {
54 | for (Map.Entry entry : request.getTempInterceptors().entrySet()) {
55 | if (entry.getValue() == Boolean.TRUE) {
56 | finalInterceptors.add(entry.getKey());
57 | } else {
58 | finalInterceptors.remove(entry.getKey());
59 | }
60 | }
61 | }
62 |
63 | if (!finalInterceptors.isEmpty()) {
64 | for (String name : finalInterceptors) {
65 | RouteInterceptor interceptor = AptHub.interceptorInstances.get(name);
66 | if (interceptor == null) {
67 | Class extends RouteInterceptor> clz = AptHub.interceptorTable.get(name);
68 | try {
69 | interceptor = clz.newInstance();
70 | AptHub.interceptorInstances.put(name, interceptor);
71 | } catch (Exception e) {
72 | RLog.e("Can't construct a interceptor instance for: " + name, e);
73 | }
74 | }
75 | // enqueue
76 | if (interceptor != null) {
77 | realChain.getInterceptors().add(index++, interceptor);
78 | }
79 | }
80 | }
81 |
82 | return chain.process();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/RealInterceptorChain.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import android.content.Context;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.annotation.Nullable;
7 | import androidx.fragment.app.Fragment;
8 |
9 | import java.util.List;
10 |
11 | /**
12 | * Interceptor chain processor.
13 | *
14 | * Created by chenenyu on 2018/6/14.
15 | */
16 | public final class RealInterceptorChain implements RouteInterceptor.Chain {
17 | @NonNull
18 | private final Object source;
19 | @NonNull
20 | private final RouteRequest request;
21 | @NonNull
22 | private final List interceptors;
23 | private int index;
24 | @Nullable
25 | private Class> targetClass; // Intent/Fragment class
26 | @Nullable
27 | private Object targetInstance; // Intent/Fragment instance
28 |
29 | RealInterceptorChain(@NonNull Object source,
30 | @NonNull RouteRequest request,
31 | @NonNull List interceptors) {
32 | this.source = source;
33 | this.request = request;
34 | this.interceptors = interceptors;
35 | }
36 |
37 | @NonNull
38 | @Override
39 | public RouteRequest getRequest() {
40 | return request;
41 | }
42 |
43 | @NonNull
44 | @Override
45 | public Object getSource() {
46 | return source;
47 | }
48 |
49 | @NonNull
50 | @Override
51 | public Context getContext() {
52 | Context context = null;
53 | if (source instanceof Context) {
54 | context = (Context) source;
55 | } else if (source instanceof Fragment) {
56 | context = ((Fragment) source).requireContext();
57 | }
58 | assert context != null;
59 | return context;
60 | }
61 |
62 | @Nullable
63 | @Override
64 | public Fragment getFragment() {
65 | return (source instanceof Fragment) ? (Fragment) source : null;
66 | }
67 |
68 | @NonNull
69 | public List getInterceptors() {
70 | return interceptors;
71 | }
72 |
73 | @Nullable
74 | public Class> getTargetClass() {
75 | return targetClass;
76 | }
77 |
78 | public void setTargetClass(@Nullable Class> targetClass) {
79 | this.targetClass = targetClass;
80 | }
81 |
82 | public void setTargetInstance(@Nullable Object targetInstance) {
83 | this.targetInstance = targetInstance;
84 | }
85 |
86 | @Nullable
87 | public Object getTargetInstance() {
88 | return targetInstance;
89 | }
90 |
91 | @NonNull
92 | @Override
93 | public RouteResponse process() {
94 | if (interceptors.isEmpty()) {
95 | RouteResponse response = RouteResponse.assemble(RouteStatus.SUCCEED, null);
96 | if (targetInstance != null) {
97 | response.setResult(targetInstance);
98 | } else {
99 | response.setStatus(RouteStatus.FAILED);
100 | }
101 | return response;
102 | }
103 | RouteInterceptor interceptor = interceptors.remove(0);
104 | return interceptor.intercept(this);
105 | }
106 |
107 | @NonNull
108 | @Override
109 | public RouteResponse intercept() {
110 | return RouteResponse.assemble(RouteStatus.INTERCEPTED, null);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |   
2 |
3 | ## 修改适配 Android Gradle Plugin 7.4+,具体实现参见 [router-plugin](router-plugin)
4 |
5 | # Router
6 |
7 | [中文wiki](https://github.com/chenenyu/Router/wiki). 方便的话给个star!❤️
8 |
9 | 
10 |
11 | ## Getting started
12 |
13 | * Add router gradle plugin to your project-level `build.gradle`, as shown below.
14 |
15 | ```Groovy
16 | buildscript {
17 | repositories {
18 | google()
19 | mavenCentral()
20 | // jcenter() // deprecated
21 | }
22 | dependencies {
23 | classpath 'com.android.tools.build:gradle:+'
24 | classpath "com.chenenyu.router:gradle-plugin:x.y.z"
25 | }
26 | }
27 | ```
28 | latest `router-gradle-plugin` version: 
29 |
30 |
31 | * Apply router plugin in your module-level 'build.gradle'.
32 |
33 | ```Groovy
34 | apply plugin: 'com.android.application' // apply plugin: 'com.android.library'
35 | apply plugin: 'com.chenenyu.router'
36 | ```
37 |
38 | **注意**: 在rootProject的`build.gradle`文件中, 可以指定插件引用的library版本.
39 |
40 | ```groovy
41 | ext {
42 | routerVersion = 'x.y.z'
43 | compilerVersion = 'x.y.z'
44 | compilerLoggable = true/false // 打开/关闭编译期log
45 | }
46 | ```
47 | latest `router` version: 
48 |
49 | latest `compiler` version: 
50 |
51 |
52 | ## 基本用法
53 |
54 | * 添加拦截器(可选)
55 |
56 | ```java
57 | @Interceptor("SampleInterceptor")
58 | public class SampleInterceptor implements RouteInterceptor {
59 | @Override
60 | public RouteResponse intercept(Chain chain) {
61 | // do something
62 | return chain.process();
63 | }
64 | }
65 | ```
66 |
67 | * 添加注解
68 |
69 | ```java
70 | // 给Activity添加注解,指定了路径和拦截器(可选)
71 | @Route(value = "test", interceptors = "SampleInterceptor")
72 | public class TestActivity extends AppCompatActivity {
73 | @InjectParam(key="foo") // 参数映射
74 | String foo;
75 |
76 | @Override
77 | protected void onCreate(Bundle savedInstanceState) {
78 | super.onCreate(savedInstanceState);
79 | Router.injectParams(this); // 自动从bundle中获取并注入参数
80 | ...
81 | }
82 | }
83 |
84 | // 给Fragment添加注解
85 | @Route("test")
86 | public class TestFragment extends Fragment {
87 | ...
88 | }
89 | ```
90 |
91 | * 跳转
92 |
93 | ```java
94 | // 简单跳转
95 | Router.build("test").go(this);
96 | // startActivityForResult
97 | Router.build("test").requestCode(0).go(this);
98 | // 携带bundle参数
99 | Router.build("test").with("key", Object).go(this);
100 | // 添加回调
101 | Router.build("test").go(this, new RouteCallback() {
102 | @Override
103 | public void callback(RouteStatus status, Uri uri, String message) {
104 | // do something
105 | }
106 | });
107 |
108 | // 获取路由对应的intent
109 | Router.build("test").getIntent();
110 | // 获取注解的Fragment
111 | Router.build("test").getFragment();
112 | ```
113 |
114 | ## 进阶用法
115 |
116 | 建议浏览 [wiki](https://github.com/chenenyu/Router/wiki).
117 |
118 | ## 讨论
119 |
120 | QQ group: 271849001
121 |
122 | ## Donate ❤️
123 |
124 | [Click here](https://github.com/chenenyu/Router/wiki/Donate).
125 |
126 | ## License
127 |
128 | [Apache 2.0](https://github.com/chenenyu/Router/blob/master/LICENSE)
129 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/IRouter.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import android.app.ActivityOptions;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.net.Uri;
7 | import android.os.Bundle;
8 | import android.os.PersistableBundle;
9 |
10 | import androidx.annotation.AnimRes;
11 | import androidx.annotation.NonNull;
12 | import androidx.annotation.RequiresApi;
13 | import androidx.core.app.ActivityOptionsCompat;
14 | import androidx.fragment.app.Fragment;
15 |
16 | /**
17 | * Router interface.
18 | *
19 | * Created by chenenyu on 2017/3/31.
20 | */
21 | public interface IRouter {
22 | /**
23 | * Entrance.
24 | */
25 | IRouter build(Uri uri);
26 |
27 | IRouter build(@NonNull RouteRequest request);
28 |
29 | /**
30 | * Route request callback.
31 | */
32 | IRouter callback(RouteCallback callback);
33 |
34 | /**
35 | * Call startActivityForResult.
36 | */
37 | IRouter requestCode(int requestCode);
38 |
39 | /**
40 | * @see Bundle#putAll(Bundle)
41 | */
42 | IRouter with(Bundle bundle);
43 |
44 | /**
45 | * @see Bundle#putAll(PersistableBundle)
46 | */
47 | @RequiresApi(21)
48 | IRouter with(PersistableBundle bundle);
49 |
50 | /**
51 | * bundle.putXXX(String key, XXX value).
52 | */
53 | IRouter with(String key, Object value);
54 |
55 | /**
56 | * @see Intent#addFlags(int)
57 | */
58 | IRouter addFlags(int flags);
59 |
60 | /**
61 | * @see Intent#setData(Uri)
62 | */
63 | IRouter setData(Uri data);
64 |
65 | /**
66 | * @see Intent#setType(String)
67 | */
68 | IRouter setType(String type);
69 |
70 | /**
71 | * @see Intent#setDataAndType(Uri, String)
72 | */
73 | IRouter setDataAndType(Uri data, String type);
74 |
75 | /**
76 | * @see Intent#setAction(String)
77 | */
78 | IRouter setAction(String action);
79 |
80 | /**
81 | * @see android.app.Activity#overridePendingTransition(int, int)
82 | */
83 | IRouter anim(@AnimRes int enterAnim, @AnimRes int exitAnim);
84 |
85 | /**
86 | * {@link ActivityOptions#toBundle()} and {@link ActivityOptionsCompat#toBundle()}.
87 | */
88 | IRouter activityOptionsBundle(Bundle activityOptionsBundle);
89 |
90 | /**
91 | * Skip {@link com.chenenyu.router.matcher.AbsImplicitMatcher}.
92 | */
93 | IRouter skipImplicitMatcher();
94 |
95 | /**
96 | * Skip all the interceptors.
97 | */
98 | IRouter skipInterceptors();
99 |
100 | /**
101 | * Skip the named interceptors.
102 | */
103 | IRouter skipInterceptors(String... interceptors);
104 |
105 | /**
106 | * Add interceptors temporarily for current route request.
107 | */
108 | IRouter addInterceptors(String... interceptors);
109 |
110 | /**
111 | * Get an intent instance.
112 | *
113 | * @param source Activity or Fragment instance.
114 | * @return {@link Intent} instance.
115 | */
116 | Intent getIntent(@NonNull Object source);
117 |
118 | /**
119 | * Get a fragment instance.
120 | *
121 | * @param source Activity or Fragment instance.
122 | * @return {@link Fragment} instance.
123 | */
124 | Fragment getFragment(@NonNull Object source);
125 |
126 | void go(Context context, RouteCallback callback);
127 |
128 | void go(Context context);
129 |
130 | void go(Fragment fragment, RouteCallback callback);
131 |
132 | void go(Fragment fragment);
133 | }
134 |
--------------------------------------------------------------------------------
/Sample/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
15 |
21 |
22 |
28 |
29 |
35 |
36 |
42 |
43 |
49 |
50 |
56 |
57 |
63 |
64 |
70 |
71 |
77 |
78 |
84 |
85 |
91 |
92 |
98 |
99 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/router-plugin/src/main/java/com/l3gacy/plugin/internal/Log.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2022 Zepp Health. All Rights Reserved.
3 | */
4 |
5 | package com.l3gacy.plugin.internal
6 |
7 | import groovy.json.JsonOutput
8 | import groovy.xml.XmlUtil
9 | import org.gradle.api.logging.LogLevel
10 | import org.gradle.api.logging.Logging
11 | import org.slf4j.helpers.MessageFormatter
12 |
13 | /**
14 | *
15 | * Created by J!nl!n on 2022/10/2.
16 | *
17 | * Copyright © 2022 J!nl!n™ Inc. All rights reserved.
18 | *
19 | * 控制台输出颜色日志工具类
20 | *
21 | * [How to print color in console using System.out.println?](https://stackoverflow.com/questions/5762491/how-to-print-color-in-console-using-system-out-println)
22 | *
23 | *
24 | * | Order | Name | Command line option | Levels outputted |
25 | * | ----- | ----------- | ------------------------------------- | ---------------------- |
26 | * | 6 | `ERROR` | does not have option (always printed) | `ERROR` |
27 | * | 5 | `QUIET` | `-q` or `--quiet` | `QUIET` and higher |
28 | * | 4 | `WARNING` | `-w` or `--warn` | `WARNING` and higher |
29 | * | 3 | `LIFECYCLE` | when no option is provided | `LIFECYCLE` and higher |
30 | * | 2 | `INFO` | `-i` or `--info` | `INFO` and higher |
31 | * | 1 | `DEBUG` | `-d` or `--debug` | `DEBUG` and higher |
32 | *
33 | *
34 | * [Logging](https://docs.gradle.org/current/userguide/logging.html)
35 | * ```
36 | * error > quiet > warn > lifecycle > info > debug > [trace]
37 | *
38 | * logger.error("An error log message.")
39 | * logger.quiet("An info log message which is always logged.")
40 | * logger.warn("A warning log message.")
41 | * logger.lifecycle("A lifecycle info log message.")
42 | * logger.info("An info log message.")
43 | * logger.debug("A debug log message.")
44 | * logger.trace("A trace log message.") // Gradle never logs TRACE level logs
45 | * ```
46 | */
47 | @Suppress("unused")
48 | internal object Log {
49 |
50 | private const val TAG = "[>>> Router <<<]"
51 |
52 | private val logger = Logging.getLogger(TAG)
53 |
54 | private const val ANSI_RESET = "\u001B[0m"
55 |
56 | private enum class AnsiColor(val color: String) {
57 | RED("\u001B[31m"), // ANSI_RED
58 | PURPLE("\u001B[35m"), // ANSI_PURPLE
59 | YELLOW("\u001B[33m"), // ANSI_YELLOW
60 | CYAN("\u001B[36m"), // ANSI_CYAN
61 | GREEN("\u001B[32m"), // ANSI_GREEN
62 | BLUE("\u001B[34m"), // ANSI_BLUE
63 | // WHITE("\u001B[37m"), // ANSI_WHITE
64 | }
65 |
66 | private val LogLevel.color: String
67 | get() = when (this) {
68 | LogLevel.DEBUG -> AnsiColor.BLUE.color
69 | LogLevel.INFO -> AnsiColor.GREEN.color
70 | LogLevel.LIFECYCLE -> AnsiColor.CYAN.color
71 | LogLevel.WARN -> AnsiColor.YELLOW.color
72 | LogLevel.QUIET -> AnsiColor.PURPLE.color
73 | LogLevel.ERROR -> AnsiColor.RED.color
74 | }
75 |
76 | private fun log(level: LogLevel, message: Any, vararg args: Any?) {
77 | val tuple = MessageFormatter.arrayFormat(message.toString(), args)
78 | logger.log(level, "🐘 👉${level.color} {}$ANSI_RESET", tuple.message, line())
79 | }
80 |
81 | fun v(message: Any, vararg args: Any?) = log(LogLevel.QUIET, message, args)
82 |
83 | fun e(message: Any, vararg args: Any?) = log(LogLevel.ERROR, message, args)
84 |
85 | fun w(message: Any, vararg args: Any?) = log(LogLevel.WARN, message, args)
86 |
87 | fun wtf(message: Any, vararg args: Any?) = log(LogLevel.LIFECYCLE, message, args)
88 |
89 | fun i(message: Any, vararg args: Any?) = log(LogLevel.INFO, message, args)
90 |
91 | fun d(message: Any, vararg args: Any?) = log(LogLevel.DEBUG, message, args)
92 |
93 | /**
94 | * 格式化输出 JSON
95 | * @param value
96 | */
97 | fun json(value: Any) = log(LogLevel.QUIET, JsonOutput.prettyPrint(value.toString()))
98 |
99 | /**
100 | * 格式化输出 XML
101 | * @param value
102 | */
103 | fun xml(value: Any) = log(LogLevel.QUIET, XmlUtil.serialize(value.toString()))
104 |
105 | private fun line(): String {
106 | return Thread.currentThread().stackTrace[3].let {
107 | val link = "(${it.fileName}:${it.lineNumber})"
108 | val path = "${it.className.substringAfterLast(".")}.${it.methodName}"
109 | // if (path.length + link.length > 80) {
110 | // "${path.take(80 - link.length)}...${link}"
111 | // } else {
112 | // "$path$link"
113 | // }
114 | "$path$link"
115 | }
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/Sample/app/src/main/java/com/chenenyu/router/app/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.app;
2 |
3 | import android.content.Intent;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.text.Editable;
7 | import android.text.TextWatcher;
8 | import android.view.View;
9 | import android.widget.Toast;
10 |
11 | import androidx.appcompat.app.AppCompatActivity;
12 |
13 | import com.chenenyu.router.RouteCallback;
14 | import com.chenenyu.router.RouteStatus;
15 | import com.chenenyu.router.Router;
16 | import com.chenenyu.router.app.databinding.ActivityMainBinding;
17 |
18 | public class MainActivity extends AppCompatActivity implements View.OnClickListener {
19 | private ActivityMainBinding binding;
20 | private String uri;
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | binding = ActivityMainBinding.inflate(getLayoutInflater());
26 | setContentView(binding.getRoot());
27 |
28 | binding.editRoute.addTextChangedListener(new TextWatcher() {
29 | @Override
30 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
31 | }
32 |
33 | @Override
34 | public void onTextChanged(CharSequence s, int start, int before, int count) {
35 | }
36 |
37 | @Override
38 | public void afterTextChanged(Editable s) {
39 | uri = s.toString();
40 | binding.btn0.setText(getString(R.string.go_to, s));
41 | }
42 | });
43 |
44 | Router.addGlobalInterceptor(new GlobalInterceptor());
45 |
46 | // 动态添加路由
47 | Router.handleRouteTable(map -> map.put("dynamic", DynamicActivity.class));
48 |
49 | // Router.handleInterceptorTable(new InterceptorTable() {
50 | // @Override
51 | // public void handle(Map> map) {
52 | // Log.d("InterceptorTable", map.toString());
53 | // }
54 | // });
55 |
56 | // Router.handleTargetInterceptors(new TargetInterceptors() {
57 | // @Override
58 | // public void handle(Map, String[]> map) {
59 | // Log.d("TargetInterceptors", map.toString());
60 | // }
61 | // });
62 | }
63 |
64 | @Override
65 | public void onClick(View v) {
66 | if (v == binding.btn0) {
67 | // 添加结果回调
68 | Router.build(uri).callback((RouteCallback) (status, uri, message) -> {
69 | if (status == RouteStatus.SUCCEED) {
70 | Toast.makeText(MainActivity.this, "succeed: " + uri.toString(), Toast.LENGTH_SHORT).show();
71 | } else {
72 | Toast.makeText(MainActivity.this, "error: " + uri + ", " + message, Toast.LENGTH_SHORT).show();
73 | }
74 | }).go(this);
75 | } else if (v == binding.btn1) {
76 | Router.build(binding.btn1.getText().toString()).go(this);
77 | } else if (v == binding.btn2) {
78 | Router.build("dynamic").go(this);
79 | } else if (v == binding.btn3) {
80 | // Bundle bundle = new Bundle();
81 | // bundle.putString("extra", "Bundle from MainActivity.");
82 | // Router.build("result").requestCode(0).with(bundle).go(this);
83 | Router.build("result").requestCode(0).with("extra", "Bundle from MainActivity.").go(this);
84 | } else if (v == binding.btn4) {
85 | startActivity(new Intent(this, WebActivity.class));
86 | } else if (v == binding.btn5) {
87 | Router.build(Uri.parse("router://implicit?id=9527&status=success")).go(this);
88 | } else if (v == binding.btn6) {
89 | Router.build(binding.btn6.getText().toString()).go(this);
90 | } else if (v == binding.btn7) {
91 | Router.build("module1").go(this);
92 | } else if (v == binding.btn8) {
93 | Router.build("module2").go(this);
94 | } else if (v == binding.btn9) {
95 | Router.build("intercepted").go(this);
96 | } else if (v == binding.btn10) {
97 | Router.build("intercepted").skipInterceptors().go(this);
98 | } else if (v == binding.btn11) {
99 | Router.build("test").addInterceptors("AInterceptor").go(this);
100 | }
101 | }
102 |
103 | @Override
104 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
105 | super.onActivityResult(requestCode, resultCode, data);
106 | if (requestCode == 0 && resultCode == RESULT_OK) {
107 | String result = data.getStringExtra("extra");
108 | Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/gradle/publish.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | if (!project.ext.has('GROUP') || !project.ext.has('ARTIFACT') || !project.ext.has('VERSION')) {
5 | throw new MissingPropertyException("Extra properties['GROUP', 'ARTIFACT', 'VERSION'] are required.")
6 | }
7 |
8 | boolean isAndroidModule = false
9 | boolean isJavaModule = false
10 | if (project.plugins.hasPlugin('com.android.library')) {
11 | isAndroidModule = true
12 | } else if (project.plugins.hasPlugin('java')) {
13 | isJavaModule = true
14 | } else {
15 | throw new UnknownPluginException('Just support android/java projects now.')
16 | }
17 |
18 | // task to generate source.jar
19 | task sourcesJar(type: Jar) {
20 | group 'build'
21 | archiveClassifier.set('sources')
22 | }
23 |
24 | // task to generate javadoc
25 | if (isAndroidModule) {
26 | sourcesJar {
27 | from android.sourceSets.main.java.srcDirs
28 | }
29 |
30 | task javadoc(type: Javadoc) {
31 | group 'documentation'
32 | options.encoding = 'UTF-8'
33 | source = android.sourceSets.main.java.srcDirs
34 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
35 | failOnError false
36 | }
37 | } else if (isJavaModule) {
38 | sourcesJar {
39 | from sourceSets.main.java.srcDirs
40 | }
41 |
42 | javadoc {
43 | options.encoding = 'UTF-8'
44 | failOnError false
45 | }
46 | }
47 |
48 | // task to generate javadoc.jar
49 | task javadocJar(type: Jar, dependsOn: javadoc) {
50 | group 'build'
51 | from javadoc.getDestinationDir()
52 | archiveClassifier.set('javadoc')
53 | }
54 |
55 | //artifacts {
56 | // if (isJavaModule) {
57 | // archives jar
58 | // }
59 | // archives javadocJar
60 | // archives sourcesJar
61 | //}
62 |
63 | afterEvaluate {
64 | publishing {
65 | repositories {
66 | maven {
67 | // https://central.sonatype.org/publish/publish-gradle/
68 | def releasesRepoUrl = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/'
69 | def snapshotsRepoUrl = 'https://s01.oss.sonatype.org/content/repositories/snapshots/'
70 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
71 |
72 | def _ossrhUsername = ''
73 | def _ossrhPassword = ''
74 | if (project.hasProperty('ossrhUsername')) {
75 | _ossrhUsername = ossrhUsername
76 | }
77 | if (project.hasProperty('ossrhPassword')) {
78 | _ossrhPassword = ossrhPassword
79 | }
80 | credentials {
81 | username = _ossrhUsername
82 | password = _ossrhPassword
83 | }
84 | }
85 | }
86 |
87 | publications {
88 | // Creates a Maven publication called "release".
89 | release(MavenPublication) {
90 | if (isAndroidModule) {
91 | // Applies the component for the release build variant.
92 | from components.release
93 | artifact sourcesJar
94 | artifact javadocJar
95 | } else if (isJavaModule) {
96 | from components.java
97 | artifact sourcesJar
98 | artifact javadocJar
99 | }
100 |
101 | groupId project.ext.GROUP
102 | artifactId project.ext.ARTIFACT
103 | version project.ext.VERSION
104 | pom {
105 | if (isAndroidModule) {
106 | packaging 'aar'
107 | } else if (isJavaModule) {
108 | packaging 'jar'
109 | }
110 | name = "${project.ext.GROUP}:${project.ext.ARTIFACT}"
111 | description = 'Awesome android router library.'
112 | url = 'https://github.com/chenenyu/Router'
113 | scm {
114 | url = 'https://github.com/chenenyu/Router'
115 | connection = 'scm:git@github.com:chenenyu/Router.git'
116 | developerConnection = 'scm:git@github.com:chenenyu/Router.git'
117 | }
118 | licenses {
119 | license {
120 | name = 'The Apache Software License, Version 2.0'
121 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
122 | }
123 | }
124 | developers {
125 | developer {
126 | id = 'chenenyu'
127 | name = 'chenenyu'
128 | url = 'https://github.com/chenenyu'
129 | }
130 | }
131 | }
132 | }
133 | }
134 | }
135 |
136 | signing {
137 | sign publishing.publications.release
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/RealRouter.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.fragment.app.Fragment;
10 |
11 | import com.chenenyu.router.chain.AppInterceptorsHandler;
12 | import com.chenenyu.router.chain.AttrsProcessor;
13 | import com.chenenyu.router.chain.BaseValidator;
14 | import com.chenenyu.router.chain.FragmentProcessor;
15 | import com.chenenyu.router.chain.FragmentValidator;
16 | import com.chenenyu.router.chain.IntentProcessor;
17 | import com.chenenyu.router.chain.IntentValidator;
18 | import com.chenenyu.router.util.RLog;
19 |
20 | import java.util.Collections;
21 | import java.util.LinkedList;
22 | import java.util.List;
23 |
24 | /**
25 | * Core of router.
26 | *
27 | * Created by chenenyu on 2017/3/30.
28 | */
29 | final class RealRouter extends AbsRouter {
30 | private final RouteInterceptor mBaseValidator = new BaseValidator();
31 | private final RouteInterceptor mIntentValidator = new IntentValidator();
32 | private final RouteInterceptor mFragmentValidator = new FragmentValidator();
33 | private final RouteInterceptor mIntentProcessor = new IntentProcessor();
34 | private final RouteInterceptor mFragmentProcessor = new FragmentProcessor();
35 | private final RouteInterceptor mAppInterceptorsHandler = new AppInterceptorsHandler();
36 | private final RouteInterceptor mAttrsProcessor = new AttrsProcessor();
37 |
38 | private static final ThreadLocal mRouterThreadLocal = new ThreadLocal() {
39 | @Override
40 | protected RealRouter initialValue() {
41 | return new RealRouter();
42 | }
43 | };
44 |
45 | private RealRouter() {
46 | }
47 |
48 | static RealRouter getInstance() {
49 | return mRouterThreadLocal.get();
50 | }
51 |
52 | private void callback(RouteResponse response) {
53 | if (response.getStatus() != RouteStatus.SUCCEED) {
54 | RLog.w(response.getMessage());
55 | }
56 | if (mRouteRequest.getRouteCallback() != null) {
57 | mRouteRequest.getRouteCallback().callback(
58 | response.getStatus(), mRouteRequest.getUri(), response.getMessage());
59 | }
60 | }
61 |
62 | @Override
63 | public Fragment getFragment(@NonNull Object source) {
64 | List interceptors = new LinkedList<>();
65 | Collections.addAll(interceptors, mBaseValidator, mFragmentValidator,
66 | mFragmentProcessor, mAppInterceptorsHandler, mAttrsProcessor);
67 | RealInterceptorChain chain = new RealInterceptorChain(source, mRouteRequest, interceptors);
68 | RouteResponse response = chain.process();
69 | callback(response);
70 | return (Fragment) response.getResult();
71 | }
72 |
73 | @Override
74 | public Intent getIntent(@NonNull Object source) {
75 | List interceptors = new LinkedList<>();
76 | Collections.addAll(interceptors, mBaseValidator, mIntentValidator,
77 | mIntentProcessor, mAppInterceptorsHandler, mAttrsProcessor);
78 | RealInterceptorChain chain = new RealInterceptorChain(source, mRouteRequest, interceptors);
79 | RouteResponse response = chain.process();
80 | callback(response);
81 | return (Intent) response.getResult();
82 | }
83 |
84 | @Override
85 | public void go(Context context) {
86 | Intent intent = getIntent(context);
87 | if (intent != null) {
88 | Bundle options = mRouteRequest.getActivityOptionsBundle();
89 | if (context instanceof Activity) {
90 | ((Activity) context).startActivityForResult(intent, mRouteRequest.getRequestCode(), options);
91 | if (mRouteRequest.getEnterAnim() >= 0 && mRouteRequest.getExitAnim() >= 0) {
92 | ((Activity) context).overridePendingTransition(
93 | mRouteRequest.getEnterAnim(), mRouteRequest.getExitAnim());
94 | }
95 | } else {
96 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
97 | context.startActivity(intent, options);
98 | }
99 | }
100 | }
101 |
102 | @Override
103 | public void go(Fragment fragment) {
104 | Intent intent = getIntent(fragment);
105 | if (intent != null) {
106 | Bundle options = mRouteRequest.getActivityOptionsBundle();
107 | if (mRouteRequest.getRequestCode() < 0) {
108 | fragment.startActivity(intent, options);
109 | } else {
110 | fragment.startActivityForResult(intent, mRouteRequest.getRequestCode(), options);
111 | }
112 | if (mRouteRequest.getEnterAnim() >= 0 && mRouteRequest.getExitAnim() >= 0
113 | && fragment.getActivity() != null) {
114 | // Add transition animation.
115 | fragment.getActivity().overridePendingTransition(
116 | mRouteRequest.getEnterAnim(), mRouteRequest.getExitAnim());
117 | }
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/RouterInitializer.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import android.content.Context;
4 | import android.content.pm.ApplicationInfo;
5 | import android.content.pm.PackageManager;
6 | import android.os.Bundle;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.startup.Initializer;
10 |
11 | import com.chenenyu.router.template.InterceptorTable;
12 | import com.chenenyu.router.template.RouteTable;
13 | import com.chenenyu.router.template.TargetInterceptorsTable;
14 | import com.chenenyu.router.util.RLog;
15 |
16 | import java.util.Collections;
17 | import java.util.List;
18 | import java.util.Set;
19 |
20 | /**
21 | * @author chenenyu
22 | * @date 2021/10/26
23 | */
24 | public class RouterInitializer implements Initializer {
25 | /**
26 | * meta-data const value in manifest.
27 | */
28 | private static final String META_VALUE = "com.chenenyu.router.moduleName";
29 |
30 | private static final String PACKAGE_PREFIX = "com.chenenyu.router.apt";
31 |
32 | @NonNull
33 | @Override
34 | public Void create(@NonNull Context context) {
35 | init(context);
36 | return null;
37 | }
38 |
39 | @NonNull
40 | @Override
41 | public List>> dependencies() {
42 | return Collections.emptyList();
43 | }
44 |
45 | private void init(Context context) {
46 | try {
47 | ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
48 | Bundle metadata = applicationInfo.metaData;
49 | if (metadata != null) {
50 | Set keys = metadata.keySet();
51 | for (String key : keys) {
52 | String value = metadata.getString(key, null);
53 | if (META_VALUE.equals(value)) {
54 | String validModuleName = key.replace(".", "_").replace("-", "_");
55 | injectRouteTable(validModuleName);
56 | injectInterceptorTable(validModuleName);
57 | injectTargetInterceptorsTable(validModuleName);
58 | }
59 | }
60 | }
61 | } catch (PackageManager.NameNotFoundException e) {
62 | e.printStackTrace();
63 | }
64 |
65 | }
66 |
67 | private String capitalize(CharSequence self) {
68 | return self.length() == 0 ? "" :
69 | "" + Character.toUpperCase(self.charAt(0)) + self.subSequence(1, self.length());
70 | }
71 |
72 | private void injectRouteTable(String moduleName) {
73 | try {
74 | Class> clazz = Class.forName(PACKAGE_PREFIX + "." + capitalize(moduleName) + "RouteTable");
75 | if (RouteTable.class.isAssignableFrom(clazz)) {
76 | Class extends RouteTable> component = (Class extends RouteTable>) clazz;
77 | RouteTable instance = component.newInstance();
78 | instance.handle(AptHub.routeTable);
79 | } else {
80 | RLog.w(clazz.getCanonicalName() + " does not implements RouteTable.");
81 | }
82 | } catch (ClassNotFoundException e) {
83 | // pass
84 | } catch (IllegalAccessException e) {
85 | e.printStackTrace();
86 | } catch (InstantiationException e) {
87 | e.printStackTrace();
88 | }
89 | }
90 |
91 | private void injectInterceptorTable(String moduleName) {
92 | try {
93 | Class> clazz = Class.forName(PACKAGE_PREFIX + "." + capitalize(moduleName) + "InterceptorTable");
94 | if (InterceptorTable.class.isAssignableFrom(clazz)) {
95 | Class extends InterceptorTable> component = (Class extends InterceptorTable>) clazz;
96 | InterceptorTable instance = component.newInstance();
97 | instance.handle(AptHub.interceptorTable);
98 | } else {
99 | RLog.w(clazz.getCanonicalName() + " does not implements InterceptorTable.");
100 | }
101 | } catch (ClassNotFoundException e) {
102 | // pass
103 | } catch (IllegalAccessException e) {
104 | e.printStackTrace();
105 | } catch (InstantiationException e) {
106 | e.printStackTrace();
107 | }
108 | }
109 |
110 | private void injectTargetInterceptorsTable(String moduleName) {
111 | try {
112 | Class> clazz = Class.forName(PACKAGE_PREFIX + "." + capitalize(moduleName) + "TargetInterceptorsTable");
113 | if (TargetInterceptorsTable.class.isAssignableFrom(clazz)) {
114 | Class extends TargetInterceptorsTable> component = (Class extends TargetInterceptorsTable>) clazz;
115 | TargetInterceptorsTable instance = component.newInstance();
116 | instance.handle(AptHub.targetInterceptorsTable);
117 | } else {
118 | RLog.w(clazz.getCanonicalName() + " does not implements TargetInterceptorsTable.");
119 | }
120 | } catch (ClassNotFoundException e) {
121 | // pass
122 | } catch (IllegalAccessException e) {
123 | e.printStackTrace();
124 | } catch (InstantiationException e) {
125 | e.printStackTrace();
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/chain/IntentProcessor.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.chain;
2 |
3 | import android.content.Intent;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import com.chenenyu.router.AptHub;
8 | import com.chenenyu.router.MatcherRegistry;
9 | import com.chenenyu.router.RealInterceptorChain;
10 | import com.chenenyu.router.RouteInterceptor;
11 | import com.chenenyu.router.RouteRequest;
12 | import com.chenenyu.router.RouteResponse;
13 | import com.chenenyu.router.RouteStatus;
14 | import com.chenenyu.router.matcher.AbsExplicitMatcher;
15 | import com.chenenyu.router.matcher.AbsImplicitMatcher;
16 | import com.chenenyu.router.matcher.AbsMatcher;
17 | import com.chenenyu.router.util.RLog;
18 |
19 | import java.util.List;
20 | import java.util.Map;
21 | import java.util.Set;
22 |
23 | /**
24 | * Created by chenenyu on 2018/6/15.
25 | */
26 | public class IntentProcessor implements RouteInterceptor {
27 | @NonNull
28 | @Override
29 | public RouteResponse intercept(Chain chain) {
30 | RealInterceptorChain realChain = (RealInterceptorChain) chain;
31 | RouteRequest request = chain.getRequest();
32 |
33 | Intent intent = null;
34 | if (AptHub.routeTable.isEmpty()) {
35 | List implicitMatcherList = MatcherRegistry.getImplicitMatcher();
36 |
37 | for (AbsImplicitMatcher implicitMatcher : implicitMatcherList) {
38 | if (implicitMatcher.match(chain.getContext(), request.getUri(), null, request)) {
39 | RLog.i(String.format("{uri=%s, matcher=%s}",
40 | chain.getRequest().getUri(), implicitMatcher.getClass().getCanonicalName()));
41 | realChain.setTargetClass(null);
42 | Object result = implicitMatcher.generate(chain.getContext(), request.getUri(), null);
43 | if (result instanceof Intent) {
44 | intent = (Intent) result;
45 | realChain.setTargetInstance(intent);
46 | } else {
47 | return RouteResponse.assemble(RouteStatus.FAILED, String.format(
48 | "The matcher can't generate an intent for uri: %s",
49 | request.getUri().toString()));
50 | }
51 | break;
52 | }
53 | }
54 | } else {
55 | List matcherList = MatcherRegistry.getMatcher();
56 | List explicitMatcherList = MatcherRegistry.getExplicitMatcher();
57 | Set>> entries = AptHub.routeTable.entrySet();
58 |
59 | MATCHER:
60 | for (AbsMatcher matcher : request.isSkipImplicitMatcher() ? explicitMatcherList : matcherList) {
61 | boolean isImplicit = matcher instanceof AbsImplicitMatcher;
62 | if (isImplicit) {
63 | if (matcher.match(chain.getContext(), request.getUri(), null, request)) {
64 | RLog.i(String.format("{uri=%s, matcher=%s}",
65 | chain.getRequest().getUri(), matcher.getClass().getCanonicalName()));
66 | realChain.setTargetClass(null);
67 | Object result = matcher.generate(chain.getContext(), request.getUri(), null);
68 | if (result instanceof Intent) {
69 | intent = (Intent) result;
70 | realChain.setTargetInstance(intent);
71 | } else {
72 | return RouteResponse.assemble(RouteStatus.FAILED, String.format(
73 | "The matcher can't generate an intent for uri: %s",
74 | request.getUri().toString()));
75 | }
76 | break;
77 | }
78 | } else {
79 | for (Map.Entry> entry : entries) {
80 | if (matcher.match(chain.getContext(), request.getUri(), entry.getKey(), request)) {
81 | RLog.i(String.format("{uri=%s, matcher=%s}",
82 | chain.getRequest().getUri(), matcher.getClass().getCanonicalName()));
83 | realChain.setTargetClass(entry.getValue());
84 | Object result = matcher.generate(chain.getContext(), request.getUri(), entry.getValue());
85 | if (result instanceof Intent) {
86 | intent = (Intent) result;
87 | realChain.setTargetInstance(intent);
88 | } else {
89 | return RouteResponse.assemble(RouteStatus.FAILED, String.format(
90 | "The matcher can't generate an intent for uri: %s",
91 | request.getUri().toString()));
92 | }
93 | break MATCHER;
94 | }
95 | }
96 | }
97 |
98 | }
99 | }
100 |
101 | if (intent == null) {
102 | return RouteResponse.assemble(RouteStatus.NOT_FOUND, String.format(
103 | "Can't find an activity that matches the given uri: %s",
104 | request.getUri().toString()));
105 | }
106 | return chain.process();
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/router-plugin/src/main/java/com/l3gacy/plugin/router/asm/RouterMethodVisitor.kt:
--------------------------------------------------------------------------------
1 | package com.l3gacy.plugin.router.asm
2 |
3 | import org.objectweb.asm.MethodVisitor
4 | import org.objectweb.asm.commons.AdviceAdapter
5 |
6 | /**
7 | *
8 | * Created by J!nl!n on 2022/10/18.
9 | *
10 | * Copyright © 2022 J!nl!n™ Inc. All rights reserved.
11 | *
12 | */
13 | internal class RouterMethodVisitor(
14 | methodVisitor: MethodVisitor,
15 | private val records: Map>,
16 | access: Int,
17 | name: String?,
18 | descriptor: String?
19 | ) : AdviceAdapter(ASM9, methodVisitor, access, name, descriptor) {
20 |
21 | // private val records = listOf(
22 | // "com/chenenyu/router/template/RouteTable" to setOf(
23 | // "com/chenenyu/router/apt/Module1RouteTable",
24 | // "com/chenenyu/router/apt/Module2RouteTable",
25 | // "com/chenenyu/router/apt/AppRouteTable",
26 | // ),
27 | // "com/chenenyu/router/template/InterceptorTable" to setOf("com/chenenyu/router/apt/AppInterceptorTable"),
28 | // "com/chenenyu/router/template/TargetInterceptorsTable" to setOf("com/chenenyu/router/apt/AppTargetInterceptorsTable")
29 | // )
30 |
31 | /**
32 | * Java code:
33 | * ``` java
34 | * static {
35 | * new AppRouteTable().handle(routeTable);
36 | * new AppInterceptorTable().handle(interceptorTable);
37 | * new AppTargetInterceptorsTable().handle(targetInterceptorsTable);
38 | * // other modules' table...
39 | *}
40 | * ```
41 | * ``` java
42 | * ASM code:
43 | *
44 | * mv.visitTypeInsn(Opcodes.NEW, "com/chenenyu/router/apt/AppRouteTable");
45 | * mv.visitInsn(Opcodes.DUP);
46 | * mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "com/chenenyu/router/apt/AppRouteTable", "", "()V", false);
47 | * mv.visitFieldInsn(Opcodes.GETSTATIC, "com/chenenyu/router/app/AsmTest", "routeTable", "Ljava/util/Map;");
48 | * mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/chenenyu/router/apt/AppRouteTable", "handle", "(Ljava/util/Map;)V", false);
49 | * mv.visitTypeInsn(Opcodes.NEW, "com/chenenyu/router/apt/AppInterceptorTable");
50 | * mv.visitInsn(Opcodes.DUP);
51 | * mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "com/chenenyu/router/apt/AppInterceptorTable", "", "()V", false);
52 | * mv.visitFieldInsn(Opcodes.GETSTATIC, "com/chenenyu/router/app/AsmTest", "interceptorTable", "Ljava/util/Map;");
53 | * mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/chenenyu/router/apt/AppInterceptorTable", "handle", "(Ljava/util/Map;)V", false);
54 | * mv.visitTypeInsn(Opcodes.NEW, "com/chenenyu/router/apt/AppTargetInterceptorsTable");
55 | * mv.visitInsn(Opcodes.DUP);
56 | * mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "com/chenenyu/router/apt/AppTargetInterceptorsTable", "", "()V", false);
57 | * mv.visitFieldInsn(Opcodes.GETSTATIC, "com/chenenyu/router/app/AsmTest", "targetInterceptorsTable", "Ljava/util/Map;");
58 | * mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/chenenyu/router/apt/AppTargetInterceptorsTable", "handle", "(Ljava/util/Map;)V", false);
59 | * mv.visitInsn(Opcodes.RETURN);
60 | * ```
61 | */
62 | override fun visitInsn(opcode: Int) {
63 | if (opcode == RETURN) {
64 | // Log.v("visitInsn 我被执行了")
65 | records.forEach { (key, value) ->
66 | // Log.v("$key - $value")
67 | value.forEach { classname ->
68 | mv.visitTypeInsn(NEW, classname)
69 | mv.visitInsn(DUP)
70 | mv.visitMethodInsn(INVOKESPECIAL, classname, "", "()V", false)
71 | mv.visitFieldInsn(
72 | GETSTATIC,
73 | TEMPLATE_APT_HUB,
74 | key.getFieldNameByInterface(),
75 | "Ljava/util/Map;"
76 | )
77 | mv.visitMethodInsn(INVOKEVIRTUAL, classname, "handle", "(Ljava/util/Map;)V", false)
78 | }
79 | }
80 |
81 | // records.forEach {
82 | // Log.d("${it.first} - ${it.second}")
83 | // it.second.forEach { classname ->
84 | // mv.visitTypeInsn(NEW, classname)
85 | // mv.visitInsn(DUP)
86 | // mv.visitMethodInsn(INVOKESPECIAL, classname, "", "()V", false)
87 | // mv.visitFieldInsn(
88 | // GETSTATIC,
89 | // TEMPLATE_APT_HUB,
90 | // it.getFieldNameByInterface(),
91 | // "Ljava/util/Map;"
92 | // )
93 | // mv.visitMethodInsn(INVOKEVIRTUAL, classname, "handle", "(Ljava/util/Map;)V", false)
94 | // }
95 | // }
96 | }
97 | super.visitInsn(opcode)
98 | }
99 |
100 | @Synchronized
101 | private fun String.getFieldNameByInterface(): String {
102 | return when (this) {
103 | TEMPLATE_ROUTE_TABLE -> "routeTable"
104 | TEMPLATE_INTERCEPTOR_TABLE -> "interceptorTable"
105 | TEMPLATE_TARGET_INTERCEPTORS_TABLE -> "targetInterceptorsTable"
106 | else -> "error"
107 | }
108 | }
109 |
110 | @Synchronized
111 | private fun Pair>.getFieldNameByInterface(): String {
112 | return when (first) {
113 | TEMPLATE_ROUTE_TABLE -> "routeTable"
114 | TEMPLATE_INTERCEPTOR_TABLE -> "interceptorTable"
115 | TEMPLATE_TARGET_INTERCEPTORS_TABLE -> "targetInterceptorsTable"
116 | else -> "error"
117 | }
118 | }
119 |
120 | private companion object {
121 | const val TEMPLATE_APT_HUB = "com/chenenyu/router/AptHub"
122 | const val TEMPLATE_ROUTE_TABLE = "com/chenenyu/router/template/RouteTable"
123 | const val TEMPLATE_INTERCEPTOR_TABLE = "com/chenenyu/router/template/InterceptorTable"
124 | const val TEMPLATE_TARGET_INTERCEPTORS_TABLE =
125 | "com/chenenyu/router/template/TargetInterceptorsTable"
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/compiler/src/main/java/com/chenenyu/router/compiler/processor/InterceptorProcessor.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router.compiler.processor;
2 |
3 | import com.chenenyu.router.annotation.Interceptor;
4 | import com.chenenyu.router.compiler.util.Logger;
5 | import com.squareup.javapoet.ClassName;
6 | import com.squareup.javapoet.JavaFile;
7 | import com.squareup.javapoet.MethodSpec;
8 | import com.squareup.javapoet.ParameterSpec;
9 | import com.squareup.javapoet.ParameterizedTypeName;
10 | import com.squareup.javapoet.TypeSpec;
11 | import com.squareup.javapoet.WildcardTypeName;
12 |
13 | import java.io.IOException;
14 | import java.util.HashMap;
15 | import java.util.HashSet;
16 | import java.util.Map;
17 | import java.util.Set;
18 |
19 | import javax.annotation.processing.AbstractProcessor;
20 | import javax.annotation.processing.ProcessingEnvironment;
21 | import javax.annotation.processing.RoundEnvironment;
22 | import javax.annotation.processing.SupportedAnnotationTypes;
23 | import javax.annotation.processing.SupportedOptions;
24 | import javax.lang.model.SourceVersion;
25 | import javax.lang.model.element.Element;
26 | import javax.lang.model.element.Modifier;
27 | import javax.lang.model.element.TypeElement;
28 |
29 | import static com.chenenyu.router.compiler.util.Constants.APT_PACKAGE_NAME;
30 | import static com.chenenyu.router.compiler.util.Constants.CLASS_JAVA_DOC;
31 | import static com.chenenyu.router.compiler.util.Constants.INTERCEPTOR_ANNOTATION_TYPE;
32 | import static com.chenenyu.router.compiler.util.Constants.INTERCEPTOR_FULL_NAME;
33 | import static com.chenenyu.router.compiler.util.Constants.INTERCEPTOR_TABLE;
34 | import static com.chenenyu.router.compiler.util.Constants.INTERCEPTOR_TABLE_FULL_NAME;
35 | import static com.chenenyu.router.compiler.util.Constants.METHOD_HANDLE;
36 | import static com.chenenyu.router.compiler.util.Constants.OPTION_LOGGABLE;
37 | import static com.chenenyu.router.compiler.util.Constants.OPTION_MODULE_NAME;
38 |
39 | /**
40 | * {@link Interceptor} annotation processor.
41 | *
42 | * Created by chenenyu on 2017/3/6.
43 | */
44 | @SupportedAnnotationTypes(INTERCEPTOR_ANNOTATION_TYPE)
45 | @SupportedOptions({OPTION_MODULE_NAME, OPTION_LOGGABLE})
46 | public class InterceptorProcessor extends AbstractProcessor {
47 | private Logger mLogger;
48 | private String mModuleName;
49 |
50 | @Override
51 | public SourceVersion getSupportedSourceVersion() {
52 | return SourceVersion.latest();
53 | }
54 |
55 | @Override
56 | public synchronized void init(ProcessingEnvironment processingEnvironment) {
57 | super.init(processingEnvironment);
58 | mModuleName = processingEnvironment.getOptions().get(OPTION_MODULE_NAME);
59 | String loggable = processingEnvironment.getOptions().get(OPTION_LOGGABLE);
60 | mLogger = new Logger(processingEnvironment.getMessager(), "true".equals(loggable));
61 | }
62 |
63 | @Override
64 | public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
65 | Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Interceptor.class);
66 | if (elements == null || elements.isEmpty()) {
67 | return true;
68 | }
69 | mLogger.info(String.format(">>> %s: InterceptorProcessor begin... <<<", mModuleName));
70 | // 合法的TypeElement集合
71 | Set typeElements = new HashSet<>();
72 | for (Element element : elements) {
73 | if (validateElement(element)) {
74 | typeElements.add((TypeElement) element);
75 | } else {
76 | mLogger.error(element, String.format("The annotated element is not a implementation class of %s",
77 | INTERCEPTOR_FULL_NAME));
78 | }
79 | }
80 |
81 | if (mModuleName != null) {
82 | String validModuleName = mModuleName.replace(".", "_").replace("-", "_");
83 | generateInterceptors(validModuleName, typeElements);
84 | } else {
85 | throw new RuntimeException(String.format("No option `%s` passed to Interceptor annotation processor.", OPTION_MODULE_NAME));
86 | }
87 | mLogger.info(String.format(">>> %s: InterceptorProcessor end. <<<", mModuleName));
88 | return true;
89 | }
90 |
91 | private boolean validateElement(Element element) {
92 | return element.getKind().isClass() && processingEnv.getTypeUtils().isAssignable(element.asType(),
93 | processingEnv.getElementUtils().getTypeElement(INTERCEPTOR_FULL_NAME).asType());
94 | }
95 |
96 | private void generateInterceptors(String moduleName, Set elements) {
97 | /*
98 | * params
99 | */
100 | TypeElement interceptorType = processingEnv.getElementUtils().getTypeElement(INTERCEPTOR_FULL_NAME);
101 | // Map> map
102 | ParameterizedTypeName mapTypeName = ParameterizedTypeName.get(ClassName.get(Map.class),
103 | ClassName.get(String.class), ParameterizedTypeName.get(ClassName.get(Class.class),
104 | WildcardTypeName.subtypeOf(ClassName.get(interceptorType))));
105 | ParameterSpec mapParameterSpec = ParameterSpec.builder(mapTypeName, "map").build();
106 | /*
107 | * method
108 | */
109 | MethodSpec.Builder handleInterceptors = MethodSpec.methodBuilder(METHOD_HANDLE)
110 | .addAnnotation(Override.class)
111 | .addModifiers(Modifier.PUBLIC)
112 | .addParameter(mapParameterSpec);
113 |
114 | Map interceptorRecorder = new HashMap<>();
115 | for (TypeElement element : elements) {
116 | mLogger.info(String.format("Found interceptor: %s", element.getQualifiedName()));
117 | Interceptor interceptor = element.getAnnotation(Interceptor.class);
118 | String name = interceptor.value();
119 | if (interceptorRecorder.containsKey(name)) {
120 | throw new RuntimeException(String.format("Duplicate interceptor name: %s[%s, %s]",
121 | name, element.getQualifiedName(), interceptorRecorder.get(name)));
122 | }
123 | handleInterceptors.addStatement("map.put($S, $T.class)", name, ClassName.get(element));
124 | interceptorRecorder.put(name, element.getQualifiedName().toString());
125 | }
126 |
127 | /*
128 | * class
129 | */
130 | TypeElement interfaceType = processingEnv.getElementUtils().getTypeElement(INTERCEPTOR_TABLE_FULL_NAME);
131 | TypeSpec type = TypeSpec.classBuilder(capitalize(moduleName) + INTERCEPTOR_TABLE)
132 | .addSuperinterface(ClassName.get(interfaceType))
133 | .addModifiers(Modifier.PUBLIC)
134 | .addMethod(handleInterceptors.build())
135 | .addJavadoc(CLASS_JAVA_DOC)
136 | .build();
137 |
138 | try {
139 | JavaFile.builder(APT_PACKAGE_NAME, type).build().writeTo(processingEnv.getFiler());
140 | } catch (IOException e) {
141 | e.printStackTrace();
142 | }
143 | }
144 |
145 | private String capitalize(CharSequence self) {
146 | return self.length() == 0 ? "" :
147 | "" + Character.toUpperCase(self.charAt(0)) + self.subSequence(1, self.length());
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/router-plugin/src/main/java/com/l3gacy/plugin/router/RouterPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.l3gacy.plugin.router
2 |
3 | import com.android.build.api.artifact.ScopedArtifact
4 | import com.android.build.api.variant.AndroidComponentsExtension
5 | import com.android.build.api.variant.ScopedArtifacts
6 | import com.android.build.gradle.AppPlugin
7 | import com.android.build.gradle.BasePlugin
8 | import com.l3gacy.plugin.internal.Log
9 | import com.l3gacy.plugin.internal.capitalize
10 | import com.l3gacy.plugin.internal.property
11 | import com.l3gacy.plugin.router.internal.apt
12 | import com.l3gacy.plugin.router.internal.kapt
13 | import com.l3gacy.plugin.router.task.RouterClassesTask
14 | import org.gradle.api.Plugin
15 | import org.gradle.api.Project
16 | import org.gradle.kotlin.dsl.register
17 |
18 | /**
19 | *
20 | * Created by J!nl!n on 2022/12/9.
21 | *
22 | * Copyright © 2022 J!nl!n™ Inc. All rights reserved.
23 | *
24 | */
25 | class RouterPlugin : Plugin {
26 |
27 | override fun apply(target: Project) {
28 | with(target) {
29 | // com.android.application -> AppPlugin::class.java
30 | // com.android.library -> LibraryPlugin::class.java
31 | // com.android.test -> TestPlugin::class.java
32 | // com.android.dynamic-feature, added in 3.2 -> DynamicFeaturePlugin::class.java
33 | require(plugins.hasPlugin(BasePlugin::class.java).not()) {
34 | "android plugin required."
35 | }
36 |
37 | val loggable = property(KEY_ROUTER_COMPILER_LOG, false).toBoolean()
38 |
39 | val hasKotlin = KOTLIN_PLUGINS.any(plugins::hasPlugin)
40 | if (hasKotlin) {
41 | if (KAPT_PLUGINS.all(plugins::hasPlugin).not()) {
42 | plugins.apply("kotlin-kapt")
43 | }
44 | kapt(loggable)
45 | } else {
46 | apt(loggable)
47 | }
48 |
49 | // Add dependencies
50 | dependencies.add("implementation", NOTATION_ROUTER)
51 | dependencies.add(
52 | if (hasKotlin) "kapt" else "annotationProcessor", NOTATION_COMPILER
53 | )
54 |
55 | val debuggable = property(KEY_ROUTER_DEBUGGABLE, false).toBoolean()
56 | Log.v("debuggable = $debuggable")
57 |
58 | val dump = property(KEY_DUMP_ROUTER_TABLE, false).toBoolean()
59 |
60 | plugins.withType(AppPlugin::class.java) {
61 | val androidComponents = extensions.findByType(AndroidComponentsExtension::class.java)
62 | androidComponents?.onVariants { variant ->
63 | val variantName = variant.name.capitalize
64 | val name = "transformClassesWithRouterFor$variantName"
65 | val taskProvider = tasks.register(name) {
66 | group = "router"
67 | description = "Transform ApiHub class for ${variant.name}"
68 | if (dump) {
69 | doc.set(layout.buildDirectory.file("router/routers-${variant.name}.json"))
70 | }
71 | }
72 | variant.artifacts.forScope(ScopedArtifacts.Scope.ALL)
73 | .use(taskProvider)
74 | .toTransform(
75 | ScopedArtifact.CLASSES,
76 | RouterClassesTask::jars,
77 | RouterClassesTask::dirs,
78 | RouterClassesTask::output,
79 | )
80 | }
81 | }
82 | }
83 | }
84 |
85 | @Suppress("SpellCheckingInspection")
86 | private companion object {
87 |
88 | private const val DEFAULT_ROUTER_RUNTIME_VERSION = "1.7.6"
89 | private const val DEFAULT_ROUTER_COMPILER_VERSION = "1.7.6"
90 |
91 | private const val NOTATION_ROUTER = "com.chenenyu.router:router:$DEFAULT_ROUTER_RUNTIME_VERSION"
92 | private const val NOTATION_COMPILER = "com.chenenyu.router:compiler:$DEFAULT_ROUTER_COMPILER_VERSION"
93 |
94 | /**
95 | * 打印路由插件日志的键值,默认关闭。可通过在 gradle.properties 文件中声明开启
96 | * ```properties
97 | * router.debuggable = true
98 | * ```
99 | */
100 | private const val KEY_ROUTER_DEBUGGABLE = "router.debuggable"
101 |
102 | /**
103 | * 打印生成器日志的键值,默认关闭。可通过在 gradle.properties 文件中声明开启
104 | * ```properties
105 | * router.compiler.log = true
106 | * ```
107 | */
108 | private const val KEY_ROUTER_COMPILER_LOG = "router.compiler.log"
109 |
110 | /**
111 | * 生成路由表json文件的键值,默认关闭。可通过在 gradle.properties 文件中声明开启
112 | * ```properties
113 | * router.table.dump = true
114 | * ```
115 | */
116 | private const val KEY_DUMP_ROUTER_TABLE = "router.table.dump"
117 |
118 | // https://github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/resources/META-INF/gradle-plugins/kotlin-android.properties
119 | private val KOTLIN_PLUGINS = arrayOf(
120 | "kotlin-android",
121 | "org.jetbrains.kotlin.android"
122 | )
123 |
124 | // https://github.com/JetBrains/kotlin/blob/master/libraries/tools/kotlin-gradle-plugin/src/common/resources/META-INF/gradle-plugins/kotlin-kapt.properties
125 | private val KAPT_PLUGINS = arrayOf(
126 | "kotlin-kapt",
127 | "org.jetbrains.kotlin.kapt"
128 | )
129 |
130 | // private val excludes = listOf(
131 | // // 类库本身不做插桩操作
132 | // "com/chenenyu/router/annotation/**",
133 | // "org/objectweb/asm/**",
134 | // "io/reactivex/**",
135 | // "com/facebook/**",
136 | // "com/google/**",
137 | // "android/**",
138 | // "androidx/**",
139 | // "kotlin/**",
140 | // "kotlinx/**",
141 | // "com/android/**",
142 | // "android/support/**",
143 | // "okhttp/**",
144 | // "okhttp3/**", // https://github.com/square/okhttp
145 | // "retrofit2/**", // https://github.com/square/retrofit
146 | // "okio/**", // https://github.com/square/okio
147 | // "dagger/**",
148 | // "org/**",
149 | // "**/R",
150 | // "**/R$**",
151 | // "**/BuildConfig",
152 | // "**/Manifest",
153 | // "**/databinding/**",
154 | // "com/bumptech/glide/**", // https://github.com/bumptech/glide
155 | // "com/airbnb/lottie/**", // https://github.com/airbnb/lottie-android
156 | // "com/squareup/**", // https://github.com/airbnb/lottie-android
157 | // "org/koin/**", // https://github.com/InsertKoinIO/koin
158 | // "coil/**", // https://github.com/coil-kt/coil
159 | // "com/alipay/**",
160 | // "com/amap/**",
161 | // "com/tencent/**",
162 | // "com/twitter/**",
163 | // "com/datatheorem/android/**",
164 | // "de/greenrobot/**",
165 | // "io/github/**",
166 | // "net/sourceforge/**",
167 | // "com/mobeta/android/dslv/**",
168 | // "com/beaglebuddy/**"
169 | // )
170 | }
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/router-plugin/src/main/java/com/l3gacy/plugin/router/task/RouterClassesTask.kt:
--------------------------------------------------------------------------------
1 | package com.l3gacy.plugin.router.task
2 |
3 | import com.android.build.gradle.internal.tasks.BuildAnalyzer
4 | import com.android.buildanalyzer.common.TaskCategory
5 | import com.joom.grip.Grip
6 | import com.joom.grip.GripFactory
7 | import com.joom.grip.classes
8 | import com.joom.grip.interfaces
9 | import com.joom.grip.mirrors.getType
10 | import com.l3gacy.plugin.internal.Log
11 | import com.l3gacy.plugin.router.internal.separator
12 | import com.l3gacy.plugin.router.asm.RouterClassVisitor
13 | import com.l3gacy.plugin.internal.Stopwatch
14 | import groovy.json.JsonOutput
15 | import org.gradle.api.DefaultTask
16 | import org.gradle.api.file.Directory
17 | import org.gradle.api.file.RegularFile
18 | import org.gradle.api.file.RegularFileProperty
19 | import org.gradle.api.provider.ListProperty
20 | import org.gradle.api.tasks.CacheableTask
21 | import org.gradle.api.tasks.InputFiles
22 | import org.gradle.api.tasks.Optional
23 | import org.gradle.api.tasks.OutputFile
24 | import org.gradle.api.tasks.PathSensitive
25 | import org.gradle.api.tasks.PathSensitivity
26 | import org.gradle.api.tasks.TaskAction
27 | import org.gradle.work.Incremental
28 | import org.gradle.work.InputChanges
29 | import org.objectweb.asm.ClassReader
30 | import org.objectweb.asm.ClassWriter
31 | import org.objectweb.asm.Opcodes
32 | import java.io.BufferedOutputStream
33 | import java.io.File
34 | import java.io.FileOutputStream
35 | import java.io.InputStream
36 | import java.util.jar.JarEntry
37 | import java.util.jar.JarFile
38 | import java.util.jar.JarOutputStream
39 |
40 | /**
41 | *
42 | * Created by J!nl!n on 2022/12/9.
43 | *
44 | * Copyright © 2022 J!nl!n™ Inc. All rights reserved.
45 | *
46 | * - [Developing Parallel Tasks using the Worker API](https://docs.gradle.org/current/userguide/custom_tasks.html)
47 | * - [Developing Custom Gradle Task Types](https://docs.gradle.org/current/userguide/custom_tasks.html)
48 | */
49 | @CacheableTask
50 | @BuildAnalyzer(primaryTaskCategory = TaskCategory.SOURCE_PROCESSING)
51 | internal abstract class RouterClassesTask : DefaultTask() {
52 |
53 | @get:Incremental
54 | @get:InputFiles
55 | @get:PathSensitive(PathSensitivity.RELATIVE)
56 | abstract val jars: ListProperty
57 |
58 | @get:Incremental
59 | @get:InputFiles
60 | @get:PathSensitive(PathSensitivity.RELATIVE)
61 | abstract val dirs: ListProperty
62 |
63 | @get:OutputFile
64 | abstract val output: RegularFileProperty
65 |
66 | @get:Optional
67 | @get:OutputFile
68 | abstract val doc: RegularFileProperty
69 |
70 | // @Inject
71 | // abstract fun getWorkerExecutor(): WorkerExecutor
72 |
73 | @TaskAction
74 | fun taskAction(inputChanges: InputChanges) {
75 | val timer = Stopwatch()
76 |
77 | timer.start("Router::: Transform started on thread: [${Thread.currentThread().name}]")
78 |
79 | val inputs = (jars.get() + dirs.get()).map { it.asFile.toPath() }
80 |
81 | val grip: Grip = GripFactory.newInstance(Opcodes.ASM9).create(inputs)
82 | val query = grip select classes from inputs where interfaces { _, interfaces ->
83 | descriptors.map(::getType).any(interfaces::contains)
84 | }
85 | val classes = query.execute().classes
86 |
87 | timer.splitTime("Router::: Prepare referenced classes")
88 |
89 | val map = classes.groupBy({ it.interfaces.first().className.separator() }, { it.name.separator() })
90 |
91 | val closure = { jarOutput: JarOutputStream, inputSource: InputStream ->
92 | val reader = ClassReader(inputSource)
93 | val writer = ClassWriter(reader, ClassWriter.COMPUTE_FRAMES)
94 | val visitor = RouterClassVisitor(writer, map.mapValues { v -> v.value.toSet() })
95 | reader.accept(visitor, 0)
96 | jarOutput.write(writer.toByteArray())
97 | timer.splitTime("Router::: ASM modify class")
98 | }
99 |
100 | JarOutputStream(BufferedOutputStream(FileOutputStream(output.get().asFile))).use { jarOutput ->
101 | processJars(jarOutput, closure)
102 | processDirs(jarOutput)
103 | }
104 |
105 | if (doc.isPresent) {
106 | dumpJson(map)
107 | timer.splitTime("Router::: Dump Router tables time")
108 | Log.v("Router::: Router tables path : ${doc.asFile.get().path}")
109 | }
110 |
111 | timer.stop()
112 | }
113 |
114 | private fun dumpJson(map: Map>) = doc.asFile.get().writeText(JsonOutput.toJson(map))
115 |
116 | private fun processDirs(jarOutput: JarOutputStream) {
117 | dirs.get().forEach { directory ->
118 | directory.asFile.walk().forEach { file ->
119 | if (file.isFile) {
120 | val relativePath = directory.asFile.toURI().relativize(file.toURI()).path
121 | jarOutput.putNextEntry(JarEntry(relativePath.replace(File.separatorChar, '/')))
122 | file.inputStream().use { inputStream ->
123 | inputStream.copyTo(jarOutput)
124 | }
125 | jarOutput.closeEntry()
126 | }
127 | }
128 | }
129 | }
130 |
131 | private fun processJars(jarOutput: JarOutputStream, block: (JarOutputStream, InputStream) -> Unit) {
132 | jars.get().forEach { file ->
133 | JarFile(file.asFile).use { jarFile ->
134 | jarFile.entries().iterator().forEach { jarEntry ->
135 | if (!jarEntry.isDirectory && jarEntry.name.contains(TARGET_CLASS)) {
136 | jarOutput.putNextEntry(JarEntry(jarEntry.name))
137 | jarFile.getInputStream(jarEntry).use {
138 | // Transforming AptHub.class with RouterClassesTask
139 | block(jarOutput, it)
140 | }
141 | } else {
142 | runCatching {
143 | jarOutput.putNextEntry(JarEntry(jarEntry.name))
144 | jarFile.getInputStream(jarEntry).use {
145 | it.copyTo(jarOutput)
146 | }
147 | }/*.onFailure { e ->
148 | Log.e("Copy jar entry failed. [entry:${jarEntry.name}]", e)
149 | }*/
150 | }
151 | jarOutput.closeEntry()
152 | }
153 | }
154 | }
155 | }
156 |
157 | // private fun Map.print(mapper: (V?) -> String): String =
158 | // StringBuilder("\n{").also { sb ->
159 | // this.iterator().forEach { entry ->
160 | // sb.append("\n\t${entry.key} = ${mapper(entry.value)}")
161 | // }
162 | // sb.append("\n}")
163 | // }.toString()
164 |
165 | @Suppress("SpellCheckingInspection")
166 | private companion object {
167 |
168 | /**
169 | * 需要处理的字节码
170 | */
171 | private const val TARGET_CLASS = "com/chenenyu/router/AptHub"
172 |
173 | /**
174 | * 实现的接口描述符
175 | */
176 | private val descriptors = listOf(
177 | "Lcom/chenenyu/router/template/RouteTable;",
178 | "Lcom/chenenyu/router/template/InterceptorTable;",
179 | "Lcom/chenenyu/router/template/TargetInterceptorsTable;"
180 | )
181 | }
182 |
183 | }
184 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/RouteRequest.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import android.net.Uri;
4 | import android.os.Bundle;
5 | import android.os.Parcel;
6 | import android.os.Parcelable;
7 |
8 | import androidx.annotation.Nullable;
9 |
10 | import java.util.LinkedHashMap;
11 | import java.util.Map;
12 |
13 | /**
14 | * Route request object.
15 | *
16 | * Created by chenenyu on 2017/3/31.
17 | */
18 | public final class RouteRequest implements Parcelable {
19 | private static final int INVALID_CODE = -1;
20 | private Uri uri;
21 | private Bundle extras;
22 | private int flags;
23 | private Uri data;
24 | private String type;
25 | private String action;
26 | private boolean skipImplicitMatcher;
27 | // skip all the interceptors
28 | private boolean skipInterceptors;
29 | @Nullable
30 | private Map tempInterceptors;
31 | @Nullable
32 | private RouteCallback routeCallback;
33 | private int requestCode = INVALID_CODE;
34 | private int enterAnim = INVALID_CODE;
35 | private int exitAnim = INVALID_CODE;
36 | @Nullable
37 | private Bundle activityOptionsBundle;
38 |
39 |
40 | public RouteRequest(Uri uri) {
41 | this.uri = uri;
42 | }
43 |
44 | public Uri getUri() {
45 | return uri;
46 | }
47 |
48 | public void setUri(Uri uri) {
49 | this.uri = uri;
50 | }
51 |
52 | public Bundle getExtras() {
53 | return extras;
54 | }
55 |
56 | public void setExtras(Bundle extras) {
57 | this.extras = extras;
58 | }
59 |
60 | public int getFlags() {
61 | return flags;
62 | }
63 |
64 | @SuppressWarnings("unused")
65 | public void setFlags(int flags) {
66 | this.flags = flags;
67 | }
68 |
69 | public void addFlags(int flags) {
70 | this.flags |= flags;
71 | }
72 |
73 | public Uri getData() {
74 | return data;
75 | }
76 |
77 | public void setData(Uri data) {
78 | this.data = data;
79 | }
80 |
81 | public String getType() {
82 | return type;
83 | }
84 |
85 | public void setType(String type) {
86 | this.type = type;
87 | }
88 |
89 | public String getAction() {
90 | return action;
91 | }
92 |
93 | public void setAction(String action) {
94 | this.action = action;
95 | }
96 |
97 | public boolean isSkipImplicitMatcher() {
98 | return skipImplicitMatcher;
99 | }
100 |
101 | public void setSkipImplicitMatcher(boolean skipImplicitMatcher) {
102 | this.skipImplicitMatcher = skipImplicitMatcher;
103 | }
104 |
105 | public boolean isSkipInterceptors() {
106 | return skipInterceptors;
107 | }
108 |
109 | public void setSkipInterceptors(boolean skipInterceptors) {
110 | this.skipInterceptors = skipInterceptors;
111 | }
112 |
113 | @Nullable
114 | public Map getTempInterceptors() {
115 | return tempInterceptors;
116 | }
117 |
118 | public void addInterceptors(String... interceptors) {
119 | if (interceptors == null || interceptors.length <= 0) {
120 | return;
121 | }
122 | if (this.tempInterceptors == null) {
123 | this.tempInterceptors = new LinkedHashMap<>(interceptors.length);
124 | }
125 | for (String interceptor : interceptors) {
126 | this.tempInterceptors.put(interceptor, Boolean.TRUE);
127 | }
128 | }
129 |
130 | public void removeInterceptors(String... interceptors) {
131 | if (interceptors == null || interceptors.length <= 0) {
132 | return;
133 | }
134 | if (this.tempInterceptors == null) {
135 | this.tempInterceptors = new LinkedHashMap<>(interceptors.length);
136 | }
137 | for (String interceptor : interceptors) {
138 | this.tempInterceptors.put(interceptor, Boolean.FALSE);
139 | }
140 | }
141 |
142 | @Nullable
143 | public RouteCallback getRouteCallback() {
144 | return routeCallback;
145 | }
146 |
147 | public void setRouteCallback(@Nullable RouteCallback routeCallback) {
148 | this.routeCallback = routeCallback;
149 | }
150 |
151 | public int getRequestCode() {
152 | return requestCode;
153 | }
154 |
155 | public void setRequestCode(int requestCode) {
156 | if (requestCode < 0) {
157 | this.requestCode = INVALID_CODE;
158 | } else {
159 | this.requestCode = requestCode;
160 | }
161 | }
162 |
163 | public int getEnterAnim() {
164 | return enterAnim;
165 | }
166 |
167 | public void setEnterAnim(int enterAnim) {
168 | if (enterAnim < 0) {
169 | this.enterAnim = INVALID_CODE;
170 | } else {
171 | this.enterAnim = enterAnim;
172 | }
173 | }
174 |
175 | public int getExitAnim() {
176 | return exitAnim;
177 | }
178 |
179 | public void setExitAnim(int exitAnim) {
180 | if (exitAnim < 0) {
181 | this.exitAnim = INVALID_CODE;
182 | } else {
183 | this.exitAnim = exitAnim;
184 | }
185 | }
186 |
187 | @Nullable
188 | public Bundle getActivityOptionsBundle() {
189 | return activityOptionsBundle;
190 | }
191 |
192 | public void setActivityOptionsBundle(@Nullable Bundle activityOptionsBundle) {
193 | this.activityOptionsBundle = activityOptionsBundle;
194 | }
195 |
196 | @Override
197 | public int describeContents() {
198 | return 0;
199 | }
200 |
201 | @Override
202 | public void writeToParcel(Parcel dest, int flags) {
203 | dest.writeParcelable(this.uri, flags);
204 | dest.writeBundle(this.extras);
205 | dest.writeInt(this.flags);
206 | dest.writeParcelable(this.data, flags);
207 | dest.writeString(this.type);
208 | dest.writeString(this.action);
209 | dest.writeByte(this.skipInterceptors ? (byte) 1 : (byte) 0);
210 | dest.writeInt(this.tempInterceptors == null ? 0 : this.tempInterceptors.size());
211 | if (this.tempInterceptors != null) {
212 | for (Map.Entry entry : this.tempInterceptors.entrySet()) {
213 | dest.writeString(entry.getKey());
214 | dest.writeValue(entry.getValue());
215 | }
216 | }
217 | dest.writeSerializable(this.routeCallback);
218 | dest.writeInt(this.requestCode);
219 | dest.writeInt(this.enterAnim);
220 | dest.writeInt(this.exitAnim);
221 | dest.writeBundle(this.activityOptionsBundle);
222 | }
223 |
224 | protected RouteRequest(Parcel in) {
225 | this.uri = in.readParcelable(Uri.class.getClassLoader());
226 | this.extras = in.readBundle(Bundle.class.getClassLoader());
227 | this.flags = in.readInt();
228 | this.data = in.readParcelable(Uri.class.getClassLoader());
229 | this.type = in.readString();
230 | this.action = in.readString();
231 | this.skipInterceptors = in.readByte() != 0;
232 | int tempInterceptorsSize = in.readInt();
233 | this.tempInterceptors = new LinkedHashMap<>(tempInterceptorsSize);
234 | for (int i = 0; i < tempInterceptorsSize; i++) {
235 | String key = in.readString();
236 | Boolean value = (Boolean) in.readValue(Boolean.class.getClassLoader());
237 | this.tempInterceptors.put(key, value);
238 | }
239 | this.routeCallback = (RouteCallback) in.readSerializable();
240 | this.requestCode = in.readInt();
241 | this.enterAnim = in.readInt();
242 | this.exitAnim = in.readInt();
243 | this.activityOptionsBundle = in.readBundle(Bundle.class.getClassLoader());
244 | }
245 |
246 | public static final Creator CREATOR = new Creator() {
247 | @Override
248 | public RouteRequest createFromParcel(Parcel source) {
249 | return new RouteRequest(source);
250 | }
251 |
252 | @Override
253 | public RouteRequest[] newArray(int size) {
254 | return new RouteRequest[size];
255 | }
256 | };
257 | }
258 |
--------------------------------------------------------------------------------
/router/src/main/java/com/chenenyu/router/AbsRouter.java:
--------------------------------------------------------------------------------
1 | package com.chenenyu.router;
2 |
3 | import android.content.Context;
4 | import android.net.Uri;
5 | import android.os.Build;
6 | import android.os.Bundle;
7 | import android.os.IBinder;
8 | import android.os.Parcelable;
9 | import android.os.PersistableBundle;
10 | import android.util.SparseArray;
11 |
12 | import androidx.annotation.AnimRes;
13 | import androidx.annotation.NonNull;
14 | import androidx.annotation.RequiresApi;
15 | import androidx.fragment.app.Fragment;
16 |
17 | import com.chenenyu.router.util.RLog;
18 |
19 | import java.io.Serializable;
20 |
21 | /**
22 | * Help to construct a {@link RouteRequest}.
23 | *
24 | * Created by chenenyu on 2017/3/31.
25 | */
26 | abstract class AbsRouter implements IRouter {
27 | RouteRequest mRouteRequest;
28 |
29 | @Override
30 | public IRouter build(Uri uri) {
31 | mRouteRequest = new RouteRequest(uri);
32 | Bundle bundle = new Bundle();
33 | bundle.putString(Router.RAW_URI, uri == null ? null : uri.toString());
34 | mRouteRequest.setExtras(bundle);
35 | return this;
36 | }
37 |
38 | @Override
39 | public IRouter build(@NonNull RouteRequest request) {
40 | mRouteRequest = request;
41 | Bundle bundle = mRouteRequest.getExtras();
42 | if (bundle == null) {
43 | bundle = new Bundle();
44 | }
45 | bundle.putString(Router.RAW_URI, request.getUri().toString());
46 | mRouteRequest.setExtras(bundle);
47 | return this;
48 | }
49 |
50 | @Override
51 | public IRouter callback(RouteCallback callback) {
52 | mRouteRequest.setRouteCallback(callback);
53 | return this;
54 | }
55 |
56 | @Override
57 | public IRouter requestCode(int requestCode) {
58 | mRouteRequest.setRequestCode(requestCode);
59 | return this;
60 | }
61 |
62 | @Override
63 | public IRouter with(Bundle bundle) {
64 | if (bundle != null && !bundle.isEmpty()) {
65 | Bundle extras = mRouteRequest.getExtras();
66 | if (extras == null) {
67 | extras = new Bundle();
68 | }
69 | extras.putAll(bundle);
70 | mRouteRequest.setExtras(extras);
71 | }
72 | return this;
73 | }
74 |
75 | @RequiresApi(21)
76 | @Override
77 | public IRouter with(PersistableBundle bundle) {
78 | if (bundle != null && !bundle.isEmpty()) {
79 | Bundle extras = mRouteRequest.getExtras();
80 | if (extras == null) {
81 | extras = new Bundle();
82 | }
83 | extras.putAll(bundle);
84 | mRouteRequest.setExtras(extras);
85 | }
86 | return this;
87 | }
88 |
89 | @SuppressWarnings("unchecked")
90 | @Override
91 | public IRouter with(String key, Object value) {
92 | if (value == null) {
93 | RLog.w("Ignored: The extra value is null.");
94 | return this;
95 | }
96 | Bundle bundle = mRouteRequest.getExtras();
97 | if (bundle == null) {
98 | bundle = new Bundle();
99 | }
100 | if (value instanceof Bundle) {
101 | bundle.putBundle(key, (Bundle) value);
102 | } else if (value instanceof Byte) {
103 | bundle.putByte(key, (byte) value);
104 | } else if (value instanceof Short) {
105 | bundle.putShort(key, (short) value);
106 | } else if (value instanceof Integer) {
107 | bundle.putInt(key, (int) value);
108 | } else if (value instanceof Long) {
109 | bundle.putLong(key, (long) value);
110 | } else if (value instanceof Character) {
111 | bundle.putChar(key, (char) value);
112 | } else if (value instanceof Boolean) {
113 | bundle.putBoolean(key, (boolean) value);
114 | } else if (value instanceof Float) {
115 | bundle.putFloat(key, (float) value);
116 | } else if (value instanceof Double) {
117 | bundle.putDouble(key, (double) value);
118 | } else if (value instanceof String) {
119 | bundle.putString(key, (String) value);
120 | } else if (value instanceof CharSequence) {
121 | bundle.putCharSequence(key, (CharSequence) value);
122 | } else if (value instanceof byte[]) {
123 | bundle.putByteArray(key, (byte[]) value);
124 | } else if (value instanceof short[]) {
125 | bundle.putShortArray(key, (short[]) value);
126 | } else if (value instanceof int[]) {
127 | bundle.putIntArray(key, (int[]) value);
128 | } else if (value instanceof long[]) {
129 | bundle.putLongArray(key, (long[]) value);
130 | } else if (value instanceof char[]) {
131 | bundle.putCharArray(key, (char[]) value);
132 | } else if (value instanceof boolean[]) {
133 | bundle.putBooleanArray(key, (boolean[]) value);
134 | } else if (value instanceof float[]) {
135 | bundle.putFloatArray(key, (float[]) value);
136 | } else if (value instanceof double[]) {
137 | bundle.putDoubleArray(key, (double[]) value);
138 | } else if (value instanceof String[]) {
139 | bundle.putStringArray(key, (String[]) value);
140 | } else if (value instanceof CharSequence[]) {
141 | bundle.putCharSequenceArray(key, (CharSequence[]) value);
142 | } else if (value instanceof IBinder) {
143 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
144 | bundle.putBinder(key, (IBinder) value);
145 | } else {
146 | RLog.e("putBinder() requires api 18.");
147 | }
148 | } else if (value instanceof SparseArray) {
149 | bundle.putSparseParcelableArray(key, (SparseArray extends Parcelable>) value);
150 | } else if (value instanceof Parcelable) {
151 | bundle.putParcelable(key, (Parcelable) value);
152 | } else if (value instanceof Parcelable[]) {
153 | bundle.putParcelableArray(key, (Parcelable[]) value);
154 | } else if (value instanceof Serializable) {
155 | bundle.putSerializable(key, (Serializable) value);
156 | } else {
157 | RLog.w("Unknown object type: " + value.getClass().getName());
158 | }
159 | mRouteRequest.setExtras(bundle);
160 | return this;
161 | }
162 |
163 | @Override
164 | public IRouter addFlags(int flags) {
165 | mRouteRequest.addFlags(flags);
166 | return this;
167 | }
168 |
169 | @Override
170 | public IRouter setData(Uri data) {
171 | mRouteRequest.setData(data);
172 | return this;
173 | }
174 |
175 | @Override
176 | public IRouter setType(String type) {
177 | mRouteRequest.setType(type);
178 | return this;
179 | }
180 |
181 | @Override
182 | public IRouter setDataAndType(Uri data, String type) {
183 | mRouteRequest.setData(data);
184 | mRouteRequest.setType(type);
185 | return this;
186 | }
187 |
188 | @Override
189 | public IRouter setAction(String action) {
190 | mRouteRequest.setAction(action);
191 | return this;
192 | }
193 |
194 | @Override
195 | public IRouter anim(@AnimRes int enterAnim, @AnimRes int exitAnim) {
196 | mRouteRequest.setEnterAnim(enterAnim);
197 | mRouteRequest.setExitAnim(exitAnim);
198 | return this;
199 | }
200 |
201 | @Override
202 | public IRouter activityOptionsBundle(Bundle activityOptionsBundle) {
203 | mRouteRequest.setActivityOptionsBundle(activityOptionsBundle);
204 | return this;
205 | }
206 |
207 | @Override
208 | public IRouter skipImplicitMatcher() {
209 | mRouteRequest.setSkipImplicitMatcher(true);
210 | return this;
211 | }
212 |
213 | @Override
214 | public IRouter skipInterceptors() {
215 | mRouteRequest.setSkipInterceptors(true);
216 | return this;
217 | }
218 |
219 | @Override
220 | public IRouter skipInterceptors(String... interceptors) {
221 | mRouteRequest.removeInterceptors(interceptors);
222 | return this;
223 | }
224 |
225 | @Override
226 | public IRouter addInterceptors(String... interceptors) {
227 | mRouteRequest.addInterceptors(interceptors);
228 | return this;
229 | }
230 |
231 | @Override
232 | public void go(Context context, RouteCallback callback) {
233 | mRouteRequest.setRouteCallback(callback);
234 | go(context);
235 | }
236 |
237 | @Override
238 | public void go(Fragment fragment, RouteCallback callback) {
239 | mRouteRequest.setRouteCallback(callback);
240 | go(fragment);
241 | }
242 | }
243 |
--------------------------------------------------------------------------------