Análisis y uso compartido del principio de ARouter

prefacio

En el caluroso verano, no sé cómo se organizará el fin de semana de todos. Este artículo compartirá contigo el principio de ARouter. Al comprender su principio, podemos saber cómo admite la creación de componentes y las llamadas o saltos de página entre módulos que no dependen entre sí.

texto

1. Introducción al enrutador

ARouter es un marco de enrutamiento basado en componentes de código abierto de Alibaba. Puede ayudar a los saltos de página y las llamadas de servicio entre componentes independientes.

2.Uso del enrutador

Añadir dependencias:

android {
  //...
  defaultConfig {
       kapt {
            arguments {
                arg("AROUTER_MODULE_NAME", project.getName())
            }
        }
  }
  //...
}

dependencies {
    api 'com.alibaba:arouter-api:1.5.0'
    kapt 'com.alibaba:arouter-compiler:1.2.2'
}

Defina la ruta para saltar a Actividad:

@Route(path = "/test/router_activity")
class RouterActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_router)
    }
}

Inicialice el marco del enrutador:

class RouterDemoApp : Application() {
    override fun onCreate() {
        super.onCreate()
      //初始化、注入
        ARouter.init(this)
    }
}

Salto de llamada:

ARouter.getInstance().build("/test/router_activity").navigation()

3. Código generado (tabla de enrutamiento generada)

Cuando agregamos la anotación de ruta a la actividad o el servicio, la compilamos, el marco ARouter generará un archivo java para nosotros de acuerdo con la plantilla, y se puede acceder a él en tiempo de ejecución. La tecnología utilizada en él es tecnología apta. Echemos un vistazo al código generado por el ejemplo anterior:

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("test", ARouter$$Group$$test.class);
  }
}

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/router_activity", RouteMeta.build(RouteType.ACTIVITY, RouterActivity.class, "/test/router_activity", "test", null, -1, -2147483648));
  }
}

De acuerdo con el código generado anteriormente, se puede ver que el código generado es una tabla de enrutamiento. Primero, el grupo corresponde a la clase del grupo, y cada grupo es la tabla de enrutamiento debajo del grupo.

4. Inicializar init() (el grupo que carga la tabla de enrutamiento)

A continuación, echemos un vistazo a lo que se hace en el marco de enrutamiento durante la inicialización:

//#ARouter
public static void init(Application application) {
    if (!hasInit) {
      //...省略部分代码
        hasInit = _ARouter.init(application);
 			//...省略部分代码
    }
}

//#_ARouter
protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        mHandler = new Handler(Looper.getMainLooper());
        return true;
    }

El código central para la inicialización parece estar en el LogisticsCenter:

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    mContext = context;
    executor = tpe;

    try {
        //...省略代码
        if (registerByPlugin) {
            //...省略代码
        } else {
            Set<String> routerMap;

            // 如果是debug包或者更新版本
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
               //获取在com.alibaba.android.arouter.routes下的所以class类名
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
              //更新到sp中
                if (!routerMap.isEmpty()) {
                    context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                }
								//更新版本
                PackageUtils.updateVersion(context); 
            } else {
                //直接从缓存拿出之前存放的class
                routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
            }
						
          //遍历routerMap,将group的类加载到缓存中
            for (String className : routerMap) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                  //生成的Root、比如我们上面示例的ARouter$$Root$$app,调用loadInto相当于加载了routes.put("test", ARouter$$Group$$test.class)
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    //加载拦截器,例如生成的ARouter$$Interceptors$$app
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    // 加载Provider,例如生成的ARouter$$Providers$$app
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }
        }
        //...省略代码
    } catch (Exception e) {
        throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
    }
}

La lógica central anterior es que si se trata de un paquete de depuración o una versión actualizada, vaya a obtener todos los nombres de clase en com.alibaba.android.arouter.routes, luego actualícelo a sp y actualice el número de versión. Luego cargue IRouteRoot a través de la reflexión para cargar el grupo y el objeto de clase correspondiente, y también cargue el interceptor, Proveedor.

