Why learn open source frameworks
1. Learn excellent open source framework design ideas 2. Learn excellent open source framework implementation technology 3. Actual handwriting excellent open source framework to deep understanding
The early single layered model
- Problem 1: No matter what the subcontracting does, as the project grows, the project loses the sense of hierarchy, and the successors rush to take over
- Problem two: The package name constraint is too weak, a little careless, different business packages will directly call each other, the code is high coupling
- Problem three: Multi-party development in version management, prone to code coverage conflicts and other problems
All the code is written in different packages within the APP module
1. What is componentization and why is it needed
Meaning of componentization: not interdependent, can interact with each other, arbitrary combination, highly decoupled, free disassembly, free assembly, reuse, stratification independent
At this point: APP is no longer the boss, and the sub-modules are the younger brother
After componentization, all modules are level. Some people say that app module is a shell in componentization. This is why it is called APP shell project
Students think, if there is no componentization, or single:
- 1. How much work is needed to completely cut down the order sub-module?
- 2. How to add a submodule (wallet) to the project? .
2. Automatically deploy and configure the integration environment or component environment.
Environment switch and run independently
- During the development of the project, we must optimize our Gradle files, such as extracting common content
- During the development of a project, Gradle should be used to configure everything related to the “official environment” and “test environment”
Componentization points to consider:
The principle of
Phone Module and Android Library:
Gradle configuration
1. Disconnect from public administration: manager.gradle
// Groovy language object oriented if for
/ / expanded fast
ext {
/ / now Gradle
// Formal environment and test environment
isRelease = false
// Official and test environment server URL configuration
url = [
"debug" : "https://192.188.22.99/debug"."release": "https://192.188.22.99/release"
]
// Create Map store, key and value are custom
androidID = [
compileSdkVersion : 30.buildToolsVersion : "30.0.1".applicationId : "com.derry.derry".minSdkVersion : 16.targetSdkVersion : 30.versionCode : 1.versionName : "1.0".testInstrumentationRunner: "androidx.test.runner.AndroidJUnitRunner"
]
// Create Map store, key and value are custom
appID = [
app: "com.derry.modularproject".login: "com.derry.login".register: "com.derry.register"
]
// 300 lines MAP key value
dependenciesID = [
"appcompat" : "Androidx. Appcompat: appcompat: 1.2.0"."constraintlayout": "Androidx. Constraintlayout: constraintlayout: 2.0.1."."material" : "Com. Google. Android. Material: material: 1.1.0." "."vectordrawable" : "Androidx. Vectordrawable: vectordrawable: 1.1.0."."fragment" : "Androidx. Navigation: navigation - fragments: 2.2.2." "."ui" : "Androidx. Navigation: navigation - UI: 2.2.2." "."extensions" : "Androidx. Lifecycle: lifecycle - extensions: 2.2.0." "]},Copy the code
2. Build. Gradle of the project
// The build.gradle file in the root directory is imported as a common copy
apply from : 'manager.gradle'
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath "Com. Android. Tools. Build: gradle: 4.0.1." "
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Copy the code
3.app.gradle
apply plugin: 'com.android.application'
println("Derry ---> app Student hao 1")
println "Derry ---> app Student hao 2"
// Complete mode performance
def androidID = rootProject.ext.androidID
android {
compileSdkVersion androidID.compileSdkVersion
buildToolsVersion androidID.buildToolsVersion
defaultConfig {
applicationId appID.app
minSdkVersion androidID.minSdkVersion
targetSdkVersion androidID.targetSdkVersion
versionCode androidID.versionCode
versionName androidID.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// Make my Java code work
// Tags for Java code leaks, flags, formal and test environments
// For componentization and integration
buildConfigField("boolean"."isRelease", String.valueOf(isRelease))
}
buildTypes {
debug {
buildConfigField("String"."debug"."\"${url.debug}\"")
}
release {
buildConfigField("String"."release"."\"${url.release}\"")
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs".include: ["*.jar"])
/ / 300 rows
/ * implementation "androidx appcompat: appcompat: 1.2.0" implementation "androidx. Constraintlayout: constraintlayout: 2.0.1." " Implementation "com. Google. Android. Material: material: 1.1.0" implementation "Androidx. Vectordrawable: vectordrawable: 1.1.0" implementation "androidx. Navigation: navigation - fragments: 2.2.2." " Implementation "androidx navigation: navigation - UI: 2.2.2" implementation "Androidx. Lifecycle: lifecycle - extensions: 2.2.0" * /
// You can do it in one line
dependenciesID.each {k,v -> implementation v}
testImplementation 'junit: junit: 4.12'
androidTestImplementation 'androidx. Test. Ext: junit: 1.1.2'
androidTestImplementation 'androidx. Test. Espresso: espresso - core: 3.3.0'
if (isRelease) {
// Attach the app shell
implementation project(':login')
implementation project(':register')}else {
// The login register cannot be attached, because the login register can run independently}}Copy the code
4. Library module build.gradle
apply plugin: 'com.android.library'
println "Derry ---> lib Student hao 2"
android {
compileSdkVersion androidID.compileSdkVersion
buildToolsVersion androidID.buildToolsVersion
defaultConfig {
minSdkVersion androidID.minSdkVersion
targetSdkVersion androidID.targetSdkVersion
versionCode androidID.versionCode
versionName androidID.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs".include: ["*.jar"])
/ * implementation "androidx appcompat: appcompat: 1.2.0" implementation "androidx. Constraintlayout: constraintlayout: 2.0.1." " Implementation "com. Google. Android. Material: material: 1.1.0" implementation "Androidx. Vectordrawable: vectordrawable: 1.1.0" implementation "androidx. Navigation: navigation - fragments: 2.2.2." " Implementation "androidx navigation: navigation - UI: 2.2.2" implementation "Androidx. Lifecycle: lifecycle - extensions: 2.2.0" * /
// You can do it in one line
dependenciesID.each {k,v -> implementation v}
testImplementation 'junit: junit: 4.12'
androidTestImplementation 'androidx. Test. Ext: junit: 1.1.2'
androidTestImplementation 'androidx. Test. Espresso: espresso - core: 3.3.0'
}
Copy the code
5. Build. Gradle for a service module
// apply plugin: 'com.android.application'
if (isRelease) { // For release versions, each module cannot run independently
apply plugin: 'com.android.library' // The formal environment library cannot run independently
} else {
apply plugin: 'com.android.application' // The test environment application runs independently
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.1"
defaultConfig {
// applicationId "" // There are appids that can run independently
if(! isRelease) {// To run independently, you must have an appID
applicationId appID.login // Componentized mode can run independently to have applicationId
}
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
if(! isRelease) {// In componentized mode, run Debug separately
manifest.srcFile 'src/main/debug/AndroidManifest.xml' / / to take effect
} else { // In a formal environment
// Integrated mode, the whole project package APK
manifest.srcFile 'src/main/AndroidManifest.xml' // Let the manifest file in our previous default path work again
java {
// Files in the debug directory do not need to be merged into the main project
exclude "**/debug/**"
}
}
}
}
}
dependencies {
implementation fileTree(dir: "libs".include: ["*.jar"])
implementation 'androidx. Appcompat: appcompat: 1.2.0'
implementation 'androidx. Constraintlayout: constraintlayout: 2.0.4'
testImplementation 'junit: junit: 4.12'
androidTestImplementation 'androidx. Test. Ext: junit: 1.1.2'
androidTestImplementation 'androidx. Test. Espresso: espresso - core: 3.3.0'
}
Copy the code
3. Interaction mode of sub-modules in componentization (non-Arouter version).
Means of communication between components
The disadvantages of using EventBus are that the EventBean is expensive to maintain and difficult to manage:
The second method uses the broadcasting method, the disadvantage is: it is not easy to manage, all sent out uniformly
The drawback is that there is too much configuration XML writing in androidmanifest.xml
Method 4 using class loading mode, the disadvantage is that it is easy to write wrong package name class name, fewer disadvantages (we try to write this way)
// Todo mode a class load
// Class load jump can be successful. High maintenance costs and prone to human error
try {
Class targetClass = Class.forName("com.xiangxue.personal.Personal_MainActivity");
Intent intent = new Intent(this, targetClass);
intent.putExtra("name"."derry");
startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Copy the code
The disadvantage of using a global Map is that a lot of objects need to be registered.
// personal/Personal_MainActivity getMap
// Todo mode 2 global MapClass<? > targetActivity = RecordPathManager.startTargetActivity("personal"."Personal_MainActivity");
startActivity(new Intent(this, targetActivity));
Copy the code
public class RecordPathManager {
Group: app,order,personal ** order: * OrderMainActivity1 * OrderMainActivity2 * OrderMainActivity3 */
private static Map<String, List<PathBean>> maps = new HashMap<>();
/** * Add path information to global Map **@paramGroupName groupName, for example, "personal" *@paramPathName specifies the pathName, for example, "Personal_MainActivity" *@paramClazz class objects, such as personal_mainactivity.class */
public static void addGroupInfo(String groupName, String pathName, Class
clazz) {
List<PathBean> list = maps.get(groupName);
if (null == list) {
list = new ArrayList<>();
list.add(new PathBean(pathName, clazz));
// Store in the warehouse
maps.put(groupName, list);
} else {
// Store in the warehouse
maps.put(groupName, list);
}
// maps.put(groupName, list);
}
/** * return "Class to jump" * by telling me the group name and pathname@paramGroupName groupName oder *@paramPathName pathName OrderMainActivity1 *@returnJump to the target class object */
public staticClass<? > startTargetActivity(String groupName, String pathName) { List<PathBean> list = maps.get(groupName);if (list == null) {
Log.d(Config.TAG, "StartTargetActivity this group name gets information that is not registered...");
return null;
}
// Traverse to match the "PathBean" object
for (PathBean pathBean : list) {
if (pathName.equalsIgnoreCase(pathBean.getPath())) {
returnpathBean.getClazz(); }}return null; }}Copy the code
Register before you get
The difference between analysis componentization and modular: www.jianshu.com/p/cac0beae8…
4.APT (Annotation Processing Tool)
What is APT?
It detects the annotations in the source code file and automatically generates the code according to the annotations. If you want the customized Annotation processor to run normally, you must use the APT tool to process it. It can also be understood that a program can only execute a custom annotation interpreter during compilation by declaring the APT tool.
Popular understanding: according to the rules, help us generate code, generate class files
Important elements used in APT
PackageElement represents a PackageElement. ExecutableElement represents a method, constructor, or initializer (static or instance) of a class or interface. TypeElement represents a class or interface program element. Provides access to information about types and their members. VariableElement represents a field, enum constant, method or constructor parameter, local variable, or exception parameter
APT uses apis
The property name | instructions |
---|---|
getEnclosedElements() | Returns the child elements directly contained by the element |
getEnclosingElement() | Returns the parent element that contains the element, as opposed to the previous method |
getKind() | Returns the type of element and determines which element it is |
getModifiers() | Get the modifier keyword, such as public static final |
getSimpleName() | Gets the name, without the package name |
getQualifiedName() | Gets the full name, or, if it is a class, the full package name path |
getParameters() | Gets the parameter elements of the method, each of which is a VariableElement |
getReturnType() | Gets the return value of the method element |
getConstantValue() | If a property variable is decorated with final, you can use this method to get its value |
APT technology
Make Java files APT JavaPoet make Java files APT
Which open source projects are useful in the traditional way? A: Look at the EventBus source code to see the traditional approach: advantages (the programming flow goes on) disadvantages (no OOP ideas come into play
5. Advanced usage JavaPoet
What is JavaPoet?
-
JavaPoet is an open source Java code generation framework from Square that provides Java Api generation
-
This framework is very functional and is the Java object-oriented OOP syntax we are used to
-
You can easily use it to generate code from annotations
-
In this way, code generation is automated
-
Can let us use a more concise and elegant way to replace the tedious redundant work
Project home page and source: github.com/square/java…
JavaPoet related
Class object | instructions |
---|---|
MethodSpec | Represents a constructor or method declaration |
TypeSpec | Represents a class, interface, or enumeration declaration |
FieldSpec | Represents a member variable, a field declaration |
JavaFile | A Java file containing a top-level class |
ParameterSpec | Used to create parameters |
AnnotationSpec | To create annotations |
ClassName | Used to wrap a class |
TypeName | Type, such as TypeName.VOID is used when adding return value types |
S, “hello” |
|
T, MainActivity |
Advanced usage JavaPoet
What exactly is JavaPoet? Oop thinking: Advantages (add OOP thinking) Disadvantages (unaccustomed, in reverse order)
Is JavaPoet really better than traditional methods? Not so, if complex code generation, but inefficient development trends, OOP thinking, real mastery of JavaPoet, fondly
6. Componentized project deployment, ARouter principle
The build of the compiler. Gradle
Compiler is a Java project
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs'.include: ['*.jar'])
// The service behind it can listen to whether you are compiling.....
// AS3.4.1 + Gradle 5.1.1 + auto-service:1.0-rc4
compileOnly'com. Google. Auto. Services: auto - service: 1.0 rc4'
annotationProcessor'com. Google. Auto. Services: auto - service: 1.0 rc4'
// Help us generate Java code through class calls [JavaPoet]
implementation "Com. Squareup: javapoet: 1.9.0."
// Rely on annotations
implementation project(":arouter-annotations")
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
Copy the code
Annotations ARouter
@Target(TYPE) / / class
@Retention(CLASS) // compile time XUtil== Runtime
public @interface ARouter {
String path(a);
String group(a) default "";
}
Copy the code
The generated code
package com.derry.compiler;
import com.derry.arouter_annotations.ARouter;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
@AutoService(Processor.class) // Enable the service
@SupportedAnnotationTypes({"com.derry.arouter_annotations.ARouter"}) / / comment
@SupportedSourceVersion(SourceVersion.RELEASE_7) // Version of the environment
// Receive the parameters from android project
@SupportedOptions("student")
public class ARouterProcessor extends AbstractProcessor {
// The utility class that operates on Element (classes, functions, attributes, actually elements)
private Elements elementTool;
// Utility class for type(class information), which contains utility methods for manipulating TypeMirror
private Types typeTool;
// Message Displays log information
private Messager messager;
// File generators, class resources, etc., are the files that need Filer to be generated
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementTool = processingEnvironment.getElementUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
String value = processingEnvironment.getOptions().get("student");
// This code has been poisoned
// I can use diagnostic.kind. ERROR if I want to throw an exception in the annotation handler
messager.printMessage(Diagnostic.Kind.NOTE, "> > > > > > > > > > > > > > > > > > > > > >"+value);
}
// Service: work at compile time
// pit: Subfunctions will not work if they are not used anywhere
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// This code has been poisoned
messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>> Derry run...");
if (set.isEmpty()) {
return false; / / do nothing
}
/ / cycle?
// Get "class node information" annotated by ARouter
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
for (Element element : elements) { // for 3 // 1 element == MainActivity 2 element == MainActivity2
/** package com.example. Helloworld; public final class HelloWorld { public static void main(String[] args) { System.out.println("Hello, JavaPoet!" ); }} * /
// Java everything is an object
// C everything is a pointer
MethodSpec mainMethod = methodSpec.methodBuilder ("main").modifier (Modifier.PUBLIC) Modifier.STATIC) .returns(void.class) .addParameter(String[].class, AddStatement ("$t.ut.println ($S)", System. Class, "Hello, JavaPoet!") ) .build(); Class TypeSpec testClass = TypeSpec. ClassBuilder ("DerryTest").addMethod(mainMethod).addModifiers(Modifier. Modifier.FINAL) .build(); JavaFile packagef = javafile.builder ("com.xiangxue.test", testClass).build(); // Generate file try {packagef.writeto (filer); } catch (IOException e) { e.printStackTrace(); PrintMessage (diagno.kind. NOTE, "failed to generate Test file, exception :" + LLDB etMessage()); } * /
/ / package information
String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();
// Get the simple class name, for example, MainActivity MainActivity2 MainActivity3
String className = element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, "Classes annotated by @aretuer are:" + className);
// String className = element.getSimpleName().toString();
// Target: Name of the file to be generated MainActivity$$$$$$$$$ARouter
String finalClassName = className + "$$$$$$$$$ARouter";
/ * * template: public class MainActivity3$$$$$$$$$ARouter { public static Class findTargetClass(String path) { return path.equals("/app/MainActivity3") ? MainActivity3.class : null; }} * /
ARouter aRouter = element.getAnnotation(ARouter.class);
/ / 1. Methods
MethodSpec findTargetClass = MethodSpec.methodBuilder("findTargetClass")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(Class.class)
.addParameter(String.class, "path")
Return path.equals("/app/MainActivity3")? MainActivity3.class : null;
// JavaPoet packaging transformation is required
.addStatement("return path.equals($S) ? $T.class : null",
aRouter.path(),
ClassName.get((TypeElement) element))
.build();
/ / 2. Class
TypeSpec myClass = TypeSpec.classBuilder(finalClassName)
.addMethod(findTargetClass)
.addModifiers(Modifier.PUBLIC)
.build();
/ / 3. Package
JavaFile packagef = JavaFile.builder(packageName, myClass).build();
// start generating
try {
packagef.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "Generated" + finalClassName + "File failed, exception :"+ e.getMessage()); }}return true; // It's done}}Copy the code
The build of the app. Gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
buildToolsVersion "30.0.1"
defaultConfig {
applicationId "com.derry.new_modular_javapoet"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// Pass parameters
javaCompileOptions {
annotationProcessorOptions {
arguments = [student: 'hello ni hao student javapoet']
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs".include: ["*.jar"])
implementation 'androidx. Appcompat: appcompat: 1.2.0'
implementation 'androidx. Constraintlayout: constraintlayout: 2.0.4'
implementation project(path: ':arouter-annotations')
implementation project(path: ':arouter-annotations')
testImplementation 'junit: junit: 4.12'
androidTestImplementation 'androidx. Test. Ext: junit: 1.1.2'
androidTestImplementation 'androidx. Test. Espresso: espresso - core: 3.3.0'
// Rely on annotations
implementation project(":arouter-annotations")
// Rely on the annotation handler for the annotation handler to work
annotationProcessor project(":compiler")}Copy the code