一个简单的Demo带你走进组件化开发

       一直以来,插件化,组件化,模块化开发一直都是大家容易弄混淆的东西,而其中让人最不易分清的是插件化与组件化,那么首先从简单说起,模块化,他很好理解,他就像是我们平时开发的工具类一样,比如说,一个网络请求库就可以说是一个模块,这么说,相信大家就都懂了,再来说说两个最易混淆的。从规模上来说,他们一个是apk,一个是module,从通信角度看,一个是进程内通信(组件化)一个是进程间通信(插件化),至于他们之间的优缺点以及其他特点这里就不过多的赘述了,因为本文的重点在组件化。

       首先新建一个工程,然后创建两个module加上一个主module(app),

      

       在这里我们可以看到这个项目的大体构成,一个主app,一个baselibrary,一个login的module,他们之间的关系是,app与longin都依赖baselibrary,app动态依赖login(当login为组件时被app依赖,如果不是组件时,login单独运行,此时app不依赖他)。为了实现这样一个整体效果,我们首先要进行项目之间的配置,而且这些配置是必不可少的。

      

      这个配置我选择是在gradle.properties中进行的,当然了,这个配置大多数童鞋还是喜欢单独写一个config.gradle来配置的,这个无伤大雅,根据自己的习惯来就好,这些配置中只说明其中一个配置条件,那就是这个singleModule,这个变量贯穿整个项目,当他设置为true的时候,login就是一个单独的app,他可以自己单独调试,可以单独运行,而且此时主app还不会依赖他,当他为false的时候,那么login这个module此时就是一个组件了,主app会依赖他,而且login此时也变成了library了,而不再是一个application了。

      接下来进行每个module来进行配置了,我们先来看看login 的gradle配置,

      

      在这里,我们重点关注三个红色框框里面的内容,第一个框框里面配置的是该module的性质,当singleModule为true 的时候,他是一个application,当他为false 的时候,他是一个library,也就是一个组件。第二个框框跟第一个框框一个意思,就是当login为组件的时候,他是不会有applicaionID的,第三个框框,他是为login这个module动态设置manifest文件的,当他为application的时候,manifest的文件在默认的位置,当他是一个组件的时候,他是在manifest这个文件加下的(manofest文件夹是自己创建的,大家也可以是其他的文件名称)。那么大家肯定好奇当他是组件的时候这个manifest文件里面的东西是什么了。那么我就带大家来看一看,其实没多大区别,

      

      大家可以对比下正常的manifest文件缺了哪些东西了。

      接下来再看看主app里面的配置了,

      

       我们还是重点看这个框框里面的内容了,当login是组件的时候,我们就依赖他,而且此时我们依赖的方式是用runtimeOnly,具体解释上面也写了,大家也可以自行百度再看看他的解释。

       到这里,我们在gradle中的所有的配置操作都已经完成了,现在我们要正式进入组件撸码环节了,在此之前,我先说说我们要达到的目的:通过之前设置的singleModule配置信息来动态控制login与app之间的依赖关系,并且他们之间是完全解耦的,也就是说,如果我们想让他们产生依赖关系就让singleModule的值为false,否则为true,而且在app依赖login的时候,主app中没有任何一点login中的代码痕迹(app中没有任何improt   login中的代码),这样才能达到我们组件化的目的。

       要想达到这个目的,那么肯定需要一个中间者了,很显然,在这个demo工程中,这个中间者就是这个library了,我们先来看看这个library的结构:

       

       在这个library中就这么几个类,但是可以实现我们想要的效果,我们一个个来看,首先是ILoginService这个接口,

public interface ILoginService {

    void launch(Context context,String targetName);
}

       这个接口里面暂时只写了一个启动Activity的方法,在正式项目中,大家根据需求自行添加方法。很显然,我们是要实现这个接口的,根据上面的需求,我们要在主app中启动login,但是又不能跟login中的代码产生关联,所以,我们在login中去实现这个接口,

  

public class LoginService implements ILoginService {
    @Override
    public void launch(Context context,String targetName) {
        Intent intent = new Intent(context,LoginActivity.class);
        intent.putExtra(Constant.COMPONENTNAME,targetName);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }
}

       在这个实现类中,我们看到在login这个模块中启动了LoginActivity了,这个肯定没问题了,至于里面的启动参数啥的,大家根据需求自行添加。至此,启动类有了,启动方法也有了,那么怎么样跟主App产生联系呢?在demo中,我的做法是首先创建一个单例ServiceFactory,我们看看该类的代码:

public class ServiceFactory {
    private static final ServiceFactory ourInstance = new ServiceFactory();

    public static ServiceFactory getInstance() {
        return ourInstance;
    }

    private ServiceFactory() {
    }

    private ILoginService loginService;

    public ILoginService getLoginService() {
        if(loginService == null){
            loginService = new EmptyService();
        }
        return loginService;
    }

