架构师学习--组件化开发之管理类

前面介绍到组件化的ARouter路由设计和参数传递设计。只是功能进行了实现,没有考虑到进一步的封装,比如界面传参,之前是用如下方式实现:

new Main_MainActivity$$Parameter().loadParams(this);

这样看起来会比较难以理解,因为使用者并不知道生成了Main_MainActivity$$Parameter()这个类。同样的界面跳转之前使用如下方式:

 ARouter$$Group$$main group = new ARouter$$Group$$main();
 Map<String, Class<? extends ARouterLoadPath>> loadMap = group.loadGroup();
 Class<? extends ARouterLoadPath> clazz = loadMap.get("main");
  ARouterLoadPath aRouterGroupPath = clazz.newInstance();
  Map<String, RouterBean> map = aRouterGroupPath.loadPath();
  RouterBean routerBean = map.get("/main/Main_MainActivity");
  if(routerBean != null){
         Intent mIntent = new Intent(this,routerBean.getClazz());
         mIntent.putExtra("name","qbbb");
         mIntent.putExtra("agex",121);
         startActivity(mIntent);
   }

使用者也是一脸懵逼,用起来很麻烦。所以就需要对它们进行进一步的封装。

一、ParamsManager

该类主要处理参数传递相关。代码如下:

public class ParamsManager {
    public static volatile ParamsManager instance;

    //Lru缓存 key:类名 value:参数加载接口
    private LruCache<String, ARouterLoadParameters> paramsCache;

    //拼接的字符串 如:Main_MainActivity$$$$Parameter
    public static final String PARAMS_PREFIX = "$$Parameter";

    public static ParamsManager getInstance(){

        if(instance == null){
            synchronized (ParamsManager.class){
                if(instance == null){
                    instance = new ParamsManager();
                }
            }
        }
        return instance;
    }

    private ParamsManager(){
        //最大缓存100
        paramsCache = new LruCache<>(100);

    }

    public void load(Object obj){

        if(obj == null) return;
        
		//拿到当前activity的全名。
        String className = obj.getClass().getName();

        try {
            //从缓存中取,避免多次反射
            ARouterLoadParameters parametersLoad = paramsCache.get(className);
            
            //缓存中拿不到
            if(parametersLoad == null){

                Log.e("参数类名",className + PARAMS_PREFIX);
                //拼接成类Main_MainActivity$$Parameter
                Class<?> clazz = Class.forName(className + PARAMS_PREFIX);
                //接口=接口的实现类
                parametersLoad = (ARouterLoadParameters) clazz.newInstance();

                //放到缓存中
                paramsCache.put(className,parametersLoad);
            }

            parametersLoad.loadParams(obj);

        } catch (Exception e) {
            e.printStackTrace();

        }

    }
}

外部提供单例调用,通过load()方法完成参数注解的取值。需要注意的是,需要将每个activity的加载放到缓存中,避免不必要的反射。这样我们就可以在activity中如下使用:

 ParamsManager.getInstance().load(this);

二、BundleManager

为什么要引入BundleManager,这是因为在跳转的时候我们可能需要传递参数,也可能会从下一界面回调信息(比如:startActivityForResult)。代码如下:

/**
 * 跳转参数管理类
 */
public class BundleManager {

    private Bundle bundle = new Bundle();

    //是否结果回调
    private boolean isResult = false;


    public Bundle getBundle() {
        return bundle;
    }
    
    public boolean isResult() {
        return isResult;
    }

    public BundleManager withString(String key,String value){
        bundle.putString(key,value);
        return this;
    }

    public BundleManager withResultString(String key,String value){
        bundle.putString(key,value);
        isResult = true;
        return this;
    }

    public BundleManager withInt(String key,int value){
        bundle.putInt(key,value);
        return this;
    }

    /**
     * 没有回调的跳转
     * @param context
     * @return
     */
    public Object navigation(Context context){
        return navigation(context,-1);
    }

