├── 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 │ │ │ └── example │ │ │ └── whensunset │ │ │ └── mytiktok │ │ │ ├── TestPresenter.java │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── camera ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ └── AndroidManifest.xml ├── build.gradle └── proguard-rules.pro ├── http ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ └── AndroidManifest.xml ├── build.gradle └── proguard-rules.pro ├── mvps ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── example │ │ └── mvps │ │ ├── Presenter.java │ │ └── BasePresenter.java ├── build.gradle └── proguard-rules.pro ├── push ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ └── AndroidManifest.xml ├── build.gradle └── proguard-rules.pro ├── router ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ └── AndroidManifest.xml ├── build.gradle └── proguard-rules.pro ├── video ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ └── AndroidManifest.xml ├── build.gradle └── proguard-rules.pro ├── widget ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ └── AndroidManifest.xml ├── build.gradle └── proguard-rules.pro ├── annotation ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── example │ └── annotation │ ├── field │ ├── FieldProvider.java │ ├── Field.java │ ├── Fetcher.java │ └── Fetchers.java │ ├── inject │ ├── Injector.java │ ├── NamedParam.java │ ├── Reference.java │ ├── Inject.java │ ├── ObjectProvider.java │ ├── Injectors.java │ ├── ProviderHolder.java │ └── ObjectProviderImpl.java │ ├── invoker │ ├── ForInvoker.java │ └── InvokeBy.java │ └── StandFor.java ├── logutil ├── .gitignore ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ └── AndroidManifest.xml ├── build.gradle └── proguard-rules.pro ├── annotation-processing ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── example │ └── annotation_processing │ ├── invoker │ ├── InvokeMethod.java │ ├── Invocation.java │ └── InvokerProcessor.java │ ├── util │ ├── ClassBuilder.java │ ├── BaseProcessor.java │ ├── AptUtils.java │ └── SortedElement.java │ ├── inject │ ├── InjectorHelperBuilder.java │ ├── InjectorProcessor.java │ └── InjectorBuilder.java │ └── field │ └── FieldProcessor.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── README.md ├── .gitignore ├── library.gradle ├── gradle.properties ├── gradlew.bat ├── versions.gradle ├── 从零开始写一个抖音App——开始(mdn).md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /camera/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /http/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mvps/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /push/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /router/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /video/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /widget/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /annotation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /logutil/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /annotation-processing/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | MyTikTok 3 | 4 | -------------------------------------------------------------------------------- /http/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Http 3 | 4 | -------------------------------------------------------------------------------- /mvps/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | mvps 3 | 4 | -------------------------------------------------------------------------------- /push/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Push 3 | 4 | -------------------------------------------------------------------------------- /video/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Video 3 | 4 | -------------------------------------------------------------------------------- /camera/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Camera 3 | 4 | -------------------------------------------------------------------------------- /logutil/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | LogUtil 3 | 4 | -------------------------------------------------------------------------------- /router/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Router 3 | 4 | -------------------------------------------------------------------------------- /widget/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Widget 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongmin2002345/MyTikTok/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /camera/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: rootProject.file('library.gradle') 2 | 3 | dependencies { 4 | implementation deps.rxjava2.rxjava 5 | } 6 | -------------------------------------------------------------------------------- /http/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: rootProject.file('library.gradle') 2 | 3 | dependencies { 4 | implementation deps.rxjava2.rxjava 5 | } 6 | -------------------------------------------------------------------------------- /push/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: rootProject.file('library.gradle') 2 | 3 | dependencies { 4 | implementation deps.rxjava2.rxjava 5 | } 6 | -------------------------------------------------------------------------------- /router/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: rootProject.file('library.gradle') 2 | 3 | dependencies { 4 | implementation deps.rxjava2.rxjava 5 | } 6 | -------------------------------------------------------------------------------- /video/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: rootProject.file('library.gradle') 2 | 3 | dependencies { 4 | implementation deps.rxjava2.rxjava 5 | } 6 | -------------------------------------------------------------------------------- /widget/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: rootProject.file('library.gradle') 2 | 3 | dependencies { 4 | implementation deps.rxjava2.rxjava 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongmin2002345/MyTikTok/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongmin2002345/MyTikTok/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /camera/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /http/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /logutil/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: rootProject.file('library.gradle') 2 | 3 | dependencies { 4 | implementation deps.rxjava2.rxjava 5 | } 6 | -------------------------------------------------------------------------------- /mvps/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /push/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /router/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /video/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /widget/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongmin2002345/MyTikTok/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongmin2002345/MyTikTok/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongmin2002345/MyTikTok/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /logutil/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':mvps', ':logutil', ':router', ':push', ':http', ':video', ':camera', ':widget', ':annotation', ':annotation-processing' 2 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dongmin2002345/MyTikTok/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/dongmin2002345/MyTikTok/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/dongmin2002345/MyTikTok/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/dongmin2002345/MyTikTok/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/dongmin2002345/MyTikTok/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MyTikTok 2 | **文章列表**: 3 | - 1.[从零开始写一个抖音App——开始](https://www.jianshu.com/p/e92bd896ac35) 4 | - 2.[从零开始写一个抖音App——基本架构图与MVPs](https://www.jianshu.com/p/3867f6cf4e82) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | /.idea 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /annotation/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | implementation fileTree(dir: 'libs', include: ['*.jar']) 5 | compile deps.gson 6 | compile deps.guava_android 7 | } 8 | 9 | sourceCompatibility = "1.8" 10 | targetCompatibility = "1.8" 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jul 14 23:20:14 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 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/field/FieldProvider.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.field; 2 | 3 | import java.util.Set; 4 | 5 | public interface FieldProvider { 6 | // void init(); 7 | 8 | T get(String fieldName); 9 | 10 | T get(Class tClass); 11 | 12 | Set allFields(); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/inject/Injector.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.inject; 2 | 3 | import java.util.Set; 4 | 5 | public interface Injector { 6 | void inject(T target, Object accessible); 7 | 8 | Set allNames(); 9 | 10 | Set allTypes(); 11 | 12 | void reset(T target); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/whensunset/mytiktok/TestPresenter.java: -------------------------------------------------------------------------------- 1 | package com.example.whensunset.mytiktok; 2 | 3 | import com.example.annotation.inject.Inject; 4 | import com.example.mvps.BasePresenter; 5 | 6 | /** 7 | * Created by whensunset on 2018/8/6. 8 | */ 9 | 10 | public class TestPresenter extends BasePresenter { 11 | 12 | @Inject("mTextString") 13 | String mTextString; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /mvps/build.gradle: -------------------------------------------------------------------------------- 1 | apply from: rootProject.file('library.gradle') 2 | 3 | dependencies { 4 | implementation deps.rxjava2.rxjava 5 | implementation deps.rxjava2.rxandroid 6 | implementation deps.rxlifecycle2.android 7 | implementation deps.rxlifecycle2.components 8 | implementation deps.guava_android 9 | implementation deps.butterknife 10 | compileOnly project(':annotation') 11 | } 12 | -------------------------------------------------------------------------------- /annotation-processing/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | implementation fileTree(dir: 'libs', include: ['*.jar']) 5 | compile deps.squareup.javapoet 6 | compile deps.guava_android 7 | compile deps.gson 8 | implementation 'com.google.auto.service:auto-service:1.0-rc2' 9 | compile project(":annotation") 10 | } 11 | 12 | sourceCompatibility = 1.8 13 | targetCompatibility = 1.8 14 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/invoker/ForInvoker.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.invoker; 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 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface ForInvoker { 11 | String methodId(); 12 | } 13 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/inject/NamedParam.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.inject; 2 | 3 | public class NamedParam { 4 | public Object mParam; 5 | public String mName; 6 | 7 | public NamedParam(String name, Object param) { 8 | mName = name; 9 | mParam = param; 10 | } 11 | 12 | public static NamedParam of(String name, Object param) { 13 | return new NamedParam(name, param); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/StandFor.java: -------------------------------------------------------------------------------- 1 | package com.example.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 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.SOURCE) 10 | public @interface StandFor { 11 | Class forClass() default StandFor.class; 12 | 13 | String forName() default ""; 14 | } 15 | -------------------------------------------------------------------------------- /annotation-processing/src/main/java/com/example/annotation_processing/invoker/InvokeMethod.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation_processing.invoker; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.io.Serializable; 6 | 7 | public class InvokeMethod implements Serializable { 8 | private static final long serialVersionUID = 3213094601630741209L; 9 | @SerializedName("class") 10 | public String className; 11 | @SerializedName("method") 12 | public String methodName; 13 | } 14 | -------------------------------------------------------------------------------- /annotation-processing/src/main/java/com/example/annotation_processing/invoker/Invocation.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation_processing.invoker; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.io.Serializable; 6 | 7 | public class Invocation implements Serializable { 8 | private static final long serialVersionUID = -4920559937353697607L; 9 | @SerializedName("target") 10 | public InvokeMethod mTarget; 11 | @SerializedName("invoker") 12 | public InvokeMethod mInvoker; 13 | } 14 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/inject/Reference.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.inject; 2 | 3 | public class Reference { 4 | Object mProvider; 5 | String mName; 6 | 7 | public Reference(Object provider, String fieldName) { 8 | mProvider = provider; 9 | mName = fieldName; 10 | } 11 | 12 | public T get() { 13 | return (T) ProviderHolder.fetch(mProvider, mName); 14 | } 15 | 16 | public void set(T value) { 17 | ProviderHolder.set(mProvider, mName, value); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/inject/Inject.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.inject; 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 | @Retention(RetentionPolicy.SOURCE) 9 | @Target(ElementType.FIELD) 10 | public @interface Inject { 11 | String value() default ""; 12 | 13 | boolean acceptNull() default false; 14 | 15 | boolean crashNoFound() default true; 16 | } 17 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/field/Field.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.field; 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 | @Retention(RetentionPolicy.SOURCE) 9 | @Target(ElementType.FIELD) 10 | public @interface Field { 11 | String value() default ""; 12 | 13 | Class asClass() default Object.class; 14 | 15 | boolean doAdditionalFetch() default false; 16 | } 17 | -------------------------------------------------------------------------------- /library.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | compileOptions { 7 | sourceCompatibility JavaVersion.VERSION_1_8 8 | targetCompatibility JavaVersion.VERSION_1_8 9 | } 10 | defaultConfig { 11 | minSdkVersion 15 12 | } 13 | } 14 | 15 | dependencies { 16 | api deps.support.annotations 17 | 18 | api fileTree(dir: "libs", include: '*.jar') 19 | 20 | } 21 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/invoker/InvokeBy.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.invoker; 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 | @Retention(RetentionPolicy.SOURCE) 9 | @Target(ElementType.METHOD) 10 | public @interface InvokeBy { 11 | Class invokerClass() default InvokeBy.class; 12 | 13 | String invokerName() default ""; 14 | 15 | String methodId(); 16 | 17 | int priority() default Integer.MAX_VALUE; 18 | } 19 | -------------------------------------------------------------------------------- /mvps/src/main/java/com/example/mvps/Presenter.java: -------------------------------------------------------------------------------- 1 | package com.example.mvps; 2 | 3 | import android.app.Activity; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by whensunset on 2018/7/28. 8 | * presenter的接口,对外使用的时候只允许使用该接口的api进行交互。 9 | * 对于其子类,不允许有public的参数、方法和带参构造函数,目的是为了解耦 10 | */ 11 | 12 | public interface Presenter { 13 | void init(View view); 14 | 15 | void bind(Object... callerContext); 16 | 17 | void destroy(); 18 | 19 | Presenter add(Presenter presenter); 20 | 21 | boolean isInitialized(); 22 | 23 | Activity getActivity(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /annotation-processing/src/main/java/com/example/annotation_processing/util/ClassBuilder.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation_processing.util; 2 | 3 | import com.squareup.javapoet.TypeSpec; 4 | 5 | /** 6 | * 这一层是没有任何跟 apt 相关的元素的,理论上可以复用到任何解析场景下,是输出用的 model. 7 | * 需要初始化三个 Field,因为 super 构造函数调用只能是第一行,所以没办法用代码固定这一点. 8 | */ 9 | public abstract class ClassBuilder { 10 | protected String mClassName; 11 | protected String mPackage; 12 | protected TypeSpec.Builder mType; 13 | 14 | public final String getClassName() { 15 | return mClassName; 16 | } 17 | 18 | public final String getPackage() { 19 | return mPackage; 20 | } 21 | 22 | public abstract TypeSpec.Builder build(); 23 | } 24 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/field/Fetcher.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.field; 2 | 3 | import java.util.Set; 4 | 5 | public interface Fetcher { 6 | Fetcher init(); 7 | 8 | T get(I target, Class tClass); 9 | 10 | T get(I target, String field); 11 | 12 | void set(I target, String field, T value); 13 | 14 | void set(I target, Class tClass, T value); 15 | 16 | default void set(I target, T value) { 17 | if (value == null) { 18 | return; 19 | } 20 | set(target, (Class) value.getClass(), value); 21 | } 22 | 23 | Set allFields(I target); 24 | 25 | Set allFieldNames(I target); 26 | 27 | Set allTypes(I target); 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/whensunset/mytiktok/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.whensunset.mytiktok; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.example.annotation.field.Field; 7 | 8 | public class MainActivity extends AppCompatActivity { 9 | 10 | @Field("mTextString") 11 | String mTextString = "mTextString"; 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | 18 | TestPresenter testPresenter = new TestPresenter(); 19 | testPresenter.init(getWindow().getDecorView()); 20 | testPresenter.bind(this); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /camera/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 | -------------------------------------------------------------------------------- /http/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 | -------------------------------------------------------------------------------- /logutil/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 | -------------------------------------------------------------------------------- /mvps/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 | -------------------------------------------------------------------------------- /push/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 | -------------------------------------------------------------------------------- /router/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 | -------------------------------------------------------------------------------- /video/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 | -------------------------------------------------------------------------------- /widget/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 | -------------------------------------------------------------------------------- /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 | org.gradle.daemon=true 14 | org.gradle.parallel=trues 15 | org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 16 | -Dorg.gradle.debug=true 17 | # When configured, Gradle will run in incubating parallel mode. 18 | # This option should only be used with decoupled projects. More details, visit 19 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 20 | # org.gradle.parallel=true 21 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/inject/ObjectProvider.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.inject; 2 | 3 | import java.util.Set; 4 | 5 | public abstract class ObjectProvider { 6 | public abstract T fetch(F obj, String fieldName); 7 | 8 | public abstract T fetch(F obj, Class tClass); 9 | 10 | public abstract void set(F obj, String fieldName, T value); 11 | 12 | public abstract void set(F obj, Class tClass, T value); 13 | 14 | public abstract Set allFieldNames(Object obj); 15 | 16 | public abstract Set allTypes(Object obj); 17 | 18 | public final boolean have(F obj, String fieldName) { 19 | Set names = allFieldNames(obj); 20 | return names != null && names.contains(fieldName); 21 | } 22 | 23 | public final boolean have(F obj, Class tClass) { 24 | Set types = allTypes(obj); 25 | return types != null && types.contains(tClass); 26 | } 27 | 28 | public abstract Set allDirectFields(Object obj); 29 | } 30 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | defaultConfig { 6 | applicationId "com.example.whensunset.mytiktok" 7 | minSdkVersion 15 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | 13 | javaCompileOptions { 14 | annotationProcessorOptions { 15 | arguments += [providerInterfaceName : 'com.example.whensunset.mytiktok.FetcherHelper', 16 | injectorInterfaceName : 'com.example.whensunset.mytiktok.InjectorHelper'] 17 | } 18 | } 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | } 27 | 28 | 29 | dependencies { 30 | implementation deps.support.app_compat 31 | implementation project(path: ':annotation') 32 | implementation project(path: ':mvps') 33 | annotationProcessor project(":annotation-processing") 34 | } 35 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/inject/Injectors.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.inject; 2 | 3 | import com.example.annotation.invoker.ForInvoker; 4 | import com.google.common.base.Optional; 5 | 6 | import java.util.Collections; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | import javax.annotation.Nonnull; 12 | 13 | public class Injectors { 14 | public static final String INVOKER_ID = "Injectors"; 15 | private static final Injector NOOP = new Injector() { 16 | @Override 17 | public void inject(Object target, Object accessible) {} 18 | 19 | @Override 20 | public Set allNames() { 21 | return Collections.emptySet(); 22 | } 23 | 24 | @Override 25 | public Set allTypes() { 26 | return Collections.emptySet(); 27 | } 28 | 29 | @Override 30 | public void reset(Object target) { 31 | 32 | } 33 | }; 34 | private static final Map sInjectors = new HashMap<>(); 35 | 36 | public static void putAll(Map map) { 37 | sInjectors.putAll(map); 38 | } 39 | 40 | public static void put(Class clazz, Injector injector) { 41 | sInjectors.put(clazz, injector); 42 | } 43 | 44 | @Nonnull 45 | public static Injector injector(Class clazz) { 46 | return Optional.fromNullable(sInjectors.get(clazz)).or(NOOP); 47 | } 48 | 49 | @ForInvoker(methodId = INVOKER_ID) 50 | public static void init(){ 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/inject/ProviderHolder.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.inject; 2 | 3 | import java.util.Set; 4 | 5 | public final class ProviderHolder { 6 | private static ObjectProvider sObjectProvider; 7 | 8 | public static void setProvider(ObjectProvider objectProvider) { 9 | sObjectProvider = objectProvider; 10 | } 11 | 12 | public static T fetch(F target, Class tClass) { 13 | return sObjectProvider.fetch(target, tClass); 14 | } 15 | 16 | public static T fetch(F target, String fieldName) { 17 | return sObjectProvider.fetch(target, fieldName); 18 | } 19 | 20 | public static boolean have(F obj, String fieldName) { 21 | return sObjectProvider.have(obj, fieldName); 22 | } 23 | 24 | public static boolean have(F obj, Class tClass) { 25 | return sObjectProvider.have(obj, tClass); 26 | } 27 | 28 | public static void set(F obj, String fieldName, T value) { 29 | sObjectProvider.set(obj, fieldName, value); 30 | } 31 | 32 | public static void set(F obj, Class tClass, T value) { 33 | sObjectProvider.set(obj, tClass, value); 34 | } 35 | 36 | public static Set allFieldNames(Object obj) { 37 | return sObjectProvider.allFieldNames(obj); 38 | } 39 | 40 | public static Set allTypes(Object obj) { 41 | return sObjectProvider.allTypes(obj); 42 | } 43 | 44 | public static Set allFields(Object obj) { 45 | return sObjectProvider.allDirectFields(obj); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /annotation-processing/src/main/java/com/example/annotation_processing/inject/InjectorHelperBuilder.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation_processing.inject; 2 | 3 | import com.example.annotation.inject.Injectors; 4 | import com.example.annotation_processing.util.AptUtils; 5 | import com.example.annotation_processing.util.ClassBuilder; 6 | import com.squareup.javapoet.AnnotationSpec; 7 | import com.squareup.javapoet.ClassName; 8 | import com.squareup.javapoet.CodeBlock; 9 | import com.squareup.javapoet.MethodSpec; 10 | import com.squareup.javapoet.TypeName; 11 | import com.squareup.javapoet.TypeSpec; 12 | 13 | import javax.lang.model.element.Modifier; 14 | 15 | public class InjectorHelperBuilder extends ClassBuilder { 16 | private final MethodSpec.Builder mInit; 17 | 18 | public InjectorHelperBuilder(String pkg, String className, AnnotationSpec generated) { 19 | mPackage = pkg; 20 | mClassName = className; 21 | mType = TypeSpec.classBuilder(className) 22 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL); 23 | // .addAnnotation(generated); 24 | AnnotationSpec invokeBy = AptUtils.invokeBy(ClassName.get(Injectors.class), 25 | CodeBlock.of("$T.INVOKER_ID", Injectors.class)); 26 | mInit = 27 | MethodSpec.methodBuilder("init") 28 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC) 29 | .addAnnotation(invokeBy); 30 | } 31 | 32 | public void onNewInjector(TypeName rootType, InjectorBuilder builder) { 33 | mInit.addStatement("$T.put($T.class, new $T())", Injectors.class, 34 | rootType, ClassName.get(builder.getPackage(), builder.getClassName())); 35 | } 36 | 37 | @Override 38 | public TypeSpec.Builder build() { 39 | return mType.addMethod(mInit.build()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /annotation-processing/src/main/java/com/example/annotation_processing/util/BaseProcessor.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation_processing.util; 2 | 3 | import com.squareup.javapoet.AnnotationSpec; 4 | import com.squareup.javapoet.JavaFile; 5 | import com.squareup.javapoet.TypeSpec; 6 | 7 | import java.io.IOException; 8 | import java.io.Writer; 9 | 10 | import javax.annotation.Generated; 11 | import javax.annotation.processing.AbstractProcessor; 12 | import javax.annotation.processing.Filer; 13 | import javax.annotation.processing.Messager; 14 | import javax.annotation.processing.ProcessingEnvironment; 15 | import javax.lang.model.util.Elements; 16 | import javax.lang.model.util.Types; 17 | 18 | public abstract class BaseProcessor extends AbstractProcessor { 19 | protected Filer mFiler; 20 | protected Elements mUtils; 21 | protected Types mTypes; 22 | protected Messager mMessager; 23 | 24 | protected AnnotationSpec mGeneratedAnnotation; 25 | 26 | @Override 27 | public synchronized void init(ProcessingEnvironment processingEnv) { 28 | super.init(processingEnv); 29 | mFiler = processingEnv.getFiler(); 30 | mUtils = processingEnv.getElementUtils(); 31 | mTypes = processingEnv.getTypeUtils(); 32 | mMessager = processingEnv.getMessager(); 33 | mGeneratedAnnotation = AnnotationSpec.builder(Generated.class) 34 | .addMember("value", "$S", getClass().getName()) 35 | .build(); 36 | } 37 | 38 | protected void writeClass(String pkg, String name, TypeSpec.Builder type) { 39 | try { 40 | Writer writer = mFiler.createSourceFile(pkg + "." + name).openWriter(); 41 | JavaFile.builder(pkg, type.build()).build().writeTo(writer); 42 | writer.flush(); 43 | writer.close(); 44 | } catch (IOException e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | 49 | protected void writeClass(ClassBuilder builder) { 50 | writeClass(builder.getPackage(), builder.getClassName(), builder.build()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /annotation-processing/src/main/java/com/example/annotation_processing/util/AptUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation_processing.util; 2 | 3 | import com.example.annotation.StandFor; 4 | import com.example.annotation.invoker.InvokeBy; 5 | import com.squareup.javapoet.AnnotationSpec; 6 | import com.squareup.javapoet.ClassName; 7 | import com.squareup.javapoet.CodeBlock; 8 | 9 | import java.util.List; 10 | 11 | import javax.lang.model.element.Element; 12 | import javax.lang.model.element.TypeElement; 13 | import javax.lang.model.type.MirroredTypeException; 14 | import javax.lang.model.type.TypeMirror; 15 | import javax.lang.model.util.Elements; 16 | 17 | public class AptUtils { 18 | public static String nameFrom(Element element) { 19 | return element.getSimpleName().toString(); 20 | } 21 | 22 | public static String packageFrom(Elements elements, Element element) { 23 | return elements.getPackageOf(element).toString(); 24 | } 25 | 26 | public static boolean isEmpty(String string) { 27 | return string == null || string.isEmpty(); 28 | } 29 | 30 | public static boolean isEmpty(Object[] array) { 31 | return array == null || array.length == 0; 32 | } 33 | 34 | public static TypeMirror fromClass(Elements elements, Class clazz) { 35 | return elements.getTypeElement(clazz.getCanonicalName()).asType(); 36 | } 37 | 38 | public static TypeMirror getInterface(TypeElement rootClass) { 39 | List interfaces = ((TypeElement) rootClass).getInterfaces(); 40 | return interfaces.size() == 1 ? interfaces.get(0) : null; 41 | } 42 | 43 | public static Element resovleStandFor(Element element, Elements elements) { 44 | StandFor standFor = element.getAnnotation(StandFor.class); 45 | if (standFor == null) { 46 | return element; 47 | } 48 | String className = standFor.forName(); 49 | if (isEmpty(className)) { 50 | try { 51 | standFor.forClass(); 52 | } catch (MirroredTypeException e) { 53 | className = e.getTypeMirror().toString(); 54 | } 55 | } 56 | return elements.getTypeElement(className); 57 | } 58 | 59 | public static AnnotationSpec invokeBy(ClassName invokerClass, CodeBlock methodId) { 60 | return AnnotationSpec.builder(InvokeBy.class) 61 | .addMember("invokerClass", "$T.class", invokerClass) 62 | .addMember("methodId", "$L", methodId) 63 | .build(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/inject/ObjectProviderImpl.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.inject; 2 | 3 | import com.example.annotation.field.Fetchers; 4 | 5 | import java.util.Collections; 6 | import java.util.HashSet; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | public class ObjectProviderImpl extends ObjectProvider { 11 | @Override 12 | public final T fetch(F obj, Class tClass) { 13 | if (obj == null) { 14 | return null; 15 | } 16 | if (obj.getClass() == tClass) { 17 | return (T) obj; 18 | } 19 | T result = (T) Fetchers.fetcherNonNull(obj.getClass()).get(obj, tClass); 20 | if (result != null) { 21 | return result; 22 | } 23 | return null; 24 | } 25 | 26 | @Override 27 | public T fetch(F obj, String fieldName) { 28 | if (obj == null) { 29 | return null; 30 | } 31 | T result = null; 32 | if (obj instanceof NamedParam && fieldName.equals(((NamedParam) obj).mName)) { 33 | return (T) ((NamedParam) obj).mParam; 34 | } 35 | if (obj instanceof Map && ((Map) obj).containsKey(fieldName)) { 36 | result = (T) ((Map) obj).get(fieldName); 37 | } 38 | if (result != null) { 39 | return result; 40 | } 41 | result = (T) Fetchers.fetcherNonNull(obj.getClass()).get(obj, fieldName); 42 | if (result != null) { 43 | return result; 44 | } 45 | return null; 46 | } 47 | 48 | @Override 49 | public void set(F obj, String fieldName, T value) { 50 | if (obj == null) { 51 | return; 52 | } 53 | Fetchers.fetcherNonNull(obj.getClass()).set(obj, fieldName, value); 54 | } 55 | 56 | @Override 57 | public void set(F obj, Class tClass, T value) { 58 | if (obj == null) { 59 | return; 60 | } 61 | Fetchers.fetcherNonNull(obj.getClass()).set(obj, tClass, value); 62 | } 63 | 64 | @Override 65 | public Set allFieldNames(Object obj) { 66 | if (obj == null) { 67 | return Collections.emptySet(); 68 | } 69 | if (obj instanceof NamedParam) { 70 | return Collections.singleton(((NamedParam) obj).mName); 71 | } 72 | if (obj instanceof Map) { 73 | return ((Map) obj).keySet(); 74 | } 75 | return Fetchers.fetcherNonNull(obj.getClass()).allFieldNames(obj); 76 | } 77 | 78 | @Override 79 | public Set allTypes(Object obj) { 80 | if (obj == null) { 81 | return Collections.emptySet(); 82 | } 83 | final Set result = new HashSet<>(); 84 | result.addAll(Fetchers.fetcherNonNull(obj.getClass()).allTypes(obj)); 85 | result.add(obj.getClass()); 86 | return result; 87 | } 88 | 89 | @Override 90 | public Set allDirectFields(Object obj) { 91 | if (obj == null) { 92 | return Collections.emptySet(); 93 | } 94 | return Fetchers.fetcherNonNull(obj.getClass()).allFields(obj); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /annotation/src/main/java/com/example/annotation/field/Fetchers.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation.field; 2 | 3 | import com.example.annotation.invoker.ForInvoker; 4 | import com.google.common.base.Optional; 5 | 6 | import java.util.Collections; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | import javax.annotation.Nonnull; 12 | 13 | public class Fetchers { 14 | public static final String INVOKER_ID = "Injectors"; 15 | 16 | private static final Fetcher NOOP = new Fetcher() { 17 | @Override 18 | public Fetcher init() { 19 | return this; 20 | } 21 | 22 | @Override 23 | public Object get(Object target, Class tClass) { 24 | return null; 25 | } 26 | 27 | @Override 28 | public Object get(Object target, String field) { 29 | return null; 30 | } 31 | 32 | @Override 33 | public void set(Object target, String field, Object value) { 34 | 35 | } 36 | 37 | @Override 38 | public void set(Object target, Class aClass, Object value) { 39 | 40 | } 41 | 42 | @Override 43 | public Set allFields(Object target) { 44 | return Collections.emptySet(); 45 | } 46 | 47 | @Override 48 | public Set allFieldNames(Object target) { 49 | return Collections.emptySet(); 50 | } 51 | 52 | @Override 53 | public Set allTypes(Object target) { 54 | return Collections.emptySet(); 55 | } 56 | }; 57 | private static final Map sFetchers = new HashMap<>(); 58 | 59 | public static void putAll(Map map) { 60 | sFetchers.putAll(map); 61 | } 62 | 63 | public static void put(Class clazz, Fetcher fetcher) { 64 | sFetchers.put(clazz, fetcher); 65 | } 66 | 67 | public static Fetcher fetcher(Class clazz) { 68 | Fetcher fetcher = sFetchers.get(clazz); 69 | if (fetcher == null) { 70 | fetcher = findSuperFetcher(clazz); 71 | if (fetcher != null) { 72 | sFetchers.put(clazz, fetcher); 73 | } 74 | } 75 | return fetcher == null ? null : fetcher.init(); 76 | } 77 | 78 | @Nonnull 79 | public static Fetcher fetcherNonNull(Class clazz) { 80 | return Optional.fromNullable(fetcher(clazz)).or(NOOP); 81 | } 82 | 83 | public static Fetcher findSuperFetcher(Class clazz) { 84 | clazz = clazz.getSuperclass(); 85 | while (clazz != null) { 86 | Fetcher fetcher = sFetchers.get(clazz); 87 | if (fetcher != null) { 88 | return fetcher.init(); 89 | } 90 | clazz = clazz.getSuperclass(); 91 | } 92 | return null; 93 | } 94 | 95 | @Nonnull 96 | public static Fetcher superFetcherNonNull(Class clazz) { 97 | return Optional.fromNullable(findSuperFetcher(clazz)).or(NOOP); 98 | } 99 | 100 | @ForInvoker(methodId = INVOKER_ID) 101 | public static void init(){ 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /annotation-processing/src/main/java/com/example/annotation_processing/util/SortedElement.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation_processing.util; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.util.ArrayList; 5 | import java.util.Comparator; 6 | import java.util.HashMap; 7 | import java.util.HashSet; 8 | import java.util.Iterator; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | import javax.annotation.processing.RoundEnvironment; 14 | import javax.lang.model.element.Element; 15 | import javax.lang.model.element.ElementKind; 16 | import javax.lang.model.element.TypeElement; 17 | 18 | public final class SortedElement implements Iterable>> { 19 | public static final Comparator COMPARATOR = new Comparator() { 20 | @Override 21 | public int compare(Element element, Element t1) { 22 | // toString就是获得element名 23 | return element.toString().compareTo(t1.toString()); 24 | } 25 | }; 26 | 27 | private final Map> mClassFieldsMapping = new HashMap<>(); 28 | private final Set mFieldSortedFlags = new HashSet<>(); 29 | 30 | public static SortedElement fromRoundEnv(RoundEnvironment roundEnv, 31 | Class annotation) { 32 | SortedElement sortedElement = new SortedElement(); 33 | for (Element field : roundEnv.getElementsAnnotatedWith(annotation)) { 34 | Element rootClass = field.getEnclosingElement(); 35 | if (rootClass == null || rootClass.getKind() != ElementKind.CLASS) { 36 | continue; 37 | } 38 | List fields = sortedElement.mClassFieldsMapping.get(rootClass); 39 | if (fields == null) { 40 | fields = new ArrayList<>(); 41 | sortedElement.mClassFieldsMapping.put((TypeElement) rootClass, fields); 42 | } 43 | fields.add(field); 44 | } 45 | return sortedElement; 46 | } 47 | 48 | private SortedElement() {} 49 | 50 | @Override 51 | public Iterator>> iterator() { 52 | return new Iterator>>() { 53 | private List mKeys; 54 | private Iterator mKeyIterator; 55 | { 56 | mKeys = new ArrayList<>(mClassFieldsMapping.keySet()); 57 | mKeys.sort(COMPARATOR); 58 | mKeyIterator = mKeys.iterator(); 59 | } 60 | 61 | @Override 62 | public boolean hasNext() { 63 | return mKeyIterator.hasNext(); 64 | } 65 | 66 | @Override 67 | public Map.Entry> next() { 68 | TypeElement rootClass = mKeyIterator.next(); 69 | List elements = mClassFieldsMapping.get(rootClass); 70 | if (mFieldSortedFlags.add(rootClass)) { 71 | elements.sort(COMPARATOR); 72 | } 73 | return new HashMap.SimpleEntry<>(rootClass, elements); 74 | } 75 | }; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /mvps/src/main/java/com/example/mvps/BasePresenter.java: -------------------------------------------------------------------------------- 1 | package com.example.mvps; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.ContextWrapper; 6 | import android.content.res.Resources; 7 | import android.view.View; 8 | 9 | import com.example.annotation.inject.Injector; 10 | import com.example.annotation.inject.Injectors; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import butterknife.ButterKnife; 16 | 17 | /** 18 | * Created by whensunset on 2018/7/28. 19 | */ 20 | 21 | public class BasePresenter implements Presenter { 22 | 23 | private View mRootView; 24 | private List mChildPresenterList = new ArrayList<>(); 25 | private boolean isValid = true; 26 | private boolean isInitialized = false; 27 | private Injector mInjector = null; 28 | 29 | 30 | @Override 31 | public void init(View view) { 32 | if (isInitialized()) { 33 | 34 | throw new IllegalStateException("Presenter只能被初始化一次!"); 35 | } 36 | 37 | try { 38 | mRootView = view; 39 | 40 | ButterKnife.bind(this, view); 41 | 42 | onInit(); 43 | 44 | initChildren(); 45 | } catch (Exception e) { 46 | isValid = false; 47 | // TODO 创建失败,之后打log或者埋点 48 | } 49 | isInitialized = true; 50 | } 51 | 52 | protected void onInit() { 53 | 54 | } 55 | 56 | private void initChildren() { 57 | for (Presenter childPresenter: mChildPresenterList) { 58 | childPresenter.init(mRootView); 59 | } 60 | } 61 | 62 | @Override 63 | public void bind(Object... callerContext) { 64 | if (!isValid) { 65 | return; 66 | } 67 | 68 | if (!isInitialized) { 69 | throw new IllegalStateException("Presenter必须先初始化!"); 70 | } 71 | 72 | if (mInjector == null) { 73 | mInjector = Injectors.injector(getClass()); 74 | } 75 | 76 | mInjector.reset(this); 77 | 78 | if (callerContext != null) { 79 | for (Object context : callerContext) { 80 | mInjector.inject(this, context); 81 | } 82 | } 83 | 84 | onBind(callerContext); 85 | 86 | bindChild(callerContext); 87 | } 88 | 89 | protected void onBind(Object... callerContext) { 90 | 91 | } 92 | 93 | private void bindChild(Object... callerContext) { 94 | for (Presenter childPresenter : mChildPresenterList) { 95 | childPresenter.bind(callerContext); 96 | } 97 | } 98 | 99 | @Override 100 | public void destroy() { 101 | if (!isValid) { 102 | return; 103 | } 104 | 105 | if (!isInitialized) { 106 | throw new IllegalStateException("Presenter必须先初始化!"); 107 | } 108 | 109 | onDestroy(); 110 | 111 | destroyChild(); 112 | } 113 | 114 | protected void onDestroy() { 115 | 116 | } 117 | 118 | private void destroyChild() { 119 | for (Presenter childPresenter : mChildPresenterList) { 120 | childPresenter.destroy(); 121 | } 122 | } 123 | 124 | @Override 125 | public boolean isInitialized() { 126 | return isInitialized; 127 | } 128 | 129 | @Override 130 | public Activity getActivity() { 131 | Context context = getContext(); 132 | while (context instanceof ContextWrapper) { 133 | if (context instanceof Activity) { 134 | return (Activity) context; 135 | } 136 | context = ((ContextWrapper) context).getBaseContext(); 137 | } 138 | return (Activity) getContext(); 139 | } 140 | 141 | @Override 142 | public Presenter add(Presenter presenter) { 143 | if (presenter == null) { 144 | return this; 145 | } 146 | mChildPresenterList.add(presenter); 147 | 148 | if (isInitialized()) { 149 | presenter.init(mRootView); 150 | } 151 | return this; 152 | } 153 | 154 | public View getRootView() { 155 | return mRootView; 156 | } 157 | 158 | protected final Context getContext() { 159 | return mRootView == null ? null : mRootView.getContext(); 160 | } 161 | 162 | protected final Resources getResources() { 163 | if (getContext() == null) { 164 | return null; 165 | } 166 | return getContext().getResources(); 167 | } 168 | 169 | protected final String getString(int id) { 170 | if (getContext() == null) { 171 | return null; 172 | } 173 | return getContext().getString(id); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /annotation-processing/src/main/java/com/example/annotation_processing/inject/InjectorProcessor.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation_processing.inject; 2 | 3 | import com.example.annotation.inject.Inject; 4 | import com.example.annotation.inject.Reference; 5 | import com.example.annotation_processing.util.BaseProcessor; 6 | import com.example.annotation_processing.util.SortedElement; 7 | import com.google.auto.service.AutoService; 8 | import com.squareup.javapoet.AnnotationSpec; 9 | import com.squareup.javapoet.TypeName; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | import javax.annotation.processing.ProcessingEnvironment; 16 | import javax.annotation.processing.Processor; 17 | import javax.annotation.processing.RoundEnvironment; 18 | import javax.annotation.processing.SupportedAnnotationTypes; 19 | import javax.annotation.processing.SupportedOptions; 20 | import javax.annotation.processing.SupportedSourceVersion; 21 | import javax.lang.model.SourceVersion; 22 | import javax.lang.model.element.Element; 23 | import javax.lang.model.element.ElementKind; 24 | import javax.lang.model.element.Modifier; 25 | import javax.lang.model.element.TypeElement; 26 | import javax.lang.model.type.TypeMirror; 27 | 28 | import static com.example.annotation_processing.inject.InjectorProcessor.CLASS_NAME; 29 | 30 | @AutoService(Processor.class) 31 | @SupportedAnnotationTypes({"com.example.annotation.inject.Inject"}) 32 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 33 | @SupportedOptions(CLASS_NAME) 34 | public class InjectorProcessor extends BaseProcessor { 35 | public static final String CLASS_NAME = "injectorInterfaceName"; 36 | private boolean mHasProcessed; 37 | private String mPackage; 38 | private String mClassName; 39 | 40 | @Override 41 | public synchronized void init(ProcessingEnvironment processingEnv) { 42 | super.init(processingEnv); 43 | String fullName = processingEnv.getOptions().get(CLASS_NAME); 44 | if (fullName == null) { 45 | mHasProcessed = true; 46 | return; 47 | } 48 | int lastDot = fullName.lastIndexOf('.'); 49 | mClassName = fullName.substring(lastDot + 1); 50 | mPackage = fullName.substring(0, lastDot); 51 | mMessager = processingEnv.getMessager(); 52 | } 53 | 54 | @Override 55 | public boolean process(Set set, RoundEnvironment roundEnv) { 56 | if (mHasProcessed) { 57 | return false; 58 | } 59 | InjectorHelperBuilder helperBuilder = 60 | new InjectorHelperBuilder(mPackage, mClassName, mGeneratedAnnotation); 61 | SortedElement sortedElement = SortedElement.fromRoundEnv(roundEnv, Inject.class); 62 | for (Map.Entry> type : sortedElement) { 63 | List fields = type.getValue(); 64 | if (fields == null) { 65 | continue; 66 | } 67 | InjectorBuilder injectorBuilder = 68 | generateForClass(mGeneratedAnnotation, type.getKey(), fields); 69 | if (injectorBuilder == null) { 70 | continue; 71 | } 72 | writeClass(injectorBuilder); 73 | helperBuilder.onNewInjector(TypeName.get(mTypes.erasure(type.getKey().asType())), 74 | injectorBuilder); 75 | } 76 | writeClass(helperBuilder); 77 | mHasProcessed = true; 78 | return false; 79 | } 80 | 81 | private InjectorBuilder generateForClass(AnnotationSpec generated, 82 | Element rootClass, List fields) { 83 | if (rootClass == null || rootClass.getKind() != ElementKind.CLASS) { 84 | return null; 85 | } 86 | String pkg = mUtils.getPackageOf(rootClass).toString(); 87 | InjectorBuilder builder = 88 | new InjectorBuilder(pkg, rootClass.getSimpleName().toString(), generated, 89 | TypeName.get(rootClass.asType())); 90 | for (Element field : fields) { 91 | if (field.getKind() != ElementKind.FIELD || field.getModifiers().contains(Modifier.STATIC)) { 92 | continue; 93 | } 94 | Inject injectAnnotation = field.getAnnotation(Inject.class); 95 | if (injectAnnotation == null) { 96 | continue; 97 | } 98 | String fieldKey = injectAnnotation.value(); 99 | String fieldName = field.getSimpleName().toString(); 100 | TypeMirror fieldType = mTypes.erasure(field.asType()); 101 | if ("".equals(fieldKey)) { 102 | builder.onByTypeField(fieldName, injectAnnotation.crashNoFound(), 103 | injectAnnotation.acceptNull(), TypeName.get(fieldType)); 104 | } else { 105 | builder.onByNameField(fieldName, injectAnnotation.crashNoFound(), 106 | injectAnnotation.acceptNull(), TypeName.get(fieldType), fieldKey, 107 | mTypes.isSameType(fieldType, 108 | mTypes.erasure(mUtils.getTypeElement(Reference.class.getName()).asType()))); 109 | } 110 | } 111 | return builder; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /versions.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Shared file between builds so that they can all use the same dependencies and 3 | * maven repositories. 4 | **/ 5 | 6 | ext.deps = [:] 7 | def versions = [:] 8 | 9 | versions.support = "26.1.0" 10 | versions.rxjava = "2.1.8" 11 | versions.rx_android = "2.0.1" 12 | versions.retrofit = "2.4.0" 13 | 14 | versions.gson = "2.8.2" 15 | versions.butterknife = "8.5.1" 16 | versions.guava = "20.0" 17 | versions.guava_android = "24.0-android" 18 | versions.rxlifecycle2 = "2.1.0" 19 | versions.android_arch_lifecycle_ext = "1.1.1" 20 | 21 | versions.greendao = "3.2.2" 22 | versions.javapoet = "1.9.0" 23 | 24 | versions.fresco = "1.3.0" 25 | versions.animated_gif = "1.3.0" 26 | versions.android_sdk = "4.8.2" 27 | 28 | versions.lottie = "2.5.4" 29 | 30 | versions.annotation_api = "1.3" 31 | versions.android_processes = "1.0.9" 32 | 33 | versions.netty = "4.1.0.CR1" 34 | versions.okhttp = "3.10.0" 35 | versions.rxpermissons2 = "0.9.4@aar" 36 | versions.rxbinding = "2.0.0" 37 | 38 | // support 相关的逻辑 39 | def support = [:] 40 | support.annotations = "com.android.support:support-annotations:$versions.support" 41 | support.app_compat = "com.android.support:appcompat-v7:$versions.support" 42 | support.recyclerview = "com.android.support:recyclerview-v7:$versions.support" 43 | support.design = "com.android.support:design:$versions.support" 44 | support.percent = "com.android.support:percent:$versions.support" 45 | support.v4 = "com.android.support:support-v4:$versions.support" 46 | support.v13 = "com.android.support:support-v13:$versions.support" 47 | support.core_utils = "com.android.support:support-core-utils:$versions.support" 48 | support.fragment = "com.android.support:support-fragment:$versions.support" 49 | support.emoji = "com.android.support:support-emoji:$versions.support" 50 | deps.support = support 51 | 52 | def retrofit = [:] 53 | retrofit.runtime = "com.squareup.retrofit2:retrofit:$versions.retrofit" 54 | retrofit.gson = "com.squareup.retrofit2:converter-gson:$versions.retrofit" 55 | retrofit.mock = "com.squareup.retrofit2:retrofit-mock:$versions.retrofit" 56 | retrofit.rxjava2 = "com.squareup.retrofit2:adapter-rxjava2:$versions.retrofit" 57 | retrofit.scalars = "com.squareup.retrofit2:converter-scalars:$versions.retrofit" 58 | deps.retrofit = retrofit 59 | deps.okhttp_logging_interceptor = "com.squareup.okhttp3:logging-interceptor:${versions.okhttp_logging_interceptor}" 60 | 61 | def rxjava2 = [:] 62 | rxjava2.rxjava = "io.reactivex.rxjava2:rxjava:$versions.rxjava" 63 | rxjava2.rxandroid = "io.reactivex.rxjava2:rxandroid:$versions.rx_android" 64 | deps.rxjava2 = rxjava2 65 | 66 | def squareup = [:] 67 | squareup.javapoet = "com.squareup:javapoet:$versions.javapoet" 68 | squareup.okhttp3 = "com.squareup.okhttp3:okhttp:$versions.okhttp" 69 | squareup.leakcanary_no_op = "com.squareup.leakcanary:leakcanary-android-no-op:$versions.leakcanary" 70 | squareup.leakcanary = "com.squareup.leakcanary:leakcanary-android:$versions.leakcanary" 71 | deps.squareup = squareup 72 | 73 | def rxlifecycle2 = [:] 74 | rxlifecycle2.android = "com.trello.rxlifecycle2:rxlifecycle-android:$versions.rxlifecycle2" 75 | rxlifecycle2.components = "com.trello.rxlifecycle2:rxlifecycle-components:$versions.rxlifecycle2" 76 | deps.rxlifecycle2 = rxlifecycle2 77 | 78 | def android_arch = [:] 79 | android_arch.lifecycle_ext = "android.arch.lifecycle:extensions:$versions.android_arch_lifecycle_ext" 80 | deps.android_arch = android_arch 81 | 82 | def netty = [:] 83 | netty.common = "io.netty:netty-common:$versions.netty" 84 | netty.buffer = "io.netty:netty-buffer:$versions.netty" 85 | netty.transport = "io.netty:netty-transport:$versions.netty" 86 | netty.codec = "io.netty:netty-codec:$versions.netty" 87 | deps.netty = netty 88 | 89 | //greenrobot 相关 90 | def greenrobot = [:] 91 | greenrobot.greendao = "org.greenrobot:greendao:$versions.greendao" 92 | greenrobot.eventbus = "org.greenrobot:eventbus:$versions.eventbus" 93 | greenrobot.eventbus_process = "org.greenrobot:eventbus-annotation-processor:$versions.eventbus" 94 | deps.greenrobot = greenrobot 95 | 96 | def lint = [:] 97 | lint.api = "com.android.tools.lint:lint-api:$versions.lint" 98 | lint.checks = "com.android.tools.lint:lint-checks:$versions.lint" 99 | lint.uast = "com.android.tools.external.org-jetbrains:uast:$versions.lint" 100 | deps.lint = lint 101 | 102 | 103 | deps.butterknife = "com.jakewharton:butterknife:$versions.butterknife" 104 | deps.butterknife_compiler = "com.jakewharton:butterknife-compiler:$versions.butterknife" 105 | deps.gson = "com.google.code.gson:gson:$versions.gson" 106 | deps.guava_android = "com.google.guava:guava:$versions.guava_android" 107 | 108 | deps.annotation_api = "javax.annotation:javax.annotation-api:$versions.annotation_api" 109 | deps.rxpermissons2 = "com.tbruyelle.rxpermissions2:rxpermissions:$versions.rxpermissons2" 110 | deps.rxbinding = "com.jakewharton.rxbinding2:rxbinding:$versions.rxbinding" 111 | 112 | ext.deps = deps 113 | -------------------------------------------------------------------------------- /从零开始写一个抖音App——开始(mdn).md: -------------------------------------------------------------------------------- 1 | # 一、写在前面 2 | >这个坑可能会持续很久,之前开过好几个坑,但是都不长久。原因是计划赶不上变化。每过一段时间我都会感觉有更重要的事情要去做,所以之前开的坑就被我抛弃了。但是这一次不同,具体的不同点我会在下面一一列举出来。 3 | 4 | - 1.关于目的:作者目前在抖音的竞品里面做android端的视频拍摄和编辑这块。大公司大家也知道,各个业务都是分层的,所以我们平时的业务都是在音视频架构组封装的sdk之上进行的。所以一旦时间长久了自身的竞争力就会减弱,毕竟没有掌握“核心科技”。好在sdk的源码是内部开放的,所以我可以读读源码去了解“核心科技”。正好读了源码之后可以练练手,自己去实现一遍,这就是本项目诞生的初衷。 5 | - 2.关于代码:首先由于保密协议,我们公司的源码是绝对不能开源出去的,因此本项目的全部代码都将会是我自己根据读源码获取到的思想最后实现的。所以本项目最终在性能、兼容性、代码可读性都是不如原本的代码的,所以如果有读者要集成入商业项目的话请慎重。 6 | - 3.关于项目:本项目预计会持续1-2年的时间,除非我中途离职了,否则一定会坚持更新。目前的预期是每两周更新一篇博客,与此同时更新一版feature。此外本项目虽然说是写一个抖音App,但其实最终项目中的实现只会是抖音App中各种特效的实现集合,至于和服务器交互还有界面的交互方面我并不会花费很长时间去写。当然除了抖音App中的特效,我有一个爱好是深度学习,所以我将会制作一些基于深度学习的特效集成到项目中,所以有这方面爱好的同学也可以和我多多交流。 7 | - 4.飨读者:本项目虽然是我自己的练手项目,但是也有部分目的是希望让一部分不甘于现状想深入学习android的同学和我共同进步。所以大家有项目上面的问题和github上的issue都欢迎和我交流。 8 | 9 | # 二、项目概述 10 | >这一节我主要是想对未来项目做一个概述吧,说一下项目的技术栈,这样也好让大家对项目有一个概念。 11 | 12 | - 1.MVP:这是项目的架构方式,熟悉架构的同学应该知道现在android中有三种架构方式:MVC、MVP、MVVM。为何选择MVP想必大家也都清楚,首先MVC非常老旧也有一堆缺点所以第一个排除。然后是MVVM虽然这种架构已经被“吹”了很久了,但是到现在为止也没有一个成熟完整的解决方案,虽然我之前几个自己写的项目都是使用MVVM(databinding为基础),但是那都是小打小闹。据我所知的“大厂”中没有使用MVVM来当做真正的解决方案的。所以如果大家对这种MVVM有兴趣的话,可以去看看我之前写的几篇博客和项目。另外说一句,我会自己从零开始封装一个MVP的框架,也算对自己一个挑战吧。 13 | - 2.okhttp+retrofit:这两个框架想必大家都很熟悉了,我就不多说了。只是我会在项目的过程中对这两个框架进行深度的定制以实现一些有意思的东西,所以这一方面还是有点看头的。 14 | - 3.fresco:这个也是广为人知的框架了,可以说这是最强大和性能最好的图片框架了吧。不是我“吹”这个框架,虽然他的缺点有一些比如:侵入性强、框架比较重。但是一个像抖音这样的音视频app,使用fresco是非常适合的,其他图片框架像glide、picasso等等,都有功能不全的问题。此外因为我完整的读过fresco的源码,所以我也可以对fresco进行比较深度的定制。对fresco源码感兴趣的同学也可以去翻翻我之前的博客。 15 | - 4.插件化+组件化+热修复:这几个东西算是类似的吧,我还是一个个说 16 | + 1.插件化:几个优点分别是,多成员负责不同的模块的时候加快编译速度、各个模块解耦、减小发版的包大小按需加载。使用的框架我倾向于自己写一个,但是不知道有没有时间,最后可能会在tinker和360RePlugin中选择一个吧。 17 | + 2.组件化:其实和插件化类似,主要用于解耦模块,用到的技术是路由组件和gradle分模块依赖技术,倾向于自己写一个。 18 | + 3.热修复:主要用于应对线上bug,应该会在andfix和Robust里面选一个,毕竟我们的项目对这个要求不高 19 | - 5.google新MVVM组件:这是google最近发布的组件套装ViewModel+Room+Lifecycles+LiveData,虽然我们用的是MVP框架,但是google推荐的组件使用起来还是用好处的。 20 | + 1.首先Room就可以抛弃了,他是数据库的组件,我们有更好的选择 21 | + 2.ViewModel:第一个功能可以使ViewModel以及ViewModel 中的数据在屏幕旋转或配置更改引起的Activity重建时存活下来,重建后数据可继续使用,第二个功能可以帮助开发者轻易实现Fragment与Fragment之间,Activity与Fragment之间的通讯以及共享数据,所以还是很有必要使用的,可以集成在我们的MVP框架中。 22 | + 3.Lifecycles:这个就不用说了,生命周期组件是Android官方架构组件中的核心组件,它可以使各种实例作为观察者与Activity和Fragment等具有生命周期特性的组件绑定在一起,LiveData和ViewModel,都是基于此组件,简而言之就是,你将需要绑定生命周期的实例注册给该组件,该组件就会在你指定的某个生命周期方法执行时通知这个实例。 23 | + 4.LiveData:这个组件其实做的和Rxjava的事情类似,第一个功能是观察者模式,在Value发生变化时通知之前注册的所有观察者,第二功能是基于生命周期组件与Activity和Fragment等具有生命周期特性的组件绑定在一起,在生命周期发生改变时停止或恢复之前的事件。了解Rxjava的同学都知道这些功能在其拓展包中都是有的,所以这个组件我们可以不使用。 24 | - 6.Rxjava:不用说Rxjava的生态已经很成熟了,他有RxCache,RxLifecycle,RxAndroid,RxPermission,Retrofit-Adapter等大量并且强大的衍生库,在写项目的过程中我也会对使用到的Rxjava的功能进行分析。 25 | - 7.AOP技术:这个技术用处很多,比如日志记录、自动埋点等等,目前候选的框架有:AspectJ、APT和ASM这些到时候看实现日志和埋点框架的时候再选吧。 26 | - 8.JNA:可能有些同学不了解这个框架,其实这就是一个代替jni的对java开发者更加友好的调用native方法的框架,因为我前面说了我们这个项目主要目的是实现抖音中的各种特效,所以必不可少的会用到jni,因此我用了JNA这个更加方便的库。 27 | - 9.opencv:目前我也没有开始了解我司到底是使用了哪种技术来实现视频中的各种特效,所以暂定是opencv,以后随着我深入核心代码,可能框架会改变。**再次声明,本项目中的代码并不会有任何我司的核心代码,所以大家最好别将项目代码用于商业项目** 28 | - 10.深度学习框架:我之前也使用过tensorflow lite,但是这个框架太简陋了,所以现在的有一个新选择是腾讯的ncnn,这个问题将会在到了该使用深度学习的时候再选择。另外如果对tensorflow lite有兴趣的同学可以去看看我之前写的**图片处理APP**的和一个使用demo。 29 | - 11.数据库:初步预想是使用GreenDao,使用起来方便一些,而且我们对于数据库使用程度并不强,我司就是使用这个框架,所以还是可靠的。 30 | - 12.WebView:最近hybird app非常流行,所以项目里先把这个技术占一个坑吧,到时候可能会实现一个简陋的与js互相调用的WebView容器吧,到时候再看。 31 | - 13.**待补充,不知道还有什么技术或者框架是一个成熟的App应该使用的,所以之后想到会陆续补充,如果大家有什么建议,特别、十分、非常、超级的欢迎在评论区指出来,十分感谢!** 32 | 33 | # 三、尾巴 34 | >在最后想和大家聊聊我的想法与未来android工程师的发展道路。 35 | 36 | ## 1.如何当好一个大厂的螺丝钉 37 | - 1.都说**面试造核弹,入职拧螺丝**,我虽然是一个才刚入职的应届生,但是感触也颇多。入职之前希望做点有意思的事情,能多么多么牛逼。但是其实大多数人进入了大厂都是有可替代性的,真正不可替代的只是顶部的小部分人,所以最后大多数人每天的工作就是维护业务,接需求,修bug。我就是大多数人中的一份子,我怕我就这样适应了这种工作,然后一天天的重复相同的事情失去了激情,最终为了钱在码代码退变成为一名光荣的“代码搬运工”。 38 | - 2.所以如何当好一个大厂的螺丝钉?辞职是不可能辞职的,这几年都不会辞职的,大厂福利和薪资又这么好,只能去利用公司的资源学习更多的东西,才能勉强维持激情。所以我才会决定开这个项目的坑,不断学习公司里的技术做一些自己没做过的事情,保持初心提升自己。 39 | 40 | ## 2.android工程师的发展道路 41 | - 1.“我要转前端”、“我要转后台”、“android开发没人要了”......想必这些话在各种android技术群里时长会出现,这样也加深了不少android工程师的焦虑感。但是我想说的只有一句话:牛逼的人做啥都牛逼,只有人的问题没有方向的问题。 42 | - 2.为什么android工程师没人要了?首先我们得明确一点,在可预期的时间范围内(五年内)android这个平台是不会死的。在我看来只有更加新型的交互操作系统(ar、vr、mr?)会革了android的命,但是最近是看不到希望的。明确了上面一点,我们就可以聊聊为啥android没人要了。 43 | + 1.第一个原因可能是经济问题导致公司倒闭了,那么App就少了,这个无解。 44 | + 2.第二个原因应该就是hybird App、类react native框架、小程序、flutter等等跨平台解决方案的崛起。 45 | * 1.首先hybird App可以不用管,因为“慢”是一个永恒的问题。其次“小程序”在我看来也不是一个大问题,但凡一个大一点的互联网公司都会有原生App,因为你不可能把自己的小命完全交给腾讯。 46 | * 2.所以现在在和原生android抢地盘的就是类react native框架与flutter。其实我们可以这么看,这两种其实都是通过js写业务逻辑然后将绘制逻辑通过c++交给native控件,所以只要你学的深并不用怕这两个东西抢了你的饭碗。 47 | - 3.android工程师的发展道路在哪?我目前想到的一个词就是“深挖”,具体体现在:深入三方框架源码、深入framework、深入c++层、深入音视频处理以及深度学习。我想大家从我前面的项目概述中就能知道为啥了。**成为一个只有很少人能替代的人,才是你我的核心竞争力**。 48 | 49 | ## 3.计划 50 | >说了这么多,我来定一个计划吧,也算是接下来整个项目的计划表,这样也好推动我进行项目的更新。其实上面概述中提到的技术我只熟悉1/3,希望我能坚持下去在接下来的1年里能完成这个对于我来说宏大的项目。(暂定两周一个推进节点,写一篇博客,项目大更新一版) 51 | 52 | + 1.完成项目基础结构的搭建,熟悉概述中的各个不熟悉的技术 53 | + 2.完成自建MVP框架和组件化路由框架的编写 54 | + 3.将google新MVVM组件集成进自建MVP组件中,完成日志框架编写 55 | + 4.完成埋点框架编写,写一个python服务器进行埋点数据以及其他需要上传服务器的数据展示(上) 56 | + 5.深入学习ncnn框架,写一个python服务器进行埋点数据以及其他需要上传服务器的数据展示(下) 57 | + 6-10.开始画界面,着重学习公司核心代码,着重继续学习深度学习 58 | + 11-20.界面画完开始实现特效每个节点实现两个特效,同时学习公司核心代码和继续学习深度学习 59 | + 20之后.离一年还有6个节点,算是进行收尾工作以及为中间突发事件留下的机动时间。 60 | + 另外.前1-5中会适当学习公司核心代码以及深度学习 -------------------------------------------------------------------------------- /annotation-processing/src/main/java/com/example/annotation_processing/invoker/InvokerProcessor.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation_processing.invoker; 2 | 3 | import com.example.annotation.invoker.ForInvoker; 4 | import com.example.annotation.invoker.InvokeBy; 5 | import com.example.annotation_processing.util.BaseProcessor; 6 | import com.google.gson.Gson; 7 | 8 | import java.io.IOException; 9 | import java.io.Writer; 10 | import java.util.HashSet; 11 | import java.util.Optional; 12 | import java.util.Set; 13 | 14 | import javax.annotation.processing.ProcessingEnvironment; 15 | import javax.annotation.processing.RoundEnvironment; 16 | import javax.annotation.processing.SupportedAnnotationTypes; 17 | import javax.annotation.processing.SupportedOptions; 18 | import javax.annotation.processing.SupportedSourceVersion; 19 | import javax.lang.model.SourceVersion; 20 | import javax.lang.model.element.Element; 21 | import javax.lang.model.element.ElementKind; 22 | import javax.lang.model.element.ExecutableElement; 23 | import javax.lang.model.element.Modifier; 24 | import javax.lang.model.element.TypeElement; 25 | import javax.lang.model.type.MirroredTypeException; 26 | import javax.tools.Diagnostic; 27 | import javax.tools.FileObject; 28 | import javax.tools.StandardLocation; 29 | 30 | import static com.example.annotation_processing.invoker.InvokerProcessor.INVOKER_CONFIG; 31 | 32 | 33 | @SupportedAnnotationTypes("com.example.annotation.invoker.InvokeBy") 34 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 35 | @SupportedOptions(INVOKER_CONFIG) 36 | public class InvokerProcessor extends BaseProcessor { 37 | public static final String INVOKER_CONFIG = "invokerConfig"; 38 | private String mFile; 39 | // 多轮apt只能创建一次文件,只能保存writer 40 | private Writer mWriter; 41 | 42 | @Override 43 | public synchronized void init(ProcessingEnvironment processingEnv) { 44 | super.init(processingEnv); 45 | mFile = processingEnv.getOptions().get(INVOKER_CONFIG); 46 | } 47 | 48 | @Override 49 | public boolean process(Set set, RoundEnvironment roundEnv) { 50 | Set invocations = new HashSet<>(); 51 | for (Element method : roundEnv.getElementsAnnotatedWith(InvokeBy.class)) { 52 | if (method == null || method.getKind() != ElementKind.METHOD) { 53 | continue; 54 | } 55 | Set modifiers = method.getModifiers(); 56 | if (!modifiers.contains(Modifier.PUBLIC) || !modifiers.contains(Modifier.STATIC)) { 57 | mMessager.printMessage(Diagnostic.Kind.ERROR, 58 | "方法必须是 public static的。" + method.getSimpleName(), method); 59 | continue; 60 | } 61 | if (((ExecutableElement) method).getParameters().size() != 0) { 62 | mMessager.printMessage(Diagnostic.Kind.ERROR, 63 | "方法必须是无参的。" + method.getSimpleName(), method); 64 | continue; 65 | } 66 | InvokeBy invokeBy = method.getAnnotation(InvokeBy.class); 67 | if (invokeBy == null) { 68 | continue; 69 | } 70 | Element clazz = method.getEnclosingElement(); 71 | if (clazz == null || clazz.getKind() != ElementKind.CLASS) { 72 | continue; 73 | } 74 | // 构建Invocation关系 75 | Invocation invocation = new Invocation(); 76 | invocation.mTarget = new InvokeMethod(); 77 | invocation.mTarget.className = clazz.toString(); 78 | invocation.mTarget.methodName = method.getSimpleName().toString(); 79 | invocation.mInvoker = new InvokeMethod(); 80 | // https://area-51.blog/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ 81 | // 获得invoker类名 82 | try { 83 | invokeBy.invokerClass(); 84 | } catch (MirroredTypeException e) { 85 | invocation.mInvoker.className = e.getTypeMirror().toString(); 86 | } 87 | // 通过methodId 反査到对应的 88 | String methodId = invokeBy.methodId(); 89 | Element invokerElement = mUtils.getTypeElement(invocation.mInvoker.className); 90 | for (Element invokerMethod : invokerElement.getEnclosedElements()) { 91 | if (invokerMethod == null || invokerMethod.getKind() != ElementKind.METHOD) { 92 | continue; 93 | } 94 | if (Optional.ofNullable(invokerMethod.getAnnotation(ForInvoker.class)) 95 | .map(forInvoker -> forInvoker.methodId().equals(methodId)).orElse(false)) { 96 | invocation.mInvoker.methodName = invokerMethod.getSimpleName().toString(); 97 | } 98 | } 99 | invocations.add(invocation); 100 | } 101 | 102 | if (invocations.isEmpty()) { 103 | return false; 104 | } 105 | boolean success = true; 106 | Writer writer = openWriter(); 107 | try { 108 | Gson gson = new Gson(); 109 | String json = gson.toJson(invocations); 110 | writer.append(json); 111 | writer.append("\n"); 112 | writer.flush(); 113 | } catch (IOException e) { 114 | success = false; 115 | } 116 | return success; 117 | } 118 | 119 | @Override 120 | protected void finalize() throws Throwable { 121 | super.finalize(); 122 | if (mWriter != null) { 123 | mWriter.close(); 124 | } 125 | } 126 | 127 | private Writer openWriter() { 128 | if (mWriter != null) { 129 | return mWriter; 130 | } 131 | FileObject source = null; 132 | try { 133 | source = mFiler.createResource(StandardLocation.CLASS_OUTPUT, "", 134 | mFile); 135 | mWriter = source.openWriter(); 136 | } catch (Exception e) { 137 | e.printStackTrace(); 138 | } 139 | return mWriter; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /annotation-processing/src/main/java/com/example/annotation_processing/inject/InjectorBuilder.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation_processing.inject; 2 | 3 | import com.example.annotation.inject.Injector; 4 | import com.example.annotation.inject.ProviderHolder; 5 | import com.example.annotation.inject.Reference; 6 | import com.example.annotation_processing.util.ClassBuilder; 7 | import com.squareup.javapoet.AnnotationSpec; 8 | import com.squareup.javapoet.ClassName; 9 | import com.squareup.javapoet.MethodSpec; 10 | import com.squareup.javapoet.ParameterizedTypeName; 11 | import com.squareup.javapoet.TypeName; 12 | import com.squareup.javapoet.TypeSpec; 13 | 14 | import java.util.HashSet; 15 | import java.util.Set; 16 | 17 | import javax.lang.model.element.Modifier; 18 | 19 | public class InjectorBuilder extends ClassBuilder { 20 | private static final String sClassSuffix = "Injector"; 21 | private static final String sFieldInjectNames = "mInjectNames"; 22 | private static final String sFieldInjectTypes = "mInjectTypes"; 23 | private static final String sParamTarget = "target"; 24 | private static final String sParamAccessible = "accessible"; 25 | 26 | private final MethodSpec.Builder mConstructor; 27 | private final MethodSpec.Builder mAllFields; 28 | private final MethodSpec.Builder mAllTypes; 29 | private final MethodSpec.Builder mInject; 30 | private final MethodSpec.Builder mReset; 31 | 32 | public InjectorBuilder(String pkg, String className, AnnotationSpec generated, 33 | TypeName rootType) { 34 | mPackage = pkg; 35 | mClassName = className.replace("$", "_") + sClassSuffix; 36 | TypeName fieldNamesType = 37 | ParameterizedTypeName.get(ClassName.get(Set.class), TypeName.get(String.class)); 38 | TypeName fieldTypesType = 39 | ParameterizedTypeName.get(ClassName.get(Set.class), TypeName.get(Class.class)); 40 | mType = TypeSpec.classBuilder(mClassName) 41 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 42 | // .addAnnotation(generated) 43 | .addField(fieldNamesType, sFieldInjectNames, Modifier.PRIVATE, Modifier.FINAL) 44 | .addField(fieldTypesType, sFieldInjectTypes, Modifier.PRIVATE, Modifier.FINAL) 45 | .addSuperinterface(ParameterizedTypeName.get(ClassName.get(Injector.class), 46 | rootType)); 47 | mConstructor = MethodSpec.constructorBuilder() 48 | .addModifiers(Modifier.PUBLIC) 49 | .addStatement("$L = new $T<$T>()", sFieldInjectNames, HashSet.class, String.class) 50 | .addStatement("$L = new $T<$T>()", sFieldInjectTypes, HashSet.class, Class.class); 51 | mAllFields = MethodSpec.methodBuilder("allNames") 52 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 53 | .addStatement("return $L", sFieldInjectNames) 54 | .returns(fieldNamesType); 55 | mAllTypes = MethodSpec.methodBuilder("allTypes") 56 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 57 | .addStatement("return $L", sFieldInjectTypes) 58 | .returns(fieldTypesType); 59 | mInject = MethodSpec.methodBuilder("inject") 60 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 61 | .addParameter(rootType, sParamTarget) 62 | .addParameter(Object.class, sParamAccessible); 63 | mReset = MethodSpec.methodBuilder("reset") 64 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 65 | .addParameter(rootType, sParamTarget); 66 | } 67 | 68 | public void onByTypeField(String fieldName, boolean crashNoFound, boolean acceptNull, 69 | TypeName fieldType) { 70 | if (crashNoFound) { 71 | mConstructor.addStatement("$L.add($T.class)", sFieldInjectTypes, fieldType); 72 | } 73 | if (acceptNull) { 74 | mInject.beginControlFlow("if ($T.have($L, $T.class))", ProviderHolder.class, sParamAccessible, 75 | fieldType); 76 | mInject.addStatement("Object valFor$L = $T.fetch($L, $T.class)", fieldName, 77 | ProviderHolder.class, sParamAccessible, fieldType); 78 | mInject.addStatement("$L.$L = ($T)valFor$L", sParamTarget, fieldName, fieldType, fieldName); 79 | mInject.endControlFlow(); 80 | } else { 81 | mInject.addStatement("Object valFor$L = $T.fetch($L, $T.class)", fieldName, 82 | ProviderHolder.class, sParamAccessible, fieldType); 83 | mInject.beginControlFlow("if (valFor$L != null)", fieldName) 84 | .addStatement("$L.$L = ($T)valFor$L", sParamTarget, fieldName, fieldType, fieldName) 85 | .endControlFlow(); 86 | } 87 | addToReset(fieldName, fieldType); 88 | } 89 | 90 | public void onByNameField(String fieldName, boolean crashNoFound, boolean acceptNull, 91 | TypeName fieldType, String fieldKey, boolean isReference) { 92 | if (crashNoFound) { 93 | mConstructor.addStatement("$L.add($S)", sFieldInjectNames, fieldKey); 94 | } 95 | if (isReference) { 96 | mInject.beginControlFlow("if ($T.have($L, $S))", ProviderHolder.class, sParamAccessible, 97 | fieldKey); 98 | mInject.addStatement("$L.$L = new $T<>($L, $S)", sParamTarget, fieldName, 99 | ClassName.get(Reference.class), sParamAccessible, fieldKey); 100 | mInject.endControlFlow(); 101 | } else { 102 | if (acceptNull) { 103 | mInject.beginControlFlow("if ($T.have($L, $S))", ProviderHolder.class, sParamAccessible, 104 | fieldKey); 105 | mInject.addStatement("Object valFor$L = $T.fetch($L, $S)", fieldName, 106 | ProviderHolder.class, sParamAccessible, fieldKey); 107 | mInject.addStatement("$L.$L = ($T)valFor$L", sParamTarget, fieldName, fieldType, fieldName); 108 | mInject.endControlFlow(); 109 | } else { 110 | mInject.addStatement("Object valFor$L = $T.fetch($L, $S)", fieldName, 111 | ProviderHolder.class, sParamAccessible, fieldKey); 112 | mInject.beginControlFlow("if (valFor$L != null)", fieldName) 113 | .addStatement("$L.$L = ($T)valFor$L", sParamTarget, fieldName, fieldType, fieldName) 114 | .endControlFlow(); 115 | } 116 | } 117 | addToReset(fieldName, fieldType); 118 | } 119 | 120 | private void addToReset(String fieldName, TypeName fieldType) { 121 | String resetValue; 122 | if (fieldType.isPrimitive() || fieldType.isBoxedPrimitive()) { 123 | fieldType = fieldType.unbox(); 124 | } 125 | if (fieldType.equals(TypeName.INT) 126 | || fieldType.equals(TypeName.SHORT) 127 | || fieldType.equals(TypeName.BYTE)) { 128 | resetValue = "0"; 129 | } else if (fieldType.equals(TypeName.LONG)) { 130 | resetValue = "0L"; 131 | } else if (fieldType.equals(TypeName.FLOAT)) { 132 | resetValue = "0.0f"; 133 | } else if (fieldType.equals(TypeName.DOUBLE)) { 134 | resetValue = "0.0"; 135 | } else if (fieldType.equals(TypeName.CHAR)) { 136 | resetValue = "java.lang.Character.MIN_VALUE"; 137 | } else if (fieldType.equals(TypeName.BOOLEAN)) { 138 | resetValue = "false"; 139 | } else { 140 | resetValue = "null"; 141 | } 142 | 143 | mReset.addStatement("$L.$L = $L", sParamTarget, fieldName, resetValue); 144 | } 145 | 146 | @Override 147 | public TypeSpec.Builder build() { 148 | return mType 149 | .addMethod(mConstructor.build()) 150 | .addMethod(mAllFields.build()) 151 | .addMethod(mAllTypes.build()) 152 | .addMethod(mInject.build()) 153 | .addMethod(mReset.build()); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /annotation-processing/src/main/java/com/example/annotation_processing/field/FieldProcessor.java: -------------------------------------------------------------------------------- 1 | package com.example.annotation_processing.field; 2 | 3 | import com.example.annotation.field.Fetcher; 4 | import com.example.annotation.field.Fetchers; 5 | import com.example.annotation.field.Field; 6 | import com.example.annotation.inject.ProviderHolder; 7 | import com.example.annotation_processing.util.AptUtils; 8 | import com.example.annotation_processing.util.BaseProcessor; 9 | import com.example.annotation_processing.util.SortedElement; 10 | import com.google.auto.service.AutoService; 11 | import com.squareup.javapoet.AnnotationSpec; 12 | import com.squareup.javapoet.ClassName; 13 | import com.squareup.javapoet.CodeBlock; 14 | import com.squareup.javapoet.MethodSpec; 15 | import com.squareup.javapoet.ParameterizedTypeName; 16 | import com.squareup.javapoet.TypeName; 17 | import com.squareup.javapoet.TypeSpec; 18 | import com.squareup.javapoet.TypeVariableName; 19 | 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.HashSet; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.Optional; 26 | import java.util.Set; 27 | 28 | import javax.annotation.processing.ProcessingEnvironment; 29 | import javax.annotation.processing.Processor; 30 | import javax.annotation.processing.RoundEnvironment; 31 | import javax.annotation.processing.SupportedAnnotationTypes; 32 | import javax.annotation.processing.SupportedOptions; 33 | import javax.annotation.processing.SupportedSourceVersion; 34 | import javax.lang.model.SourceVersion; 35 | import javax.lang.model.element.Element; 36 | import javax.lang.model.element.ElementKind; 37 | import javax.lang.model.element.Modifier; 38 | import javax.lang.model.element.TypeElement; 39 | import javax.lang.model.type.DeclaredType; 40 | import javax.lang.model.type.MirroredTypeException; 41 | import javax.lang.model.type.TypeKind; 42 | import javax.lang.model.type.TypeMirror; 43 | import javax.tools.Diagnostic; 44 | 45 | import static com.example.annotation_processing.field.FieldProcessor.CLASS_NAME; 46 | 47 | @AutoService(Processor.class) 48 | @SupportedAnnotationTypes({"com.example.annotation.field.Field"}) 49 | @SupportedSourceVersion(SourceVersion.RELEASE_8) 50 | @SupportedOptions(CLASS_NAME) 51 | public class FieldProcessor extends BaseProcessor { 52 | public static final String CLASS_NAME = "providerInterfaceName"; 53 | private boolean mHasProcessed; 54 | private String mPackage; 55 | private String mClassName; 56 | 57 | private DuplicationChecker mChecker = new DuplicationChecker(); 58 | 59 | @Override 60 | public synchronized void init(ProcessingEnvironment processingEnv) { 61 | super.init(processingEnv); 62 | String fullName = processingEnv.getOptions().get(CLASS_NAME); 63 | if (fullName == null) { 64 | mHasProcessed = true; 65 | return; 66 | } 67 | int lastDot = fullName.lastIndexOf('.'); 68 | mClassName = fullName.substring(lastDot + 1); 69 | mPackage = fullName.substring(0, lastDot); 70 | } 71 | 72 | @Override 73 | public boolean process(Set set, RoundEnvironment roundEnv) { 74 | if (mHasProcessed) { 75 | return false; 76 | } 77 | AnnotationSpec invokeBy = 78 | AptUtils.invokeBy(ClassName.get(Fetchers.class), 79 | CodeBlock.of("$T.INVOKER_ID", Fetchers.class)); 80 | MethodSpec.Builder init = 81 | MethodSpec.methodBuilder("init") 82 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC) 83 | .addAnnotation(invokeBy); 84 | TypeSpec.Builder fetcherInitClass = 85 | TypeSpec.classBuilder(mClassName) 86 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL); 87 | SortedElement sortedElement = SortedElement.fromRoundEnv(roundEnv, Field.class); 88 | for (Map.Entry> type : sortedElement) { 89 | List fields = type.getValue(); 90 | if (fields == null) { 91 | continue; 92 | } 93 | generateForClass(mGeneratedAnnotation, init, type.getKey(), fields); 94 | } 95 | fetcherInitClass.addMethod(init.build()); 96 | writeClass(mPackage, mClassName, fetcherInitClass); 97 | mHasProcessed = true; 98 | return false; 99 | } 100 | 101 | private void generateForClass(AnnotationSpec generated, MethodSpec.Builder init, 102 | Element rootClass, List elements) { 103 | if (rootClass == null || rootClass.getKind() != ElementKind.CLASS) { 104 | return; 105 | } 106 | String className = rootClass.getSimpleName().toString() + "Fetcher"; 107 | className = className.replace("$", "_"); 108 | TypeName rootType = TypeName.get(mTypes.erasure(rootClass.asType())); 109 | TypeName fieldNamesType = 110 | ParameterizedTypeName.get(ClassName.get(Set.class), TypeName.get(String.class)); 111 | TypeName fieldTypesType = 112 | ParameterizedTypeName.get(ClassName.get(Set.class), TypeName.get(Class.class)); 113 | TypeName allFieldsReturn = 114 | ParameterizedTypeName.get(ClassName.get(Set.class), TypeName.get(Object.class)); 115 | TypeName fetcherType = ParameterizedTypeName.get(ClassName.get(Fetcher.class), 116 | rootType); 117 | TypeSpec.Builder fetcherClass = 118 | TypeSpec.classBuilder(className) 119 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 120 | .addField(fieldNamesType, "mAccessibleNames", Modifier.PRIVATE, Modifier.FINAL) 121 | .addField(fieldTypesType, "mAccessibleTypes", Modifier.PRIVATE, Modifier.FINAL) 122 | .addField(Fetcher.class, "mSuperFetcher", Modifier.PRIVATE) 123 | .addSuperinterface(fetcherType); 124 | MethodSpec.Builder constructor = MethodSpec.constructorBuilder() 125 | .addModifiers(Modifier.PUBLIC) 126 | .addStatement("mAccessibleNames = new $T<$T>()", HashSet.class, String.class) 127 | .addStatement("mAccessibleTypes = new $T<$T>()", HashSet.class, Class.class); 128 | TypeVariableName typeT = TypeVariableName.get("T"); 129 | MethodSpec.Builder getByNameMethod = MethodSpec.methodBuilder("get") 130 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 131 | .addTypeVariable(typeT) 132 | .addParameter(rootType, "target") 133 | .addParameter(String.class, "fieldName") 134 | .returns(typeT); 135 | MethodSpec.Builder setByNameMethod = MethodSpec.methodBuilder("set") 136 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 137 | .addTypeVariable(typeT) 138 | .addParameter(rootType, "target") 139 | .addParameter(String.class, "fieldName") 140 | .addParameter(typeT, "value"); 141 | MethodSpec.Builder allFields = MethodSpec.methodBuilder("allFields") 142 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 143 | .addParameter(rootType, "target") 144 | .returns(allFieldsReturn) 145 | .addStatement("$T result = new $T()", allFieldsReturn, HashSet.class); 146 | MethodSpec.Builder getByTypeMethod = MethodSpec.methodBuilder("get") 147 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 148 | .addTypeVariable(typeT) 149 | .addParameter(rootType, "target") 150 | .addParameter(Class.class, "tClass") 151 | .returns(typeT); 152 | MethodSpec.Builder setByTypeMethod = MethodSpec.methodBuilder("set") 153 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 154 | .addTypeVariable(typeT) 155 | .addParameter(rootType, "target") 156 | .addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), typeT), "tClass") 157 | .addParameter(typeT, "value"); 158 | MethodSpec.Builder allFieldNames = MethodSpec.methodBuilder("allFieldNames") 159 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 160 | .addParameter(rootType, "target") 161 | .addStatement("$T result = new $T<$T>(mAccessibleNames)", fieldNamesType, HashSet.class, 162 | String.class) 163 | .returns(fieldNamesType); 164 | MethodSpec.Builder allTypes = MethodSpec.methodBuilder("allTypes") 165 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 166 | .addParameter(rootType, "target") 167 | .addStatement("$T result = new $T<$T>(mAccessibleTypes)", fieldTypesType, HashSet.class, 168 | Class.class) 169 | .returns(fieldTypesType); 170 | 171 | for (Element field : elements) { 172 | if (field.getKind() != ElementKind.FIELD || field.getModifiers().contains(Modifier.STATIC)) { 173 | continue; 174 | } 175 | genByField(constructor, getByNameMethod, setByNameMethod, getByTypeMethod, setByTypeMethod, 176 | allFields, field, rootClass.asType()); 177 | checkForAdditionalFetch(getByNameMethod, setByNameMethod, getByTypeMethod, setByTypeMethod, 178 | allFields, allFieldNames, allTypes, field, typeT); 179 | } 180 | getByNameMethod.addStatement("return (T) mSuperFetcher.get(target, fieldName)"); 181 | getByTypeMethod.addStatement("return (T) mSuperFetcher.get(target, tClass)"); 182 | setByNameMethod.addStatement("mSuperFetcher.set(target, fieldName, value)"); 183 | setByTypeMethod.addStatement("mSuperFetcher.set(target, tClass, value)"); 184 | allFields 185 | .addStatement("result.addAll(mSuperFetcher.allFields(target))") 186 | .addStatement("return result"); 187 | allFieldNames 188 | .addStatement("result.addAll(mSuperFetcher.allFieldNames(target))") 189 | .addStatement("return result"); 190 | allTypes 191 | .addStatement("result.addAll(mSuperFetcher.allTypes(target))") 192 | .addStatement("return result"); 193 | fetcherClass 194 | .addMethod(constructor.build()) 195 | .addMethod(buildInit(fetcherType, rootType)) 196 | .addMethod(getByNameMethod.build()) 197 | .addMethod(setByNameMethod.build()) 198 | .addMethod(getByTypeMethod.build()) 199 | .addMethod(setByTypeMethod.build()) 200 | .addMethod(allFieldNames.build()) 201 | .addMethod(allTypes.build()) 202 | .addMethod(allFields.build()); 203 | String pkg = mUtils.getPackageOf(rootClass).toString(); 204 | writeClass(pkg, className, fetcherClass); 205 | init.addStatement("$T.put($T.class, new $T())", Fetchers.class, 206 | mTypes.erasure(rootClass.asType()), ClassName.get(pkg, className)); 207 | } 208 | 209 | private void checkForAdditionalFetch(MethodSpec.Builder getByNameMethod, 210 | MethodSpec.Builder setByNameMethod, MethodSpec.Builder getByTypeMethod, 211 | MethodSpec.Builder setByTypeMethod, MethodSpec.Builder allFields, 212 | MethodSpec.Builder allFieldNames, MethodSpec.Builder allTypes, Element field, 213 | TypeVariableName typeT) { 214 | if (!Optional.ofNullable(field.getAnnotation(Field.class)) 215 | .map(annotation -> annotation.doAdditionalFetch()).orElse(false)) { 216 | return; 217 | } 218 | String fieldName = field.getSimpleName().toString(); 219 | getByNameMethod.addStatement("$T result$L = ($T) $T.fetch(target.$L, fieldName)", typeT, 220 | fieldName, typeT, ProviderHolder.class, fieldName); 221 | getByNameMethod.addCode("if(result$L != null) {\n" + 222 | "return result$L;\n" 223 | + "}\n", fieldName, fieldName); 224 | setByNameMethod.addStatement("$T.set(target.$L, fieldName, value)", ProviderHolder.class, 225 | fieldName); 226 | 227 | getByTypeMethod.addStatement("$T result$L = ($T) $T.fetch(target.$L, tClass)", typeT, 228 | fieldName, typeT, ProviderHolder.class, fieldName); 229 | getByTypeMethod.addCode("if(result$L != null) {\n" + 230 | "return result$L;\n" 231 | + "}\n", fieldName, fieldName); 232 | setByTypeMethod.addStatement("$T.set(target.$L, tClass, value)", ProviderHolder.class, 233 | fieldName); 234 | 235 | allFieldNames.addStatement("result.addAll($T.allFieldNames(target.$L))", ProviderHolder.class, 236 | fieldName); 237 | allTypes.addStatement("result.addAll($T.allTypes(target.$L))", ProviderHolder.class, fieldName); 238 | allFields.addStatement("result.addAll($T.allFields(target.$L))", ProviderHolder.class, 239 | fieldName); 240 | } 241 | 242 | private MethodSpec buildInit(TypeName fetcherType, TypeName rootType) { 243 | // 丑又没办法的代码 244 | String loopCode = "if(mSuperFetcher != null){\n" + 245 | " return this;\n" + 246 | "}\n" + 247 | "mSuperFetcher = $T.superFetcherNonNull($T.class);\n" + 248 | "return this;\n"; 249 | return MethodSpec.methodBuilder("init") 250 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 251 | .addCode(loopCode, Fetchers.class, rootType) 252 | .returns(fetcherType) 253 | .build(); 254 | } 255 | 256 | private void genByField(MethodSpec.Builder constructor, MethodSpec.Builder getByNameMethod, 257 | MethodSpec.Builder setByNameMethod, MethodSpec.Builder getByTypeMethod, 258 | MethodSpec.Builder setByTypeMethod, MethodSpec.Builder allFields, 259 | Element field, TypeMirror rootClass) { 260 | Field fieldAnnotation = field.getAnnotation(Field.class); 261 | if (fieldAnnotation == null) { 262 | return; 263 | } 264 | String fieldKey = fieldAnnotation.value(); 265 | String fieldName = field.getSimpleName().toString(); 266 | if ("".equals(fieldKey)) { 267 | fetchByType(constructor, getByTypeMethod, setByTypeMethod, field, fieldAnnotation, fieldName); 268 | mChecker.onField(field.asType(), rootClass); 269 | } else { 270 | fetchByName(constructor, getByNameMethod, setByNameMethod, field, fieldKey, fieldName); 271 | mChecker.onField(fieldKey, rootClass); 272 | } 273 | TypeMirror fieldType = mTypes.erasure(field.asType()); 274 | TypeName fieldTypeName = TypeName.get(fieldType); 275 | if (fieldTypeName.isPrimitive()) { 276 | allFields.addStatement("result.add(target.$L)", fieldName); 277 | } else { 278 | allFields.beginControlFlow("if (target.$L != null)", fieldName) 279 | .addStatement("result.add(target.$L)", fieldName) 280 | .endControlFlow(); 281 | } 282 | } 283 | 284 | private void fetchByType(MethodSpec.Builder constructor, MethodSpec.Builder getByTypeMethod, 285 | MethodSpec.Builder setByTypeMethod, Element field, Field fieldAnnotation, String fieldName) { 286 | TypeMirror rawType = field.asType(); 287 | try { 288 | fieldAnnotation.asClass(); 289 | } catch (MirroredTypeException e) { 290 | TypeMirror typeMirror = e.getTypeMirror(); 291 | if (!typeMirror.toString().equals(Object.class.getName())) { 292 | rawType = typeMirror; 293 | } 294 | } 295 | if (rawType.getKind() == TypeKind.DECLARED) { 296 | if (!((DeclaredType) rawType).getTypeArguments().isEmpty()) { 297 | // 对于无泛型的Map、List并不识别 298 | mMessager.printMessage(Diagnostic.Kind.WARNING, "泛型类型不能按类型存取 " + fieldName); 299 | return; 300 | } 301 | } 302 | TypeMirror fieldType = mTypes.erasure(rawType); 303 | TypeName fieldTypeName = TypeName.get(fieldType); 304 | if (fieldTypeName.isPrimitive()) { 305 | mMessager.printMessage(Diagnostic.Kind.WARNING, "primitive 类型不能按类型存取 " + fieldName); 306 | return; 307 | } 308 | getByTypeMethod.beginControlFlow("if (tClass == $T.class)", fieldType) 309 | .addStatement("return (T)target.$L", fieldName) 310 | .endControlFlow(); 311 | setByTypeMethod.beginControlFlow("if (tClass == $T.class)", fieldType) 312 | .addStatement("target.$L = ($T)value", fieldName, fieldType) 313 | .addStatement("return") 314 | .endControlFlow(); 315 | constructor.addStatement("mAccessibleTypes.add($T.class)", fieldType); 316 | } 317 | 318 | private void fetchByName(MethodSpec.Builder constructor, MethodSpec.Builder getByNameMethod, 319 | MethodSpec.Builder setByNameMethod, Element field, String fieldKey, String fieldName) { 320 | TypeMirror fieldType = mTypes.erasure(field.asType()); 321 | TypeName fieldTypeName = TypeName.get(fieldType); 322 | if (fieldTypeName.isPrimitive()) { 323 | getByNameMethod.beginControlFlow("if (\"$L\".equals(fieldName))", fieldKey) 324 | .addStatement("return (T)$T.valueOf(target.$L)", fieldTypeName.box(), fieldName) 325 | .endControlFlow(); 326 | setByNameMethod.beginControlFlow("if (\"$L\".equals(fieldName))", fieldKey) 327 | .addStatement("target.$L = ($T)value", fieldName, fieldTypeName.box()) 328 | .addStatement("return") 329 | .endControlFlow(); 330 | } else { 331 | getByNameMethod.beginControlFlow("if (\"$L\".equals(fieldName))", fieldKey) 332 | .addStatement("return (T)target.$L", fieldName) 333 | .endControlFlow(); 334 | setByNameMethod.beginControlFlow("if (\"$L\".equals(fieldName))", fieldKey) 335 | .addStatement("target.$L = ($T)value", fieldName, fieldType) 336 | .addStatement("return") 337 | .endControlFlow(); 338 | } 339 | constructor.addStatement("mAccessibleNames.add(\"$L\")", fieldKey); 340 | } 341 | 342 | private final class DuplicationChecker { 343 | private Map> mFieldNameMapping = new HashMap<>(); 344 | private Map> mFieldTypeMapping = new HashMap<>(); 345 | 346 | public void onField(String fieldName, TypeMirror type) { 347 | List mirrors = 348 | mFieldNameMapping.computeIfAbsent(fieldName, k -> new ArrayList<>()); 349 | type = mTypes.erasure(type); 350 | for (TypeMirror existing : mirrors) { 351 | if (mTypes.isSubtype(existing, type) || mTypes.isSubtype(type, existing)) { 352 | mMessager.printMessage(Diagnostic.Kind.ERROR, 353 | "Field key " + fieldName + " 在定义中冲突,类:" + type.toString() + "与类:" 354 | + existing.toString()); 355 | return; 356 | } 357 | } 358 | mirrors.add(mTypes.erasure(type)); 359 | } 360 | 361 | public void onField(TypeMirror fieldType, TypeMirror type) { 362 | List mirrors = 363 | mFieldTypeMapping.computeIfAbsent(fieldType, k -> new ArrayList<>()); 364 | type = mTypes.erasure(type); 365 | for (TypeMirror existing : mirrors) { 366 | if (mTypes.isSubtype(existing, type) || mTypes.isSubtype(type, existing)) { 367 | mMessager.printMessage(Diagnostic.Kind.ERROR, 368 | "Field 类型" + fieldType.toString() + "在定义中冲突,类:" + type.toString() + "与类:" 369 | + existing.toString()); 370 | return; 371 | } 372 | } 373 | mirrors.add(mTypes.erasure(type)); 374 | } 375 | } 376 | } 377 | --------------------------------------------------------------------------------