Aquí nos enfocamos en el método getFileNameByPackageName para obtener la ruta del archivo de clase:

public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws NameNotFoundException, IOException, InterruptedException {
    final Set<String> classNames = new HashSet();
  //获取到dex文件路径
    List<String> paths = getSourcePaths(context);
    final CountDownLatch parserCtl = new CountDownLatch(paths.size());
    Iterator var5 = paths.iterator();

    while(var5.hasNext()) {
        final String path = (String)var5.next();
        DefaultPoolExecutor.getInstance().execute(new Runnable() {
            public void run() {
                DexFile dexfile = null;
                try {
                  //加载出dexfile文件
                    if (path.endsWith(".zip")) {
                        dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                    } else {
                        dexfile = new DexFile(path);
                    }

                    Enumeration dexEntries = dexfile.entries();
									//	遍历dexFile里边的元素,加载出.class文件
                    while(dexEntries.hasMoreElements()) {
                        String className = (String)dexEntries.nextElement();
                      //开头"com.alibaba.android.arouter.routes"
                        if (className.startsWith(packageName)) {
                            classNames.add(className);
                        }
                    }
                } catch (Throwable var12) {
                    Log.e("ARouter", "Scan map file in dex files made error.", var12);
                } finally {
                    //...省略代码
                    parserCtl.countDown();
                }
            }
        });
    }

    parserCtl.await();
  	//。。。省略代码
    return classNames;
}

La lógica principal de este método es cargar la ruta del archivo dex, luego construir el DexFile a través de la ruta y recorrer los elementos en él después de la construcción.Si es un archivo de clase que comienza con com.alibaba.android.arouter.routes , guárdelo en la lista Esperando para volver.

getSourcePaths:

public static List<String> getSourcePaths(Context context) throws NameNotFoundException, IOException {
    ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
    File sourceApk = new File(applicationInfo.sourceDir);
    List<String> sourcePaths = new ArrayList();
    sourcePaths.add(applicationInfo.sourceDir);
    String extractedFilePrefix = sourceApk.getName() + ".classes";
  //是否开启了multidex,如果开启的话,则需获取每个dex路径
    if (!isVMMultidexCapable()) {
        int totalDexNumber = getMultiDexPreferences(context).getInt("dex.number", 1);
        File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME);
				//遍历每一个dex文件
        for(int secondaryNumber = 2; secondaryNumber <= totalDexNumber; ++secondaryNumber) {
          //app.classes2.zip、app.classes3.zip ...
            String fileName = extractedFilePrefix + secondaryNumber + ".zip";
            File extractedFile = new File(dexDir, fileName);
            if (!extractedFile.isFile()) {
                throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'");
            }
            sourcePaths.add(extractedFile.getAbsolutePath());
        }
    }

    if (ARouter.debuggable()) {
        sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo));
    }

    return sourcePaths;
}

getSourcePaths的功能就是获取app的所有dex文件的路径,为后面转成class文件从而获取class文件路径提供数据。

小结:

  • ARouter.init(this)调用交给了内部的_ARouter.init(application),然后真正做事的是LogisticsCenter.init(mContext, executor)
  • 如果是debug包或者升级版本,则去加载出com.alibaba.android.arouter.routes包下的dex文件的路径,并且更新到缓存里边
  • 通过这些dex去获取对应的所有class文件的路径
  • 最后根据类名的前缀加载到Warehouse中对应的map里,其中就有group、interceptor和provider

5.调用及处理

ARouter.getInstance().build("/test/router_activity").navigation()

build会构建一个Postcard对象出来:

//#Router
public Postcard build(String path) {
    return _ARouter.getInstance().build(path);
}

//#_ARouter
    protected Postcard build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
          //extractGroup方法就是从path中提取出group,比如"/test/router_activity",test便是提取出来的group
            return build(path, extractGroup(path));
        }
    }

build(path, group)方法最终会构建一个Postcard对象出来。