    /**
     * 交给ARouterManager处理跳转,将this传过去就可以拿到bundle和isResult
     * @param context
     * @param code 可以是requestCode ,也可以是resultCode 根据isResult来定
     * @return
     */
    public Object navigation(Context context,int code){
        return ARouterManager.getInstance().navigation(context,code,this);
    }
}

分析:
1、类中有一个字段isResult ,它的作用是控制当前是否需要界面回调。比如上面的withResultString()方法,将它置为true。

2、navigation()方法,参数为当前activity的上下文,还有一个重载方法,参数多了一个code,它既可以作为requestCode,也可以作为resultCode。当isResult 为false的时候,如果code大于0,它就作为requestCode;如果isResult 为true,它就作为resultCode。当code为-1的时候,就是普通的跳转,不需要回调。

3、最终会调用ARouterManager.getInstance().navigation(context,code,this)方法,交给它进行跳转。

三、ARouterManager

负责主要的跳转业务

public class ARouterManager {
    
    public static volatile ARouterManager instance;

    //组名
    private String group;

    //path路径
    private String path;

    //缓存 key:组名 value:路由组接口
    private LruCache<String, ARouterLoadGroup> groupCache;

    //key:路径地址 value:路由地址接口
    private LruCache<String, ARouterLoadPath> pathCache;

    private static final String GROUP_PREFIX = "ARouter$$Group$$";

    public static ARouterManager getInstance() {
        if (instance == null) {
            synchronized (ARouterManager.class) {
                if (instance == null) {
                    instance = new ARouterManager();
                }
            }
        }
        return instance;
    }

    private ARouterManager() {
        groupCache = new LruCache<>(100);
        pathCache = new LruCache<>(100);
    }

    /**
     * 传递路由地址
     */
    public BundleManager build(String path) {
        if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
            throw new IllegalArgumentException("path路径未按照规范,如:/app/MainActivity");
        }

        group = subGroupName(path);
        this.path = path;

