├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── 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
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── layout
│ │ │ │ └── activity_main.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── fengshihao
│ │ │ │ └── example
│ │ │ │ └── xlistener
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── GenerateTest.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ ├── com
│ │ │ └── fengshihao
│ │ │ │ └── example
│ │ │ │ └── xlistener
│ │ │ │ └── ExampleUnitTest.java
│ │ │ └── android
│ │ │ └── util
│ │ │ └── Log.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── fengshihao
│ │ └── xlistener
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── XListenerProcessor
├── .gitignore
├── delpoly_mvn.sh
├── build.gradle
├── src
│ └── main
│ │ └── java
│ │ └── com
│ │ └── fengshihao
│ │ └── xlistener
│ │ ├── XListener.java
│ │ ├── XListenerProcessor.java
│ │ └── CodeGenerator.java
└── pom.xml
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/XListenerProcessor/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':XListenerProcessor'
2 |
--------------------------------------------------------------------------------
/XListenerProcessor/delpoly_mvn.sh:
--------------------------------------------------------------------------------
1 | mvn clean deploy -P release
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengshihao/xlistener/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | xlistener test
3 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengshihao/xlistener/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengshihao/xlistener/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengshihao/xlistener/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengshihao/xlistener/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengshihao/xlistener/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengshihao/xlistener/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengshihao/xlistener/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengshihao/xlistener/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengshihao/xlistener/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengshihao/xlistener/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Jan 20 23:05:32 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/XListenerProcessor/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java-library'
2 |
3 | dependencies {
4 | implementation fileTree(dir: 'libs', include: ['*.jar'])
5 | compile 'com.google.auto.service:auto-service:1.0-rc2'
6 | }
7 |
8 | sourceCompatibility = "1.8"
9 | targetCompatibility = "1.8"
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/XListenerProcessor/src/main/java/com/fengshihao/xlistener/XListener.java:
--------------------------------------------------------------------------------
1 | package com.fengshihao.xlistener;
2 |
3 | import java.lang.annotation.Retention;
4 | import java.lang.annotation.RetentionPolicy;
5 |
6 | /**
7 | * Created by fengshihao on 18-7-6.
8 | */
9 |
10 | @Retention(RetentionPolicy.SOURCE)
11 | public @interface XListener {
12 |
13 | boolean notifyOnMainThread() default false;
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/test/java/com/fengshihao/example/xlistener/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.fengshihao.example.xlistener;
2 |
3 | import org.junit.Test;
4 |
5 | /**
6 | * Example local unit test, which will execute on the development machine (host).
7 | *
8 | * @see Testing documentation
9 | */
10 | public class ExampleUnitTest {
11 |
12 | @Test
13 | public void testListenerGenerate() {
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fengshihao/example/xlistener/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.fengshihao.example.xlistener;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 |
6 | public class MainActivity extends Activity {
7 |
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | setContentView(R.layout.activity_main);
12 | GenerateTest test = new GenerateTest();
13 | test.run();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/src/test/java/android/util/Log.java:
--------------------------------------------------------------------------------
1 | package android.util;
2 |
3 | /**
4 | * Created by fengshihao on 18-10-17.
5 | */
6 |
7 | public class Log {
8 | public static int d(String tag, String msg) {
9 | System.out.println("DEBUG: " + tag + ": " + msg);
10 | return 0;
11 | }
12 |
13 | public static int i(String tag, String msg) {
14 | System.out.println("INFO: " + tag + ": " + msg);
15 | return 0;
16 | }
17 |
18 | public static int w(String tag, String msg) {
19 | System.out.println("WARN: " + tag + ": " + msg);
20 | return 0;
21 | }
22 |
23 | public static int e(String tag, String msg) {
24 | System.out.println("ERROR: " + tag + ": " + msg);
25 | return 0;
26 | }
27 |
28 | // add other methods if required...
29 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/fengshihao/xlistener/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.fengshihao.xlistener;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.fengshihao.xlistener.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | defaultConfig {
6 | applicationId "com.fengshihao.xlistenerTest"
7 | minSdkVersion 16
8 | targetSdkVersion 26
9 | versionCode 1
10 | versionName "1.0"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 |
13 | javaCompileOptions {
14 | annotationProcessorOptions {
15 | includeCompileClasspath = true
16 | }
17 | }
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | // jackOptions {
27 | // enabled true
28 | // }
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 |
35 | testOptions {
36 | unitTests.returnDefaultValues = true
37 | }
38 |
39 | }
40 |
41 | dependencies {
42 | implementation fileTree(dir: 'libs', include: ['*.jar'])
43 | implementation 'com.android.support.constraint:constraint-layout:1.0.2'
44 |
45 | testImplementation 'junit:junit:4.12'
46 | testCompile "org.mockito:mockito-core:1.9.5"
47 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
48 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
49 |
50 | compileOnly project(path : ':XListenerProcessor')
51 | //compileOnly 'com.fengshihao.xlistener:xlistener:1.0'
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/fengshihao/example/xlistener/GenerateTest.java:
--------------------------------------------------------------------------------
1 | package com.fengshihao.example.xlistener;
2 |
3 | import android.os.Handler;
4 | import android.os.HandlerThread;
5 | import android.util.Log;
6 |
7 | import com.fengshihao.xlistener.XListener;
8 |
9 | @XListener
10 | interface TestListener {
11 | default void onX(int x) {}
12 | default void onY(int x, float y, String z) {}
13 | void onZ();
14 | }
15 |
16 |
17 | /**
18 | * Created by fengshihao on 18-7-8.
19 | */
20 |
21 | public class GenerateTest {
22 | private static final String TAG = "GenerateTest";
23 |
24 | HandlerThread mWorkThread = new HandlerThread("WorkerThread");
25 | Handler mWorkHandler;
26 | private void init() {
27 | mWorkThread.start();
28 | mWorkHandler = new Handler(mWorkThread.getLooper());
29 | }
30 |
31 | private void createListenerListAndCallMethods() {
32 | Log.d(TAG, "createListenerListAndCallMethods: thread=" + Thread.currentThread().getName());
33 | TestListenerList t = new TestListenerList();
34 | t.attachToMainThread();
35 | t.addListener(new TestListener() {
36 | @Override
37 | public void onX(int x) {
38 | Log.d(TAG, "onX() called with: x = [" + x + "] thread=" + Thread.currentThread().getName());
39 | }
40 |
41 | @Override
42 | public void onZ() {
43 | Log.d(TAG, "onZ() called on thread=" + Thread.currentThread().getName());
44 | }
45 | });
46 |
47 | t.onX(100);
48 | t.onZ();
49 | }
50 |
51 | public void run() {
52 | init();
53 | mWorkHandler.post(() -> createListenerListAndCallMethods());
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## 使用
3 |
4 | 第一步:
5 | gradle 添加依赖:
6 | ```
7 | annotationProcessor 'com.fengshihao.xlistener:xlistener:1.0.1'
8 | compileOnly 'com.fengshihao.xlistener:xlistener:1.0.1'
9 | ```
10 |
11 | 第二步:
12 | ```java
13 | import com.fengshihao.xlistener.XListener;
14 |
15 | @XListener //使用完成代码自动生成 TestListenerList 类,和TestListener包一样.
16 | interface TestListener {
17 | default void onX(int x) {}
18 | default void onY(int x, float y, String z) {}
19 | void onZ();
20 | }
21 |
22 | ```
23 |
24 | 第三步:
25 | ```java
26 | TestListenerList t = new TestListenerList();
27 | // 之后onX, onZ... 将会被在主线程调用。 如果调用attachToCurrentThread,会让回调在当前线程
28 | t.attachToMainThread();
29 | t.addListener(new TestListener() {
30 | @Override
31 | public void onX(int x) {
32 | Log.d(TAG, "onX() called with: x = [" + x + "] thread=" + Thread.currentThread().getName());
33 | }
34 |
35 | @Override
36 | public void onZ() {
37 | Log.d(TAG, "onZ() called on thread=" + Thread.currentThread().getName());
38 | }
39 | });
40 |
41 | t.onX(100);
42 | t.onZ();
43 | t.clean(); //清空listener列表
44 | ```
45 | ## 什么是XListener?
46 |
47 | XListener是观察者模型的实现。 可以让开发者用最简单的方式写出各种Listener。 它主要目的是替换EventBus。
48 |
49 | 想象这么个场景, 你创建了一个播放器类
50 | ```java
51 | class Player {
52 | public void play() {}
53 | public void pause() {}
54 | public void stop() {}
55 | }
56 | ```
57 | 播放器会有一些事件需要其他模块处理,不能把其他的逻辑,比如UI的展示,写在播放器中,于是你创建一个listener
58 |
59 | ```java
60 | interface PlayerListener {
61 | void onStarted();
62 | void onProgress(int progress);
63 | void onStop();
64 | }
65 | // 这时候player需要做些改动 增加一个成员 listener, 并在合适的时候调用通知, 这样外边的模块就可以根据事件做出相应逻辑。
66 | class Player {
67 | PlayerListener mListener;
68 | public void play() {
69 | mListener.onStarted();
70 | }
71 |
72 | //在程序某个地方
73 | mListener.onProgress(pro);
74 |
75 | public void pause() {
76 | }
77 |
78 | public void stop() {
79 | mListener.onStop();
80 | }
81 | }
82 | ```
83 |
84 | 但是问题很快又来了。 关心这些事件的不是一个模块, 比如字幕模块, 声音模块...他们都需要知道什么时候开始播放 什么时候停止播放。
85 | 于是Player.mListener变成了一个列表 PlayerListenerList。 你得维护它。
86 | ```java
87 | //这个类里边充斥着样板代码。 XListener可以方便的生成这种样板代码。
88 | public interface PlayerListenerList implements PlayerListener {
89 | private List mListeners = new ArrayList<>();
90 |
91 | public void addListener(PlayerListener listener) {
92 | ...
93 | }
94 | public void removeListener(PlayerListener listener) {
95 | ...
96 | }
97 |
98 |
99 | @Override
100 | public void onStarted() {
101 | for (PlayerListener l: mListeners) {
102 | l.onStarted();
103 | }
104 | }
105 |
106 | ....
107 | }
108 | ```
109 | ## 为什么放弃EventBus
110 | 这时候有很多人会想到EventBus。 但是EventBus 会引诱人们写各种凌乱的event。 然后就成了EventBugs。
111 | * EventBus 使得程序逻辑杂乱无章, 看似解耦实则是为错误的设计做了补丁。
112 | * 被迫定义各种Event, 调用和被调用方使用都很不方便。
113 | * 使用了反射的机制效率不高。
114 |
115 | ## XListener特点
116 | * 使用自动代码生成方式让开发者省去了编写addXListener() removeXListener() 这样的样板代码
117 | * 使用代码生成方式保证Listener的调用效率, 和类型检查不失效,调用方和被调用方在重构时保证参数正确。
118 | * 非常小的代价。几乎不影响现有接口, 生成的XListenerList 和 XListene的接口保持一致。
119 | * 可以设置XListenerList回调线程。 线程切换异常方便。
120 |
--------------------------------------------------------------------------------
/XListenerProcessor/src/main/java/com/fengshihao/xlistener/XListenerProcessor.java:
--------------------------------------------------------------------------------
1 | package com.fengshihao.xlistener;
2 |
3 | import com.google.auto.service.AutoService;
4 |
5 | import java.io.IOException;
6 | import java.io.Writer;
7 | import java.util.HashMap;
8 | import java.util.LinkedHashSet;
9 | import java.util.LinkedList;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.Set;
13 |
14 | import javax.annotation.processing.AbstractProcessor;
15 | import javax.annotation.processing.Filer;
16 | import javax.annotation.processing.Messager;
17 | import javax.annotation.processing.ProcessingEnvironment;
18 | import javax.annotation.processing.Processor;
19 | import javax.annotation.processing.RoundEnvironment;
20 | import javax.lang.model.SourceVersion;
21 | import javax.lang.model.element.Element;
22 | import javax.lang.model.element.ElementKind;
23 | import javax.lang.model.element.ExecutableElement;
24 | import javax.lang.model.element.PackageElement;
25 | import javax.lang.model.element.TypeElement;
26 | import javax.lang.model.element.VariableElement;
27 | import javax.lang.model.util.Elements;
28 | import javax.tools.Diagnostic;
29 | import javax.tools.JavaFileObject;
30 |
31 | @AutoService(Processor.class)
32 | public class XListenerProcessor extends AbstractProcessor{
33 | private Filer mFiler;
34 | private Messager mMessager;
35 | private Elements mElementUtils;
36 |
37 | @Override
38 | public synchronized void init(ProcessingEnvironment processingEnvironment) {
39 | super.init(processingEnvironment);
40 | mFiler = processingEnvironment.getFiler();
41 | mMessager = processingEnvironment.getMessager();
42 | mElementUtils = processingEnvironment.getElementUtils();
43 | }
44 |
45 | @Override
46 | public Set getSupportedAnnotationTypes() {
47 | Set annotations = new LinkedHashSet<>();
48 | annotations.add(XListener.class.getCanonicalName());
49 | return annotations;
50 | }
51 |
52 | @Override
53 | public SourceVersion getSupportedSourceVersion() {
54 | return SourceVersion.latestSupported();
55 | }
56 |
57 | @Override
58 | public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
59 | Set extends Element> allInterfaces = roundEnvironment.getElementsAnnotatedWith(XListener.class);
60 | for (Element element : allInterfaces) {
61 | if (element.getKind() != ElementKind.INTERFACE) {
62 | note("dont use XListener on a non-Interface object " + element.getSimpleName());
63 | return false;
64 | }
65 | CodeGenerator model = new CodeGenerator();
66 |
67 | XListener anotation = element.getAnnotation(XListener.class);
68 | model.notifyOnMainThread = anotation.notifyOnMainThread();
69 |
70 | model.interfaceName = element.getSimpleName().toString();
71 | model.className = model.interfaceName + "Notifier";
72 | //1.获取包名
73 | PackageElement packageElement = mElementUtils.getPackageOf(element);
74 | String pkName = packageElement.getQualifiedName().toString();
75 | model.packageName = pkName;
76 | //note(String.format("package = %s", pkName));
77 |
78 | List extends Element> methods = element.getEnclosedElements();
79 | for (Element m: methods) {
80 | String methodName = m.getSimpleName().toString();
81 | //note("m=" + methodName);
82 | List plist = new LinkedList<>();
83 | ExecutableElement em = (ExecutableElement) m;
84 | List extends VariableElement> parameters = em.getParameters();
85 | for (VariableElement p: parameters) {
86 | //note("p=" + p.getSimpleName() + " " + p.asType());
87 | plist.add(p.asType().toString());
88 | plist.add(p.getSimpleName().toString());
89 | }
90 | model.methods.put(methodName, plist);
91 | }
92 |
93 | createFile(model);
94 | }
95 | return true;
96 | }
97 |
98 | private void createFile(CodeGenerator model) {
99 | try {
100 | JavaFileObject jfo = mFiler.createSourceFile(model.packageName + "." + model.interfaceName + "List", new Element[]{});
101 | Writer writer = jfo.openWriter();
102 | writer.write(model.toCode());
103 | writer.flush();
104 | writer.close();
105 | } catch (IOException e) {
106 | note("error ", e);
107 | }
108 | }
109 |
110 | private void note(String msg) {
111 | mMessager.printMessage(Diagnostic.Kind.NOTE, msg);
112 | }
113 |
114 | private void note(String format, Object... args) {
115 | mMessager.printMessage(Diagnostic.Kind.NOTE, String.format(format, args));
116 | }
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/XListenerProcessor/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 | com.fengshihao.xlistener
8 | xlistener
9 | 1.0.1
10 |
11 | xlistener
12 | A simple tool to generate any listener's list.
13 | https://github.com/fengshihao/xlistener
14 |
15 |
16 | The Apache Software License, Version 2.0
17 | http://www.apache.org/licenses/LICENSE-2.0.txt
18 |
19 |
20 |
21 |
22 | fengshihao
23 | fengshihao@hotmail.com
24 |
25 |
26 |
27 | scm:git:git@github.com:fengshihao/xlistener.git
28 | scm:git:git@github.com:fengshihao/xlistener.git
29 | git@github.com:fengshihao/xlistener.git
30 |
31 |
32 |
33 | UTF-8
34 | 1.7
35 | 1.7
36 |
37 |
38 |
39 |
40 | junit
41 | junit
42 | 4.11
43 | test
44 |
45 |
46 | com.google.auto.service
47 | auto-service
48 | 1.0-rc2
49 |
50 |
51 |
52 |
53 | oss
54 | https://oss.sonatype.org/content/repositories/snapshots/
55 |
56 |
57 | oss
58 | Snapshots
59 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
60 |
61 |
62 |
63 |
64 | release
65 |
66 |
67 |
68 |
69 | org.apache.maven.plugins
70 | maven-source-plugin
71 | 2.2.1
72 |
73 |
74 | package
75 |
76 | jar-no-fork
77 |
78 |
79 |
80 |
81 |
82 |
83 | org.apache.maven.plugins
84 | maven-javadoc-plugin
85 | 2.9.1
86 |
87 | private
88 | true
89 | UTF-8
90 | UTF-8
91 | UTF-8
92 | -Xdoclint:none
93 |
94 |
95 |
96 | package
97 |
98 | jar
99 |
100 |
101 |
102 |
103 |
104 |
105 | org.apache.maven.plugins
106 | maven-gpg-plugin
107 | 1.5
108 |
109 |
110 | verify
111 |
112 | sign
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | oss
122 | https://oss.sonatype.org/content/repositories/snapshots/
123 |
124 |
125 | oss
126 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | org.apache.maven.plugins
137 | maven-gpg-plugin
138 | 1.5
139 |
140 |
141 | verify
142 |
143 | sign
144 |
145 |
146 |
147 |
148 |
149 | maven-clean-plugin
150 | 3.0.0
151 |
152 |
153 |
154 | maven-resources-plugin
155 | 3.0.2
156 |
157 |
158 | maven-compiler-plugin
159 | 3.7.0
160 |
161 |
162 | maven-surefire-plugin
163 | 2.20.1
164 |
165 |
166 | maven-jar-plugin
167 | 3.0.2
168 |
169 |
170 | maven-install-plugin
171 | 2.5.2
172 |
173 |
174 | maven-deploy-plugin
175 | 2.8.2
176 |
177 |
178 |
179 |
180 |
181 |
--------------------------------------------------------------------------------
/XListenerProcessor/src/main/java/com/fengshihao/xlistener/CodeGenerator.java:
--------------------------------------------------------------------------------
1 | package com.fengshihao.xlistener;
2 |
3 | import java.util.HashMap;
4 | import java.util.List;
5 | import java.util.Map;
6 |
7 | /**
8 | * Created by fengshihao on 18-10-16.
9 | */
10 | class CodeGenerator {
11 | String className;
12 | String interfaceName;
13 | String packageName;
14 | boolean notifyOnMainThread;
15 | Map> methods = new HashMap<>();
16 |
17 | private String getMethodsCode() {
18 | StringBuilder code = new StringBuilder();
19 | for (String methodName: methods.keySet()) {
20 | List parameters = methods.get(methodName);
21 | code.append(getMethodCode(methodName, parameters));
22 | code.append("\n");
23 | }
24 | return code.toString();
25 | }
26 |
27 |
28 | private String getMethodCode(String methodName, List parameters) {
29 | final String METHOD_TMPL =
30 | " @Override" + "\n" +
31 | " public void METHOD(PARAMS) {" + "\n" +
32 | " if (mHandler == null || isRightThread()) {" + "\n" +
33 | "METHOD_INNER_TMPL" + "\n" +
34 | " return;" + "\n" +
35 | " }" + "\n" +
36 | " mHandler.post(() -> {" + "\n" +
37 | "METHOD_INNER_TMPL" + "\n" +
38 | " });" + "\n" +
39 | " }" + "\n";
40 |
41 | final String METHOD_INNER_TMPL =
42 | " for (INTERFACE l: mListeners) {" + "\n" +
43 | " l.METHOD(NAMES);" + "\n" +
44 | " }";
45 |
46 | final String METHOD_T = METHOD_TMPL.replace("METHOD_INNER_TMPL", METHOD_INNER_TMPL);
47 | String params = "";
48 | String paramNames = "";
49 |
50 | int lastParam = parameters.size() - 2;
51 | for (int i = 0; i < parameters.size() ; i+=2) {
52 | String paramType = parameters.get(i);
53 | String paramName = parameters.get(i + 1);
54 | params += paramType + " " + paramName;
55 | paramNames += paramName;
56 | if (i != lastParam) {
57 | params += ", ";
58 | paramNames += ", ";
59 | }
60 | }
61 | return METHOD_T.replace("METHOD", methodName)
62 | .replace("PARAMS", params)
63 | .replace("NAMES", paramNames);
64 | }
65 |
66 | String toCode() {
67 | String code = Template.replace("PACKAGE_NAME", packageName)
68 | .replace("METHODS_BODY", getMethodsCode());
69 | return code.replace("INTERFACE", interfaceName);
70 | }
71 |
72 | private String Template = "package PACKAGE_NAME;\n" +
73 | "\n" +
74 | "\n" +
75 | "import android.os.Handler;\n" +
76 | "import android.os.Looper;\n" +
77 | "import android.util.Log;\n" +
78 | "import java.util.ArrayList;\n" +
79 | "import java.util.List;\n" +
80 |
81 | "\n" +
82 | "public class INTERFACEList implements INTERFACE {\n" +
83 | " private List mListeners = new ArrayList<>();\n" +
84 | " private static final String TAG = \"INTERFACEList\";\n" +
85 | "METHODS_BODY" +
86 | " private Handler mHandler;\n" +
87 |
88 | " public void attachToCurrentThread() {\n" +
89 | " if (Looper.myLooper() == null) {\n" +
90 | " Log.e(TAG, \"this thread do not has looper!\");\n" +
91 | " return;\n" +
92 | " }\n" +
93 | " mHandler = new Handler(Looper.myLooper());\n" +
94 | " }\n" +
95 | "\n" +
96 | " public void attachToMainThread() {\n" +
97 | " mHandler = new Handler(Looper.getMainLooper());\n" +
98 | " }" +
99 | "\n" +
100 | " private boolean isRightThread() {\n" +
101 | " return mHandler.getLooper() == Looper.myLooper();\n" +
102 | " }\n" +
103 | "\n" +
104 | " public void addListener(INTERFACE listener) {\n" +
105 | " if (mHandler == null || isRightThread()) {" + "\n" +
106 | " addListener_(listener);" + "\n" +
107 | " return;" + "\n" +
108 | " }" + "\n" +
109 | " mHandler.post(() -> {" + "\n" +
110 | " addListener_(listener);" + "\n" +
111 | " });" + "\n" +
112 | " }\n" +
113 | "\n" +
114 | " public void removeListener(INTERFACE listener) {\n" +
115 | " if (mHandler == null || isRightThread()) {" + "\n" +
116 | " removeListener_(listener);" + "\n" +
117 | " return;" + "\n" +
118 | " }" + "\n" +
119 | " mHandler.post(() -> {" + "\n" +
120 | " removeListener_(listener);" + "\n" +
121 | " });" + "\n" +
122 | " }\n" +
123 | "\n" +
124 | " public void clean() {\n" +
125 | " if (mHandler == null || isRightThread()) {" + "\n" +
126 | " clean_();" + "\n" +
127 | " return;" + "\n" +
128 | " }" + "\n" +
129 | " mHandler.post(() -> {" + "\n" +
130 | " clean_();" + "\n" +
131 | " });" + "\n" +
132 | " }\n" +
133 | "\n" +
134 | " private void addListener_(INTERFACE listener) {\n" +
135 | " if (listener == null) {\n" +
136 | " Log.e(TAG, \"addListener: wrong arg null\");\n" +
137 | " return;\n" +
138 | " }\n" +
139 | " if (mListeners.contains(listener)) {\n" +
140 | " Log.e(TAG, \"addListener: already in \" + listener);\n" +
141 | " return;\n" +
142 | " }\n" +
143 | " mListeners.add(listener);\n" +
144 | " Log.d(TAG, \"addListener: now has listener=\" + mListeners.size());\n" +
145 | " }\n" +
146 | "\n" +
147 | " private INTERFACE removeListener_(INTERFACE listener) {\n" +
148 | " if (listener == null) {\n" +
149 | " Log.e(TAG, \"removeListener: wrong arg null\");\n" +
150 | " return null;\n" +
151 | " }\n" +
152 | " if (mListeners.isEmpty()) {\n" +
153 | " return null;\n" +
154 | " }\n" +
155 | " int idx = mListeners.indexOf(listener);\n" +
156 | " if (idx == -1) {\n" +
157 | " Log.e(TAG, \"removeListener: did not find this listener \" + listener);\n" +
158 | " return null;\n" +
159 | " }\n" +
160 | " INTERFACE r = mListeners.remove(idx);\n" +
161 | " Log.d(TAG, \"removeListener: now has listener=\" + mListeners.size());\n" +
162 | " return r;\n" +
163 | " }\n" +
164 | "\n" +
165 | " private void clean_() {\n" +
166 | " Log.d(TAG, \"clean() called\");\n" +
167 | " mListeners.clear();\n" +
168 | " }\n" +
169 | "}\n";
170 | }
171 |
--------------------------------------------------------------------------------