Android组件化开发的实现(二)Android组件之间页面如何跳转和传递数据

前言

上一篇文章Android组件化开发的实现(一),讲了组件化架构首先要解决的几个问题:
一.如何统一管理编译环境
二.如何实现各个组件既能单独调试运行,又能集成到整体里
三.如何避免组件之间资源引用

本篇文章,我们来讨论组件化架构要解决的一个最重要的问题:四、组件之间如何跳转,传递数据
本文所对应的示例代码,已经上传到这里:
https://download.csdn.net/download/fenggering/10876548

正文

上一篇我们讲到,我们在设计组件化架构的时候,一个要考虑的事就是降低各个组件之间的耦合,即各个组件之间尽可能不依赖对方,要依赖的,是基础组件和lib库等。

上一篇我们的项目里,有一个基础lib组件,一个app组件作为项目主框架,还有一个login组件(假设是实现登录相关功能的)。那么问题来了,我们想从app组件的MainActivity,跳转到login组件的LoginActivity,该怎么写呢?

如果不用组件化架构,那么我们一般都在MainActivity里这么写:

Intent intent = new Intent(MainActivity.this,  LoginActivity.class);
startActivity(intent);

但是如果是组件化的架构,这么写的话,就得要在app组件的build.gradle文件里,加上对Login组件的依赖,并且在MainActivity里,把import LoginActivity加上。这样无疑违背了我们减少组件间依赖的设计初衷,是不可取的。

那么怎么实现组件间跳转呢?有两种比较常用的方法:

一,用隐式Intent跳转。

这种方案一般用在规模不大的项目里,比较好用。
只需要在manifest里,给LoginActivity的注册加上intent-filter,然后就可以在需要跳转的地方,用隐式Intent来跳转了。这种方式要对activity的注册进行统一的管理,否则容易出错,匹配不到要跳转的页面。项目如果规模很大的话,管理起来也挺麻烦的。

二,用基础库组件来统一管理组件间的跳转。

app组件不能依赖Login组件,而需要跳转到Login组件,那么我们可以让login组件来提供具体的跳转方法,让基础库lib组件来承担这个跳转方法的调用,然后app组件依赖lib组件即可,是一个迂回的策略。

其他的组件要跳转到另外的组件,也是同样的处理。随着业务增加可能会有X组件,Y组件等等,那么X组件负责提供跳转到X组件的方法,Y组件负责提供跳转到Y组件的方法,并且这些方法,都要经由lib组件来管理。
总结下就是,第一步:

1.各个组件应该提供跳转到自己的方法,供别的组件调用

具体如何实现呢?对于Login组件的跳转来说,首先,lib组件要给别的组件提供一个接口,用于跳转到Login组件。
这个接口写在lib组件里:

/**
 * 登录组件对外暴露的接口
 */
public interface ILoginService {
    // 跳转到登录页面,其中extra是要传递的数据
    void launch(Context context, String extra);
}

然后在Login组件里,实现此接口(当然Login组件的gradle文件得添加对lib组件的依赖):

public class LoginService implements ILoginService {
    @Override
    public void launch(Context context, String extra) {
        Intent intent = new Intent(context, LoginActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);	
        intent.putExtra("extra_data", extra);	// 传递数据
        context.startActivity(intent);
    }
}

这样,跳转方法就已经暴露出来可以供其他组件调用了,那在app组件里,我们怎么调用呢?得想办法拿到LoginService的实例,然后就能调用它的launch方法来跳转了。

因为lib组件还要管理其它组件的跳转,所以这些跳转得统一管理起来:

2.对组件间的跳转进行统一管理

我们在lib组件里写一个工厂类,用于分配这些跳转:

public class ServiceFactory {
    private ServiceFactory() {}
    // 单例对象
    private static final ServiceFactory ourInstance = new ServiceFactory();
    public static ServiceFactory getInstance() {
        return ourInstance;
    }
    
	// Login组件的跳转服务的注册和获取
    private ILoginService mLoginService;
    public ILoginService getLoginService() {
        return mLoginService;
    }
    public void setLoginService(ILoginService mLoginService) {
        this.mLoginService = mLoginService;
    }
    
	// 其他组件的跳转服务的注册和获取,与Login组件的一样
	xxxxxx
}