        return new BundleManager();
    }

    private String subGroupName(String path) {

        //如/MainActivity 不合规范
        if (path.lastIndexOf("/") == 0) {
            throw new IllegalArgumentException("path路径未按照规范,如:/app/MainActivity");
        }

        //第一个/和下一个/之间截取group
        String finalGroup = path.substring(1, path.indexOf("/", 1));

        if (TextUtils.isEmpty(finalGroup)) {
            throw new IllegalArgumentException("path路径未按照规范,如:/app/MainActivity");
        }

        return finalGroup;

    }

    public Object navigation(Context context, int code, BundleManager bundleManager) {

        //group文件生成的路径
        try {
            ARouterLoadGroup groupLoad = groupCache.get(group);
            if (groupLoad == null) {
                String className = context.getPackageName() + ".apt." + GROUP_PREFIX+group;
                Log.e("group文件路径", className);
                Class<?> clazz = Class.forName(className);

                //接口=接口的实现类
                groupLoad = (ARouterLoadGroup) clazz.newInstance();

                groupCache.put(group, groupLoad);
            }

            if(groupLoad.loadGroup().isEmpty()) {
                throw  new RuntimeException("路由组加载失败");
            }

            ARouterLoadPath pathLoad = pathCache.get(path);

            if(pathLoad == null){
                Class<? extends ARouterLoadPath> clazz = groupLoad.loadGroup().get(group);
                if(clazz != null) pathLoad = clazz.newInstance();
                pathCache.put(path,pathLoad);
            }

            if(pathLoad.loadPath().isEmpty()) {
                throw  new RuntimeException("路由地址加载失败");
            }

            //拿到routerBean对象
            RouterBean routerBean = pathLoad.loadPath().get(path);
            if(routerBean != null){
                switch (routerBean.getType()){
                    case ACTIVITY:
                        Intent intent = new Intent();
                        intent.putExtras(bundleManager.getBundle());
                        intent.setClass(context,routerBean.getClazz());

                        //回调的数据 setResult() 然后finish()
                        if(bundleManager.isResult()){
                            ((Activity)context).setResult(code,intent);
                            ((Activity)context).finish();
                        }

                        //使用startActivityForResult
                        if(code>0){
                            ((Activity)context).startActivityForResult(intent,code);
                        }else{
                            context.startActivity(intent);
                        }
                        break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

分析:
1、build()方法,参数为当前activity的路径path,从path中能够截取当前的组名group,返回结果是BundleManager对象。

2、拿到BundleManager对象可以进行参数的传递,比如可以链式调用withString()方法。

3、参数设置完成最后调用navigation()方法:根据group拿到路由组接口,通过路由组接口的loadGroup()方法拿到路由地址接 口,进而通过路由地址接口的loadPath()方法拿到RouterBean对象(封装了类信息)。

4、判断当前类是否是Activity类型。满足条件通过intent跳转,根据isResult和code判断是回调结果还是进行界面传值。

五、使用

1、主界面传递参数,调用如下:
在这里插入图片描述
2、main/Main_MainActivity回调如下:
在这里插入图片描述
3、app/MainActivity的接收结果如下:
在这里插入图片描述
4、打印信息如下:
在这里插入图片描述
如果发现回调结果为空,请在接受回调结果的界面添加启动模式 android:launchMode=“singleTask”

六、模块间如何实现数据的完全解耦

比如,我想从main模块获取personal模块的一张图片,常规的做法是在common模块复制一张一样的图片放在res/drawable目录下,那如果我想获取personal模块的一个接口返回数据呢?这样就需要其他的方法了。

1、请看下面一个例子
(1)定义一个接口,如下:
在这里插入图片描述
(2)定义一个接口实现类,并返回一张图片
在这里插入图片描述
(3)在activity中使用如下方式就可以获取这张图片
在这里插入图片描述
没错,重点就是:接口=接口的实现类

2、基于上面的例子,实现的思路大致是这样的:

  1. 定义可扩展接口Call,是一个空接口,什么也不做,供子类去继承,用于在javapoet生成文件过程中判断接口的类型
  2. 在common模块,定义接口DrawableCall,实现Call接口,提供可重写方法getDrawable()
  3. 在main模块,定义接口DrawableCall的实现类MainDrawableImpl,重写方法,并返回该模块一张图片。该类使用ARouter注解,将该实现类添加到path路由表中,这样其他模块才能根据path路径拿到该实现类
  4. 假如我们是在app主模块需要该图片,就需要定义DrawableCall变量,并且使用参数注解,注解的key与第3步注解参数一致。

3、代码实现
(1)DrawableCall接口

public interface DrawableCall extends Call {
    int getDrawable();
}

(2)main模块实现该接口,使用ARouter注解

@ARouter(path = "/main/getDrawable")
public class MainDrawableImpl implements DrawableCall {
    @Override
    public int getDrawable() {
        return R.drawable.ic_add_shopping_cart_black_24dp;
    }
}

这样编译出来的代码如下:
在这里插入图片描述
(3)ARouterCompiler注解处理器中,构造RouterBean的时候,判断是否为接口类型CALL
在这里插入图片描述
(4)ParameterCompiler注解处理器中,在构造loadPath方法过程中添加CALL类型,在ParameterFactory类中添加代码如下:
在这里插入图片描述
用到了"接口=接口的实现类"。这样编译出来的代码如下:

在这里插入图片描述(5)ARouterManager管理类中,在navigation()方法,判断为CALL类型
在这里插入图片描述
这里的clazz为MainDrawableImpl类,return返回的为Call的实现类MainDrawableImpl。
(6)在app的MainActivity中,添加参数注解
在这里插入图片描述
使用如下:
在这里插入图片描述
好了,关于组件化的所有内容,至此已全部结束!!!

完整代码如下:

组件化完整代码

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值