Android 组件化实现之手撸步骤

在App功能逐渐冗杂的今天,衍生出很多优化方案,组件化作为其中一种方式也被广泛应用。抛开那些第三方,简单手撸一次实现组件化的通信,也更利于学习第三方的开源框架,下面来一步一步实现。——2019.3.4

一、准备工作
  1. 首先我在创建完project后,除了默认创建的app model,另外创建了两个model:LoginComponent和MineComponent。(component就是组件的意思,相信来到这里的朋友都是了解过组件化原理和一些相关术语,这里就不再赘述)

  2. 在创建完两个单独的component后,创建一个Base Library,因为相互通信的需要。

  3. 创建后的目录就是这样的:
    Android组件分布.png
    model.PNG

二、配置依赖环境和分离运行需求

这里的配置环境就是在gradle里做一些相关操作,主要有如下操作:

  1. 统一每个model之间的sdk相关配置;
  1. 将model的两种运行方式进行分离,也就是分开处理,如果有资源文件也同样需要分开处理
  2. 分离的内容通常包括如下几种:
  • apply运行方式的分离
  • applicationId的分离
    以上两种都是在component gradle里操作
  • app model引用library model的分离
    以上一种是在app model里操作
  • AndroidManifest(清单文件)的分离
  1. 将所有model都对baseLibrary依赖

以上分离的目的是区分application和library两种运行方式所对应的需求,
而依赖的目的就是互相通信。

下 面 就 来 逐 一 介 绍 \color{red}{ 下面就来逐一介绍 }

1. 在app的gradle.properties里,配置如下(如果有其他需求一样可以增添):
#全局配置gradle环境
compile_sdk_version = 28
min_sdk_version = 23
target_sdk_version = 28
support_sdk_version =  28.0.0
constraint_sdk_version = 1.1.3

#配置单个model是否可独立运行
loginRunAlone = false
mineRunAlone = false
2. 在 每 个 c o m p o n e n t 的 b u i l d . g r a d l e 里 \color{red}{每个component的build.gradle里} componentbuild.gradle引用如上配置。

以下是引用配置和分离的截图:
除library model和主app model(也就是创建项目时自动创建的那个model)不需要进行application和library的判断,因为第一个始终以library运行;第二个始终以application运行。其他的component皆需考虑是否以单独的application运行的情况。

对上面文字清晰地解释一下吧。下面三种颜色,代表的在gradle里三种需要配置的事务:
    红色框:apply的运行方式的分离;
    绿色框:sdk版本的统一配置;
    黄色框:applicationId的分离,component以application运行需要ID;

其中红色框和黄色框部分只需要在component里配置,library和app里都不需要;
绿色框的内容则试用于所有gradle,能配置sdk的地方都可以。

gradle配置.png

3. app model引用library model的分离

在不以单独application运行的情况下,把component作为library依赖且运行。

app_model分离依赖.png

4. AndroidManifest的分离

首先很明确,library和application的清单文件内容是不需要的,除了保存必有的包名(package)和四大组件的注册信息。

首先,切换project目录,因为在Android目录下看不到的。
切换目录为project展示.png

然后,给所有component创建另外一个目录的AndroidManifest文件,我这里在main/下创建的文件夹和文件,跟Java文件夹同级,也可以创建其他目录,后续加载路径。
(下图中带main下的是自动创建的,作用于application运行;manifest下的是刚才我创建的,作用于library运行)
创建清单文件.png

接下来,来到作用于library运行的清单文件,干掉用不着的只剩包名和四大组件注册信息(我这里只有activity示例),如下图:
整理作用于library的清单文件.png

来,最后一步--------------分开加载清单文件
来到所有component的gradle里如下配置:
分离清单文件.png

当然注意的是,判断运行状态是根据component的,因为gradle对groovy好像不太友好,没有提示,记得改一下。顺便提一下,maniFest不要写成了mainFest,相信大家不会犯我这种低级错误。当然如果你文件名一致无所谓。
拼写错误.png

5. 各个model对baseLibrary的依赖

app_model引用baseLibrary.png

login_model引用baseLibrary.png
mine_model引用baseLibrary.png

只至此呢,所有的配置都搞完了。下面需要搭建交互逻辑!

三、搭建model间交互逻辑

        1. 来到baseLibrary下,新建接口IBaseAppComponent,写入方法initialize(Application application),不知道不要紧,后面就知道干啥了。

 public interface IBaseAppComponent {
    void initialize(Application application);
}

        2. 让app和另外两个component的自定义application都实现IBaseAppComponent 接口。

// MainAppcation 
public class MainAppcation extends Application implements IBaseAppComponent {

    private static MainAppcation app;
    public static MainAppcation getInstance(){ return app;}

    @Override
    public void onCreate() {
        super.onCreate();
        initialize(this);
    }

    @Override
    public void initialize(Application application) {
        app = (MainAppcation) application;// 全局统一的上下文
    }

在component的onCreate方法里手动调用,则是考虑到app model单独运行时需要调用。

// LoginApplication 
public class LoginApplication extends Application implements IBaseAppComponent {

    private static Application app;
    public static Application getInstance(){return app;}
    @Override
    public void onCreate() {
        super.onCreate();
        initialize(this);
    }