构建好PostCard之后,调用它的navigation方法便可以实现我们的跳转或者获取对应的实体。navigation方法最后会调用到_ARouter的navigation方法:

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    //...省略代码
    try {
      //1.根据postCard的group加载路由表,并且补全postCard的信息
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        //...异常处理
        return null;
    }
    if (null != callback) {
        callback.onFound(postcard);
    }

  //如果不是绿色通道的话,需要走拦截器的逻辑,否则会跳过拦截器
    if (!postcard.isGreenChannel()) { 
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            @Override
            public void onContinue(Postcard postcard) {
              //2.真正实现动作处理
                _navigation(context, postcard, requestCode, callback);
            }
            @Override
            public void onInterrupt(Throwable exception) {
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }
								//...省略代码
            }
        });
    } else {
      //2.真正实现动作处理
        return _navigation(context, postcard, requestCode, callback);
    }
    return null;
}

navigation方法的核心逻辑为:加载路由表,并且补全postCard的信息,然后真正处理跳转或者请求逻辑。

LogisticsCenter.completion(postcard)的核心源码如下:

public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!");
    }

    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
      //groupsIndex在init的时候已经加载好了,这里就可以通过group获取到对应group的class对象
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
        if (null == groupMeta) {
            throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
        } else {
            // Load route and cache it into memory, then delete from metas.
            try {
                //...省略代码
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
              //将group里边的路由表加载进内存,我们最开始的例子想当于执行:atlas.put("/test/router_activity", RouteMeta.build(RouteType.ACTIVITY, RouterActivity.class, "/test/router_activity", "test", null, -1, -2147483648));
                iGroupInstance.loadInto(Warehouse.routes);
              //因为加载路由表了,所以可以将group从内存中移除,节省内存
                Warehouse.groupsIndex.remove(postcard.getGroup());
								//...省略代码
            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
            }
						//已经将group里的路由表加载出来了,再执行一遍函数。
            completion(postcard);   // Reload
        }
    } else {
      	//	第二次时,给postCard填补信息
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());
			//...省略代码,主要是解析uri然后参数的赋值

      //根据路由获取的不同的类型,继续补充一些信息给postCard
        switch (routeMeta.getType()) {
            case PROVIDER:  
            		//...省略代码,主要是补充一些其他参数
                postcard.greenChannel();    // Provider should skip all of interceptors
                break;
            case FRAGMENT:
                postcard.greenChannel();    // Fragment needn't interceptors
            default:
                break;
        }
    }
}

补充完postCard信息之后,接下来我们看看_navigation方法:

private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = null == context ? mContext : context;

    switch (postcard.getType()) {
        case ACTIVITY:
            //构造Intent,然后切换到主线程,并且跳转到指定的Activity
            break;
        case PROVIDER:
            return postcard.getProvider();
        case BOARDCAST:
        case CONTENT_PROVIDER:
        case FRAGMENT:
        	//反射构造出实例并返回
        case METHOD:
        default:
            return null;
    }

    return null;
}

可以看到,最终会根据不同的type,去做出不同的响应,例如ACTIVITY的话,会进行activity的跳转,其他的会通过反射构造出实例返回等操作。

小结:

  • 调用的最开始,会构建一个PostCard对象,初始化path和group
  • navigation方法最终会调用到_ARouter的navigation方法,然后通过LogisticsCenter.completion(postcard)去加载group里边的路由表,并且补全postcard信息。
  • 如果是有绿色通道的话,则不执行拦截器,直接跳过,否则需要执行拦截器。
  • 最后便是通过不同类型执行对应的操作。

结语

本文的分享到这里就结束了,相信看完后,能够对ARouter的原理有了一定的理解,以便我们后面如果有使用到它的时候,能够更好的地使用,或者为项目定制出路由框架提供了很好的思路参考。同时,这么优秀的框架也值得我们去学习它的一些设计思路。

Supongo que te gusta

Origin juejin.im/post/7118385586850758686
Recomendado
Clasificación