经过前一篇的文章,我们已经可以将Module单独运行了,这一篇的话,我们继续探索下组件化(其实是模块化)的内容。
我们这次使用的是AppJoint的组件化方案,仍旧使用上篇文章的代码来做演示。
Appjoint的详细说明文档在 回归初心:极简 Android 组件化方案 — AppJoint !
这次我们的目标是在app模块的MainActivity中启动food模块的FoodActivity,然后在food模块的FoodActivity中启动movie模块中的MovieActivity。其实但是启动Activity的话我们可以直接采取Arouter的路由方案来进行,但是这里的话我们不使用Arouter,而是使用AppJoint提供的方法来演示。
在food模块中创建FoodActivity,在movie模块中创建MovieActivity,app模块中有默认的MainActivity。创建好必备的Activity后我们就需要准备跨模块来启动Activity了。
其实当两个模块之间想要通讯的话,我们一般需要暴漏出来一个接口,然后给另一个模块调用,然而这样的话可能就会发生一个模块依赖另一个模块的问题了,所以我们需要将这个接口单独暴漏出来,这时有两种方案:
方案就是这样,我们按照第二种来进行尝试:
在该模块中,只创建对外暴漏的接口IMovieRouter,不包含任何业务逻辑,业务只交由movie模块来实现。该接口包含一个启动MovieActivity的方法,代码如下:
package com.cooloongwu.movieapi;
import android.content.Context;
public interface IMovieRouter {
void startMovieActivity(Context context);
}
movie模块依赖movieApi模块后,需要实现IMovieRouter接口,代码如下:
package com.cooloongwu.movie;
import android.content.Context;
import com.cooloongwu.movieapi.IMovieRouter;
import io.github.prototypez.appjoint.core.ServiceProvider;
@ServiceProvider
public class MovieRouterImpl implements IMovieRouter {
@Override
public void startMovieActivity(Context context) {
Intent intent = new Intent(context, MovieActivity.class);
context.startActivity(intent);
}
}
注意: MovieRouterImpl 类需要使用 ServiceProvider 注解!
注意: MovieRouterImpl 类需要使用 ServiceProvider 注解!
注意: MovieRouterImpl 类需要使用 ServiceProvider 注解!
food模块依赖movieApi模块后,直接可以在FoodActivity中调用IMovieRouter 中的方法了。代码如下:
IMovieRouter movieRouter = AppJoint.service(IMovieRouter.class);
movieRouter.startMovieActivity(this);
因为app模块直接依赖了food和movie模块,所以在app模块中可以直接启动food模块中的FoodActivity了,但是正常开发中不建议在app模块中做过多的操作,app只作为一个壳即可。然后在FoodActivity中可以通过AppJoint提供的**AppJoint.service()**来调用其他模块的方法。
以上所有步骤当你运行app模块的时候是没有问题的,但是当你单独运行food模块的时候,此时问题就来了,因为我们在food模块中调用了movie模块中的方法。而单独food运行的时候,movie模块是完全不参与的,所以就没了IMovieRouter 的实现,当我们调用AppJoint.service()就会获取到null,这时也会产生各种问题。
怎么解决呢?
还记得我们第一篇文章的壳模块么,我们可以在food模块的壳模块(比如我们新建可模块叫runfood模块)中做些操作,因为这个模块在正式打包的时候是不会参与打包的,所以我们可以随意在其中添加代码。既然没有IMovieRouter 的实现,那我们就在可模块中去实现,当然正常逻辑是走不通的,我们就在实现里面进行打印或者进行Toast操作吧。
在原来的food模块中,可能我们直接依赖movieapi的方法是使用的implementation 。
dependencies {
...
implementation project(path: ':movieapi')
}
这样当runfood模块依赖food模块的时候,runfood模块就会访问不到movieapi模块,也就是无法访问到IMovieRouter 接口,此时我们需要把 implementation
修改为api
,这样将movieapi模块也暴漏给runfood模块。
我们给runfood模块添加IMovieRouter 的Mock类,只打印日志:
package com.cooloongwu.foodrun;
import android.content.Context;
import android.util.Log;
import com.cooloongwu.movieapi.IMovieRouter;
public class MovieRouterMockImpl implements IMovieRouter {
@Override
public void startMovieActivity(Context context) {
Log.e("MovieRouterMockImpl", "startMovieActivity");
}
}
其实在使用IMovieRouter的等其他暴漏的接口的时候可以直接将这些接口实例存放在类(例如Router类,演示用)中,代码如下:
package com.cooloongwu.food;
import com.cooloongwu.movieapi.IMovieRouter;
import io.github.prototypez.appjoint.AppJoint;
public class Router {
public static IMovieRouter movieRouter = AppJoint.service(IMovieRouter.class);
}
使用的时候直接使用 Router.movieRouter.startMovieActivity(this); 。
这样的话当然也可以对其进行统一赋值了,也就是我们可以在runfood模块中添加Application,然后在Application的onCreate()方法中对Router.movieRouter进行赋值,如下:
Router.movieRouter = new MovieRouterMockImpl();
这样,当我们在单独运行的时候就不会出现AppJoint.service()获取的是null的问题了,而是我们Mock的这么一个实现。