    @Override
    public void initialize(Application application) {
        app = application;
    }
}
  1. 回到MainAppcation的initialize方法里,因为有了一层实现关系,我们可以在这个方法里通过library的接口实现调用component内的方法,并传递MainApplication的context。
    @Override
    public void initialize(Application application) {
        app = (MainAppcation) application;

        try {
            for (String app : AppComponontConfig.COMPONENTS) {
                // COMPONENTS是一个AppComponontConfig类里的静态字符串数组----->
                    // ,包含了所有component的包名
                // 用包名反射,判断是否实现了IBaseAPPComponent
                Object aClass = Class.forName(app).newInstance();
                if (aClass instanceof IBaseAppComponent) {
                     // 确保component实现了此接口 
                    ((IBaseAppComponent) aClass).initialize(this);// ---->
                     // 这里主app的在整体运行时需要提供全局统一的上下文
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
  1. 继续在library model里,创建ServiceFactory类, 用来管理model间交互的引用对象实例。
public class ServiceFactory {

    private static final ServiceFactory instance = new ServiceFactory();
    public static ServiceFactory getInstance() {
        return instance;
    }

    // 为组件化接口提供get及set方法
    private I_LoginService mLoginService;
    private I_MineService mMineService;

    public I_LoginService getLoginService() {
        // 避免在移除组件后报错。这里不返回null,返回一个空实现类
        if (mLoginService == null) {
            mLoginService = new EmptyLoginServiceImpl();
        }
        return mLoginService;
    }

    public void setLoginService(I_LoginService loginService) {
        mLoginService = loginService;
    }

    public I_MineService getMineService() {
        if (mMineService == null) {
            mMineService = new EmptyMineServiceImpl();
        }
        return mMineService;
    }

    public void setMineService(I_MineService mineService) {
        mMineService = mineService;
    }
}
  1. 在component里创建业务类LoginServiceImpl(这里只拿其中一个component举例,另外的同理)实现I_LoginService接口。
public class LoginServiceImpl implements I_LoginService {
    @Override
    public void launch(Context contex) {
        // 每个组件都提供一个launch方法用来启动该组件
    }
}
  1. 在component的application里(这里同样拿其中一个举例)绑定传递对象
	@Override
    public void initialize(Application application) {
        app = application;
        ServiceFactory.getInstance().setLoginService(new LoginServiceImpl());
    }

那么我们的准备也就到此结束了,下面我将准备实现两个功能,以测试是否成功和效果。

四、实现交互逻辑

终于来到这里了对吧!
先说下我要实现的功能:

  1. main跳转至mine界面
  2. mine再跳转至login界面
  3. login再调用mine组件中的方法展示一个fragment
    那好,开整!
1. 来到login组件的实现类,前面代码里提到过我们为每个组件提供了一个launch方法,这个方法用来其他组件启动本组件。里面自然做了启动的操作。
public class LoginServiceImpl implements I_LoginService {
    @Override
    public void launch(Context context) {
        Intent intent = new Intent(context, LoginActivity.class);
        context.startActivity(intent);
    }
}
2. 来到Mine组件的业务实现类,同样提供给外部组件的启动方法和login组件显示fragment的方法
public class MineServiceImpl implements I_MineService {
    /**
     * 提供外部组件启动方法
     * @param context context
     */
    @Override
    public void launch(Context context) {
        Intent intent = new Intent(context, MineActivity.class);
        context.startActivity(intent);
    }

    /**
     * 显示fragment的方法
     */
    @Override
    public Fragment showFragment(FragmentManager manager, int viewId, Bundle bundle) {
        TestFragment testFragment = new TestFragment();
        testFragment.setArguments(bundle);
        return testFragment;
    }
3. 在Mine model中的Fragment页面,这里我们把login组件中调用时传的内容展示出来 。
public class TestFragment extends Fragment {
    private View mView;
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mView = inflater.inflate(R.layout.fragment_test, null);
        return mView;
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle arguments = getArguments();
        ((TextView) mView.findViewById(R.id.tv_fr)).setText(arguments.getString("string"));
    }
}
4. 来到app model的Activity,我们先写好第一个的测试代码
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tv = findViewById(R.id.sample_text);
        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ServiceFactory.getInstance().getMineService().launch(MainActivity.this);
            }
        });
    }
5. 再来到Mine model的Activity,搞定第二个测试
public class MineActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mine);
        findViewById(R.id.tv_go_to_login).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                  // 跳转到Login界面
                  ServiceFactory.getInstance().getLoginService().launch(MineActivity.this);
            }
        });
    }
}
6. Login页第三个测试,都是些没什么技术含量的代码应该没同学看不懂。
public class LoginServiceImpl implements I_LoginService {
    @Override
    public void launch(Context context) {
        Intent intent = new Intent(context, LoginActivity.class);
        context.startActivity(intent);
    }
}
OK,跑一个

组件化Demo录屏.gif
成功地进行了三个页面的跳转,并在一个组件中调用另一个组件的方法,展示了一个组件携带到另一个组件的数据。

总结四个自己挖的坑
  1. MainApplication忘记在清单文件配置name,其他两个组件因为是反射调用的,所以没有进行name配置
  2. 判空方法写在了setLoginService里
  3. 在gradle里加载AndroidManiFest,写成了AndroidMainFest
  4. 修改了两个组件的application类名,忘记在AppComponontConfig.COMPONENTS修改,导致instanceOf始终走了异常
    ######另外提醒一下,在组件和app model里资源名字一样时,组件里的activity加载的是app model里的资源,需要加上标识。
五、小结一下

组件化在最初的实现是比较繁琐的一件事,特别是在搭建项目通信环境,即使在使用第三方的情况下。所以,在开发过程中依据业务特性进行项目架构选择,分析出各种优缺点才是最好的。

没毛病,搞完收工!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柯基爱蹦跶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值