那么很显然,在app组件里,我们就要想办法通过lib组件的ServiceFactory 的getLoginService()方法来获取Login组件的LoginService的实例。要get,就首先要set,否则拿到的是一个null对象。那么这个set应该放在哪里实现呢?

3.利用Java反射将跳转服务进行实例化

set LoginService对象的操作肯定得放在跳转之前,即get之前。最好在app组件初始化的时候就完成set,而且这个set 操作得放在Login组件里,要不然又产生依赖了。

有了这个思路,就可以实现如下:
在lib组件里,再增加一个接口:

public interface IAppComponent {
    public void initialize(Application app);
}

这个接口用来表示各个组件的初始化。各个组件都要重写自己的Application,实现这个接口,并在manifest里做指定。比如Login组件重写的Application类如下:

public class LoginApp extends Application implements IAppComponent{
    @Override
    public void onCreate() {
        super.onCreate();
        initialize(this);
    }

    @Override
    public void initialize(Application app) {
        ServiceFactory.getInstance().setLoginService(new LoginService());
    }
}

然后在Login组件的用于组件单独运行的AndroidManifest.xml里,指定这个类为组件的application:

    <application
        android:name=".LoginApp"	// 加上这个
        android:theme="@style/AppTheme">
        <activity
            android:name=".LoginActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

但是别忘了,当组件单独运行时,Application类的onCreate()可以走到;而Login组件作为lib集成之后,Application类是不会被加载的。

怎么办呢?因为app组件的Application是肯定会被加载的,所以可以在这里,用反射来加载其他组件的Application类。这也是为什么要在lib组件里新建IAppComponent 接口类的原因。app组件的Application类也要重写并在manifest里指定。在app组件的Application初始化时,可以对其他的组件进行挨个加载,这样,上面我们想要的set 操作就可以在这里完成了。

为了管理要加载的组件,我们在lib组件里新建一个AppConfig类,如下;

public class AppConfig {
    public static String[] COMPONENTS = {
        "com.example.componentlogin.LoginApp" //这个是Login组件的Application类
        // 还有其他的组件的Application类的全名,也都放这里
        //, xxx.xxx.xxx.xxx
    };
}

上面这个AppConfig类用来记录所有的组件的Application类的全名。
下面是app组件的新Application类:

public class App extends Application implements IAppComponent {
    @Override
    public void onCreate() {
        super.onCreate();
        initialize(this);
    }

    @Override
    public void initialize(Application app) {
        // 遍历所有的组件的Application类,依次用反射的方式实例化 
        for (String component: AppConfig.COMPONENTS) {
            try {
                Class<?> clazz = Class.forName(component);
                Object object = clazz.newInstance();
                 // 实例化后,调用各个组件的 set 方法
                if (object instanceof IAppComponent) {
                    ((IAppComponent) object).initialize(app);
                }

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

经过这样的处理,上面第二步最后“该在哪里set 跳转服务”的问题就解决了。总结本篇实现的组件间跳转原理如下:
在应用启动的时候,它的Application类会进行初始化,在这里我们通过反射的方式初始化了其他各组件的Application类,而各组件的Application类在初始化时,又会通过set操作把自己的跳转服务注册到lib组件。这样的话,通过lib组件get对应的服务,即可实现跳转。

我们来试一下,在app组件里,跳转到login组件,就可以这么写了;

ServiceFactory.getInstance().getLoginService().launch(this, data); // data是传给Login组件的数据 

这样app组件就不依赖Login组件也可以进行跳转,实现了我们的期望。

还有一点,要是Login组件没有被集成到app里,那么ServiceFactory.getInstance().getLoginService()就是null,会报空指针异常。我们需要把getLoginService()补充下:

    public ILoginService getLoginService() {
        if (mLoginService == null) {
            mLoginService = new EmptyLoginService();
        }
        return mLoginService;
    }

这里EmptyLoginService是对ILoginService接口的一个空实现,目的只是为了避免这个空指针异常,就不贴代码了。

本文所对应的示例代码,已经上传到这里:
https://download.csdn.net/download/fenggering/10876548

本次只是一个组件化的简单实现,实际上要处理的问题要复杂的多。目前市场上已经有很成熟的,用于解决组件间跳转问题的框架,比如Arouter等,它的原理跟本篇的不太一样。有空了再分析。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值