    public void setLoginService(ILoginService loginService) {
        this.loginService = loginService;
    }
}

       在这个类中,我们看到有一个ILoginService 的引用,然后设置了他的getter,setter方法,目的就是方便app拿到他们,而且这个实例类的作用远不止于此,因为这只是我们一个登陆模块,如果还有其他的service,依然可以设置在这里面,那么这样的话,主app想要调用的类都在这里面了,我们看到在getLoginService方法中对loginService做了一个判空的判断,目的是如果当login模块是单独运行的时候,那么此时主app是不会依赖login的,那么获取到的loginService肯定是空的,在这里返回一个EmptyService就是防止空指针,因为这个时候主app点击登录的时候是不会有反应的,不会崩溃(这种情况可以根据实际项目做对应的处理),现在获取,设置loginservice的类,方法都有了,那么在哪里设置呢,而且这个设置是要在一个组件启动的时候就要设置进去了,很明显,我们是要在组件启动最早的时候去启动它了,那么就是组件的Application中去操作了,但是这里又要跟前面的需求挂钩了,我们在主app中不能有login的任何代码,那么此时我们就在library中新增加一个接口了,然后让主App的Application与组件的Application都去实现它,这样的话就可以做到最早初始化了,所以对于login组件的Application,他的代码是这样的:

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

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

        初始化之后,那么主App如果去获取这个App呢,他们之间既然没有任何关系,但是又想拿到他,那么怎么做呢?相信大家都想到了,那就是反射了,我们看看主App的Application:

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

    @Override
    public void initial(Application application) {
        for(String component: AppConfig.COMPONENTS){
            try {
                Class<?> clazz = Class.forName(component);
                Object obj = clazz.newInstance();
                if(obj instanceof IModuleComponent){
                    ((IModuleComponent)obj).initial(this);
                }
            } catch (Exception e) {
                Log.e("molin", "loginApp can't be found");
            }
        }
    }
}

        通过这个反射,我们就可以获取到login组件的Application了,那么这样的话,loginService此时不就设置到了ServiceFactory中去了吗,至于这个AppConfig类,里面只是记录我们需要反射的组件全名称。他的代码如下:

public class AppConfig {
    public static String[] COMPONENTS = {"lml.molin.com.login.App"};
}

至此,我们整个流程就已经全部完成,现在就是见证成果的时候了:

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

import lml.molin.com.baselibrary.ServiceFactory;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }

    public void login(View view) {
        ServiceFactory.getInstance().getLoginService().launch(this,MainActivity.class.getName());
    }
}

        这是我们的主App的MainActivity类,login方法是一个按钮点击方法,我们发现,在这整个类中,我们没有任何有关Login组件的代码。

       但是我们在这个组件化开发的过程中我们还要注意一些问题,也是特别要注意的,就是资源名称一致的问题,特别是layout的资源问题,名称一定不能相同,否则调用的时候就会出异常了。至此,组件化整个开发流程已经完成,希望可以帮助到大家,谢谢!!!

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面我将基于 Vue.js 和 Three.js 实现一个简单demo,用于展示如何结合使用这两个库开发可视项目。 首先,你需要安装 Vue.js 和 Three.js。你可以使用以下命令来安装它们: ``` npm install vue three --save ``` 接下来,在 Vue.js 应用程序中创建一个组件,用于渲染 Three.js 场景。以下是一个简单组件代码: ```vue <template> <div ref="renderer"></div> </template> <script> import * as THREE from 'three'; export default { name: 'ThreeScene', data() { return { width: 800, height: 600, scene: null, camera: null, renderer: null, cube: null, } }, mounted() { // 创建 Three.js 场景 this.scene = new THREE.Scene(); // 创建 Three.js 相机 this.camera = new THREE.PerspectiveCamera( 75, // 视角 this.width / this.height, // 宽高比 0.1, // 近处平面 1000 // 远处平面 ); // 创建 Three.js 渲染器 this.renderer = new THREE.WebGLRenderer(); // 设置渲染器大小 this.renderer.setSize(this.width, this.height); // 将渲染器添加到 HTML 元素中 this.$refs.renderer.appendChild(this.renderer.domElement); // 创建一个立方体 const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); this.cube = new THREE.Mesh(geometry, material); // 将立方体添加到场景中 this.scene.add(this.cube); // 设置相机位置 this.camera.position.z = 5; // 渲染 Three.js 场景 const render = () => { this.renderer.render(this.scene, this.camera); requestAnimationFrame(render); } requestAnimationFrame(render); } } </script> ``` 上面的代码创建了一个 Three.js 场景,添加了一个立方体到场景中,并通过 requestAnimationFrame() 方法实现了场景的渲染。在组件的 mounted 钩子中,我们创建了 Three.js 场景、相机、渲染器,并将它们添加到 HTML 元素中。然后,我们创建了一个立方体,将其添加到场景中,并设置相机的位置。最后,我们使用 requestAnimationFrame() 方法渲染场景。 接下来,你可以在 Vue.js 应用程序中使用这个组件,例如: ```vue <template> <div> <three-scene></three-scene> </div> </template> <script> import ThreeScene from './components/ThreeScene.vue'; export default { name: 'App', components: { ThreeScene, }, } </script> ``` 这样,你就可以在 Vue.js 应用程序中使用 Three.js 创建一个简单的可视场景了。当你运行这个应用程序时,将会看到一个绿色的立方体在屏幕上旋转。你可以尝试修改代码,添加更多的 Three.js 物体和动画,实现更复杂的可视场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值