ARouter is a classic open source project, and we’ll take a look at one of its main features: cross-Module data transfer.
1. Data transfer across Modules (ask questions)
ARouter’s scene:
Public interface IHelloService extends IProvider {void sayHello(); }Copy the code
// B Moudle defines the interface implementation class: @route (path =)"/service/hello")
public class HelloServiceImpl implements IHelloService{
@Override
public void sayHello() {
xxxx
}
}
}
Copy the code
Arouter.getinstance ().build();"/service/hello").navgation();
ARouter.getInstance().navigation(IHelloService.class);
Copy the code
A module has no dependencies on B Module. A Module needs to get an instance of B Module’s HelloServiceImpl.
2. Analyze data transfer across Modules (Analyze problems)
As in the previous article, the page jump across modules, get the class of the page to jump, the source stage, A, B module does not exist coupling, rely on the compilation period to generate the middle class, this class as A, B communication bridge, A, B module communication.
You need to deal with the following sub-problems: 1. Generate the intermediate class, find the specified annotation Route, annotation generated this class. 2. It takes on the task of creating the HelloServiceImpl instance object and storing it with key_value.
ARouter.getInstance().navigation(HelloService.class).sayHello("mike");
ARouter.getInstance().build("/xxx/hello").navigation().sayHello("mike");
Copy the code
This instance object key can be HelloService. Class. The getName (), can be “/ / hello XXX”, the value is, of course, HelloServiceImpl instance objects.
The ARouter class is exposed to the developer, and the actual class that handles the logic is _ARouter. LogisticsCenter(LogisticsCenter) handles object tasks, such as reflecting instance objects created by empty parameter constructs; The WareHouse holds data as a WareHouse. Consistent with the principle of single responsibility.
3. Implement data transfer across Modules (solve problems)
1. The annotation generates the following classes:
2. Find these two. Provider? The app and ARouter? Root? App) the newly generated class is then reflected to create the object and the loadInto method is called to save the data to the WareHouse WareHouse.
How to find:
Method 1: initialization (runtime) time traverse base.apk, scan the two classes, directly reflection creation. Method 2: Auto-register, a custom plug-in implemented using bytecode under the ASM operation transforms folder during compilation.
// Method 1: public static void init(Context context, ThreadPoolExecutor executor) { final Set<String> fileNames = new HashSet<>(); ApplicationInfo applicationInfo = context.getApplicationInfo(); final String path = applicationInfo.sourceDir; DexFile = null; DexFile = null; DexFile = null; try { dexFile = new DexFile(path); Enumeration<String> entries = dexFile.entries();while(entries.hasMoreElements()) { String element = entries.nextElement(); / / go to contain this com. Docwei. Arouter. Routes path file nameif (element.contains(Consts.PACKAGE_OF_GENERATE_FILE)) {
fileNames.add(element);
}
}
} catch (IOException e) {
e.printStackTrace();
}
for(String fileName: fileNames) {// Reflection creates this class object and saves it to the repositoryif (fileName.startsWith(Consts.PACKAGE_OF_GENERATE_FILE + "." + "ARouter$$Root")) {
((IRouterRoot) (Class.forName(fileName).getConstructor().newInstance()))
.loadInto(WareHouse.sGroups);
}
if (fileName.startsWith(Consts.PACKAGE_OF_GENERATE_FILE + "." + "ARouter$$Provider")) { ((IProviderGroup) (Class.forName(fileName).getConstructor().newInstance())) .loadInto(WareHouse.sProviders); }}Copy the code
Public class LogisticsCenter {static Boolean sAutoRegister; static Context sContext; public static void init(Context context, ThreadPoolExecutor executor) { loadRouteMap();if (sAutoRegister) {
return;
}else{// Go mode 1 XXX}} public static voidloadRouteMap() {
sAutoRegister = false; // This method will be modified by ASM to add the corresponding code //register("com.docwei.arouter.routes.ARouter$$Root$$app);
}
public static void register(String name) {
if (!TextUtils.isEmpty(name)) {
Object obj = Class.forName(name).getConstructor().newInstance();
if (obj instanceof IRouterRoot) {
((IRouterRoot) obj).loadInto(WareHouse.sGroups);
}
if (obj instanceof IProviderGroup) {
((IProviderGroup) (Class.forName(name).getConstructor().newInstance()))
.loadInto(WareHouse.sProviders);
}
sAutoRegister = true;
}
}
Copy the code
The second option is to manipulate the bytecode at compile time, find the jar where the LogisticsCenter class is located, and find the generated class ARouter? Root? App, ARouter? Provider? Full path to app, then navigate to loadRouteMap and insert the following code:
public static void loadRouteMap() {
sAutoRegister = false;
register("com.docwei.arouter.routes.ARouter$$Root$$app");
register("com.docwei.arouter.routes.ARouter$$Provider$$app");
}
Copy the code
The loadRouteMap method is finally called at initialization. Whether it is annotations to generate a new class, or using ASM to modify the bytecode body content, in the ARouter source code, if you are too complex source code, you can see the final streamlined ARouterDemo.
3. Find the instance object based on the path or interface class passed in by the user.
Public Object Navigation (Class Service) {RouteMeta RouteMeta = WareHouse.sProviders.get(service.getName());if (routeMeta == null) {
return null;
}
PostCard postCard = new PostCard(routeMeta.getPath(), routeMeta.getGroup(), routeMeta.destination, routeMeta.type);
LogisticsCenter.completePostCard(postCard);
return postCard.getProvider();
}
Copy the code
Public static void completePostCard(PostCard) {RouteMeta RouteMeta = WareHouse.sRoutes.get(postCard.getPath());if (routeMeta == null) {
Class<? extends IRouterGroup> iRouterGroup = WareHouse.sGroups.get(postCard.getGroup());
if (iRouterGroup == null) {
Log.e("myRouter"."completePostCard: " + "path map page not found");
return;
}
IRouterGroup routerGroup = iRouterGroup.getConstructor().newInstance();
routerGroup.loadInto(WareHouse.sRoutes);
completePostCard(postCard);
} else{ postCard.destination = routeMeta.destination; postCard.type = routeMeta.getType(); // Get the object instanceif (postCard.getType() == BizType.IPROVIDER) {
IProvider iProvider = WareHouse.sProviderObjects.get(postCard.destination);
if (iProvider == null) {
iProvider = (IProvider) postCard.getDestination().getConstructor()
.newInstance();
postCard.setProvider(iProvider)
iProvider.init(sContext);
}
}
}
}
}
Copy the code
Four, interceptor and AutoWird implementation principle
The interceptor
Take a look at user-defined interceptors
@Interceptor(priority = 9)
public class MyInterceptor implements IInterceptor { xxx }
Copy the code
public interface IInterceptor extends IProvider {
void process(PostCard postCard,IInterceptorCallback iInterceptorCallback);
}
Copy the code
The IProvider interface is used to create interceptors. The IProvider interface is used to create interceptors. The IProvider interface is used to create interceptors.
// All interceptors have been added to the repositoryfor (Map.Entry<Integer, Class<? extends IInterceptor>> entry : WareHouse.sInterceptors.entrySet()) {
IInterceptor interceptor = entry.getValue().getConstructor().newInstance();
interceptor.init(context);
WareHouse.sInterceptorObjects.add(interceptor);
}
Copy the code
On a page jump, go through all interceptor process methods in turn, and break the page jump if any interceptor intercepts the operation. The following code has been rewritten equivalently. (The child thread runs through all the interceptor process methods, but CountDownLatch is also used in the source code, which feels redundant because the child thread’s run methods are executed sequentially and the interceptors are iterated by priority.)
@Override
public void doInterceptor(final PostCard postcard, final IInterceptorCallback callback) {
final int size = WareHouse.sInterceptorObjects.size();
if (size > 0) {
_ARouter.sExecutor.execute(new Runnable() {
@Override
public void run() { try { mCountDownLatch = new CountDownLatch(size); executeInterceptor(0, postcard, callback, size); mCountDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); }}}); } } private void executeInterceptor(final int index, final PostCard postcard, final IInterceptorCallback callback, final int size) {if (index < size) {
WareHouse.sInterceptorObjects.get(index).process(postcard, new IInterceptorCallback() {
@Override
public void continuing(PostCard postCard) {
executeInterceptor(index + 1, postcard, callback, size);
mCountDownLatch.countDown();
if (index + 1 == size) {
callback.continuing(postCard);
}
}
@Override
public void interrupted(Throwable throwable) {
callback.interrupted(throwable);
int n = index;
while(n + 1 <= size) { mCountDownLatch.countDown(); n++; }}}); }}Copy the code
The AutoWird annotation automatically assigns values to fields
Let’s take a look at the user using autowird annotations
public class SecondActivity extends AppCompatActivity {
@AutoWird
public String name;
@AutoWird
public long price;
@AutoWird
public MyTestSerializableBean mSerializableBean;
@AutoWird
public MyTestParcelBean mMyTestParcelBean;
@AutoWird
public int score;
@AutoWird
public double goal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
ARouter.getInstance().inject(this);
TextView textView = findViewById(R.id.tv);
textView.setText(name + "-" + mMyTestParcelBean.desk
+ "-" + mSerializableBean.book + "-" +
price + "-" + score + "-"+ goal); }}Copy the code
ARouter.getInstance().inject(this); Public class ARouter$public class ARouter$$SecondActivity$$AutoWirdimplements IAutoWird { @Override public void inject(Object target) { SecondActivity substitute= (SecondActivity) target; ; Intent intent=substitute.getIntent(); substitute.mSerializableBean = (MyTestSerializableBean) intent.getSerializableExtra("mSerializableBean");
substitute.price = intent.getLongExtra("price", 0); substitute.goal = intent.getDoubleExtra("goal", 0); substitute.name = intent.getStringExtra("name");
substitute.score = intent.getIntExtra("score", 0); substitute.mMyTestParcelBean = intent.getParcelableExtra("mMyTestParcelBean"); }}Copy the code
What is this IAutoWird?
public interface IAutoWird {
void inject(Object target);
}
Copy the code
The annotation generates this class, and after we initialize the ARouter we create this ARouter, right? AutoWird? App object, wait until the user calls arouter.getInstance ().inject(this); So let’s reflect and create this ARouter, okay? SecondActivity? AutoWird object, which calls its Inject method.
Five, what have you learned?
Interceptors, Autowirds, page-jumps, and fetch instance objects all work the same way, and the same principle runs through all of ARouter’s functionality. This principle has also been applied by many componentized frameworks and is really worth a look.
But to be honest, you don’t learn anything. You probably don’t learn anything at all, because without going through the logic of the source code, you don’t know what potholes and skill points the author solved while writing.
If you think the source code is complicated, look at the simplified version. Welcome to ARouterDemo lite