广告
返回顶部
首页 > 资讯 > 移动开发 >Android组件化原理详细介绍
  • 442
分享到

Android组件化原理详细介绍

2024-04-02 19:04:59 442人浏览 独家记忆
摘要

目录什么是组件化?为什么使用组件化?一步步搭建组件化1.新建模块2.统一Gradle版本号3.创建基础库4.组件模式和集成模式转换5.AndroidManifest的切换6.*业务A

什么是组件化?

一个大型APP版本一定会不断的迭代,APP里的功能也会随之增加,项目的业务也会变的越来越复杂,这样导致项目代码也变的越来越多,开发效率也会随之下降。并且单一工程下代码耦合严重,每修改一处代码后都要重新编译,非常耗时,单独修改的一个模块无法单独测试

组件化架构的目的是让各个业务变得相对独立,各个组件在组件模式下可以独立开发调试,集成模式下又可以集成到“app壳工程”中,从而得到一个具有完整功能的APP。

组件化每一个组件都可以是一个APP可以单独修改调试,而不影响总项目。

组件化基础架构图

为什么使用组件化?

编译速度: 可以但需测试单一模块,极大提高了开发速度
超级解耦: 极度降低了模块间的耦合,便于后期的维护和更新
功能重用: 某一块的功能在另外的组件化项目中使用只需要单独依赖这一模块即可
便于团队开发: 组件化架构是团队开发必然会选择的一种开发方式,它能有效的使团队更好的协作

一步步搭建组件化

这里以演示为例,只设置登录这一个功能组件

组件化开发要注意的几点问题 :

  • 要注意包名和资源文件命名冲突问题
  • Gradle中的版本号的统一管理
  • 组件在AppIicationLibrary之间如何做到随意切换
  • AndroidManifest. xml文件的区分
  • Library不能在Gradle文件中有applicationId

这里以演示为例,只设置登录和个人中心这两个功能组件

1.新建模块

在这里插入图片描述

并且在module里新建一个activity

在这里插入图片描述

到这里我们看到login和我们的app都在有一个绿点证明创建成功

在这里插入图片描述

个人中心member模块创建同理,并且每个模块目前都可以独立运行。

在这里插入图片描述

2.统一Gradle版本号

每一个模块都是一个application,所以每个模块都会有一个build.gradle,各个模块里面的配置不同,我们需要重新统一Gradle
在主模块创建config.gradle

在这里插入图片描述

config.gradle里去添加一些版本号

ext{
    android = [
            compileSdkVersion :30,
            buildToolsVersion: "30.0.2",
            applicationId :"activitytest.com.example.moduletest",
            minSdkVersion: 29,
            targetSdkVersion :30,
            versionCode :1,
            versionName :"1.0",
    ]
    androidxDeps = [
            "appcompat": 'androidx.appcompat:appcompat:1.1.0',
            "material": 'com.Google.android.material:material:1.1.0',
            "constaraintlayout": 'androidx.constraintlayout:constraintlayout:1.1.3',
    ]
    commonDeps = [
            "arouter_api"          : 'com.alibaba:arouter-api:1.5.1',
            "glide"                : 'com.GitHub.bumptech.glide:glide:4.11.0'

    ]
    annotationDeps = [
            "arouter_compiler" : 'com.alibaba:arouter-compiler:1.5.1'
    ]
    retrofitDeps = [
            "retrofit"  : 'com.squareup.retrofit2:retrofit:2.9.0',
            "converter" : 'com.squareup.retrofit2:converter-gson:2.9.0',
            "rxjava"    : 'io.Reactivex.rxjava2:rxjava:2.2.20',
            "rxandroid" : 'io.reactivex.rxjava2:rxandroid:2.1.1',
            "adapter"   : 'com.squareup.retrofit2:adapter-rxjava2:2.9.0'
    ]
    androidxLibs = androidxDeps.values()
    commonLibs = commonDeps.values()
    annotationLibs = annotationDeps.values()
    retrofitLibs = retrofitDeps.values()
}

在主模块的build.gradle里添加

apply from: "config.gradle"

在这里插入图片描述

在各模块中去引用这些版本号
引用格式如下,两种写法均可

compileSdkVersion rootProject.ext.android["compileSdkVersion"]
buildToolsVersion rootProject.ext.android.buildToolsVersion

引用前:

在这里插入图片描述

引用后:

在这里插入图片描述

并且使用同样的方法,我们还可以统一我们的依赖库在config.gradle里去添加我们要依赖的库,并在各个模块中去添加依赖

 implementation  rootProject.ext.dependencies.publicImplementation

也可以采用第二种写法

dependencies = [

            "appcompat"             : 'androidx.appcompat:appcompat:1.2.0',

            "material"               : 'com.google.android.material:material:1.2.1',
            "constraintLayout"       : 'androidx.constraintlayout:constraintlayout:2.0.4',//约束性布局

            //test
            "junit"                  : "junit:junit:4.13.1",
            "testExtJunit"           : 'androidx.test.ext:junit:1.1.2',//测试依赖,新建项目时会默认添加,一般不建议添加
            "espressoCore"           : 'androidx.test.espresso:espresso-core:3.3.0',//测试依赖,新建项目时会默认添加,一般不建议添加

    ]

添加依赖:

dependencies {

    implementation rootProject.ext.dependencies.appcompat
    implementation  rootProject.ext.dependencies["constraintLayout"]
    testImplementation rootProject.ext.dependencies["junit"]
    androidTestImplementation rootProject.ext.dependencies["testExtJunit"]
    androidTestImplementation rootProject.ext.dependencies["espressoCore"]

}

3.创建基础库

和新建module一样,这里需要新建一个library我们把它命名为Baselibs

在这里插入图片描述

同样需要统一版本号,由于这是一个library模块,所以它不需要applicationId

在这里插入图片描述

我们一样可以把它写进config.gradle

other:[path:':Baselibs']

在每个模块去调用

implementation  project(rootProject.ext.dependencies.other)

同理,当本地库为单独所用,我们可以直接调用,而不需要将其写入config.gradle,两种方法选择合适使用即可。

implementation project(':Baselibs')

但有时因为gradle版本问题,我们可能无法依赖到这些公共库,因为我们在config.gradle里是以数组形式定义的,这时我们可以同for-each循环的方法将其依次导入
config.gradle

dependencies = [
          ......
        other:[':Baselibs']
    ]

其他模块的build.gradle

dependencies {
......
    rootProject.ext.dependencies.other.each{
        implementation project(it)
    }

4.组件模式和集成模式转换

在主模块gradle.properties里添加布尔类型选项。

在这里插入图片描述

在各个模块的build.gradle里添加更改语句

if(is_Module.toBoolean()){
    apply plugin: 'com.android.application'
}else{
    apply plugin: 'com.android.library'
}

每个模块的applicationId也需要处理

if(is_Module.toBoolean()){
            applicationId "activitytest.com.example.login"
        }

在这里插入图片描述

当我们将is_module改为false时,再次运行编译器我们的模块都不能单独运行了

在这里插入图片描述

在app模块中添加判断依赖就可以在集成模式下将各模块添加到app主模块中

// 每加入一个新的模块,就需要在下面对应的添加一行
    if (is_Module.toBoolean())]) {
        implementation project(path:':login')
        implementation project(path:':member')
    }

5.AndroidManifest的切换

为了单独开发加载不同的AndroidManifest这里需要重新区分下。
在组件模块里的main文件里新建manifest文件夹

在这里插入图片描述

并且重写一个AndroidManifest.xml文件,集成模式下,业务组件的表单是绝对不能拥有自己的 Application 和 launch 的 Activity的,也不能声明APP名称、图标等属性,总之app壳工程有的属性,业务组件都不能有,在这个表单中只声明了应用的主题,而且这个主题还是跟app壳工程中的主题是一致的

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="Http://schemas.android.com/apk/res/android"
    package="com.example.login">

    <application
        android:theme="@style/Theme.MoudleTest">
        <activity android:name=".LoginActivity">
       
        </activity>
    </application>

</manifest>

并且我们还要使其在不同的模式下加载不同的AndroidManifest只需在各模块的build.gradle里添加更改语句

sourceSets {
        main {
            if (is_Module.toBoolean()) {
                manifest.srcFile 'src/main/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/mainfest/AndroidManifest.xml'
            }
        }
    }

6.*业务Application切换

每个模块在运行时都会有自己的application,而在组件化开发过程中,我们的主模块只能有一个application,但在单独运行时又需要自己的application这里就需要配置一下。
在业务模块添加新文件夹命名module

在这里插入图片描述

在里面建一个application文件

在这里插入图片描述

并且我们在build.gradle文件里配置module文件夹使其在单独运行时能够运行单独的application
在配置manifest的语句中添加java.srcDir 'src/main/module'

sourceSets {
        main {
            if (is_Module.toBoolean()) {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java.srcDir 'src/main/module'
            } else {
                manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
            }
        }
    }

同时我们在basic基础层内新建application,用于加载一些数据的初始化

public class BaseApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("fff","baseapplication");
    }
}

在业务模块内module里重写该模块的application

public class LoginApplication extends BaseApplication {
    @Override
    public void onCreate() {
        super.onCreate();
    }
}

至此,组件化框架搭建结束

组件之间的跳转

这里采用阿里巴巴的开源ARouter来实现跳转功能,我会在以后的文章单独拿出一篇来一步步去解读Arouter源码,让我们自己去搭建一个自己的路由

一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦

github 上 ARouter 的介绍可以知道,它可以实现组件间的路由功能。路由是指从一个接口上收到数据包,根据数据路由包的目的地址进行定向并转发到另一个接口的过程。这里可以体现出路由跳转的特点,非常适合组件化解耦。

要使用 ARouter 进行界面跳转,需要我们的组件对 Arouter 添加依赖,因为所有的组件都依赖了 Baselibs模块,所以我们在 Baselibs 模块中添加 ARouter 的依赖即可。其它组件共同依赖的库也最好都放到 Baselibs中统一依赖。

这里需要注意的是,arouter-compiler 的依赖需要所有使用到 ARouter 的模块和组件中都单独添加,不然无法在 apt 中生成索引文件,也就无法跳转成功。并且在每一个使用到 ARouter 的模块和组件的 build.gradle 文件中,其 android{} 中的 javaCompileOptions 中也需要添加特定配置。

1.添加依赖

Baselibs里的build.gradle添加依赖

dependencies {
    api 'com.alibaba:arouter-api:1.3.1'
    // arouter-compiler 的注解依赖需要所有使用 ARouter 的 module 都添加依赖
    annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
}
// 所有使用到 ARouter 的组件和模块的 build.gradle
android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [ moduleName : project.getName() ]
            }
        }
    }
}
dependencies {
    ...
    implementation project (':base')
    annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
}

主模块需要对跳转模块进行依赖:

// 主项目的 build.gradle 需要添加对 login 组件和 share 组件的依赖
dependencies {
    // ... 其他
    implementation project(':login')
    implementation project(':share')
}

2.初始化ARouter

添加了对 ARouter 的依赖后,还需要在项目的 Application 中将 ARouter 初始化,我们这里将 ARouter 的初始化工作放到主模块Application 的 onCreate()方法中,在应用启动的同时将 ARouter 初始化。

public class MainApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // 初始化 ARouter
        if (isDebug()) {           
            // 这两行必须写在init之前,否则这些配置在init过程中将无效
            // 打印日志
            ARouter.openLog();     
            // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
            ARouter.openDebug();   
        }

        // 初始化 ARouter
        ARouter.init(this);

    }
    private boolean isDebug() {
        return BuildConfig.DEBUG;
    }
}

3.添加跳转

这里我们在首页添加登录分享两个跳转页面。

login.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ARouter.getInstance().build("/login/login").navigation();
    }
});
share.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ARouter.getInstance().build("/share/share").navigation();
    }
});

然后,需要在登录和分享组件中分别添加 LoginActivityShareActivity ,然后分别为两个 Activity 添加注解 Route,其中path 是跳转的路径,这里的路径需要注意的是至少需要有两级,/xx/xx

@Route(path = "/login/login")
public class Login extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

    }
}
@Route(path = "/share/share")
public class Share extends AppCompatActivity {

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

这样就可以实现跳转了。

组件之间的数据传递

由于主项目与组件,组件与组件之间都是不可以直接使用类的相互引用来进行数据传递的,那么在开发过程中如果有组件间的数据传递时应该如何解决呢,这里我们可以采用 [接口 + 实现] 的方式来解决。

Baselibs基础库里定义组件可以对外提供访问自身数据的抽象方法的 Service。并且提供了一个 ServiceFactory,每个组件中都要提供一个类实现自己对应的 Service 中的抽象方法。在组件加载后,需要创建一个实现类的对象,然后将实现了 Service 的类的对象添加到ServiceFactory 中。这样在不同组件交互时就可以通过 ServiceFactory 获取想要调用的组件的接口实现,然后调用其中的特定方法就可以实现组件间的数据传递与方法调用。

当然,ServiceFactory 中也会提供所有的 Service 的空实现,在组件单独调试或部分集成调试时避免出现由于实现类对象为空引起的空指针异常。

下面我们就按照这个方法来解决组件间数据传递与方法的相互调用这个问题,这里我们通过分享组件 中调用 登录组件 中的方法来获取登录状态是否登录这个场景来演示。

1.定义接口

其中 service文件夹中定义接口,LoginService 接口中定义了 Login 组件向外提供的数据传递的接口方法,EmptyService 中是 service 中定义的接口的空实现,ServiceFactory 接收组件中实现的接口对象的注册以及向外提供特定组件的接口实现。

在这里插入图片描述

LoginService

public interface LoginService {
    
    boolean isLogin();
    
    String getPassword();
}

EmptyService

public class EmptyService implements LoginService {
    @Override
    public boolean isLogin() {
        return false;
    }

    @Override
    public String getPassword() {
        return null;
    }
}

ServiceFactory

public class ServiceFactory {
    private LoginService loginService;
    private ServiceFactory(){
 
    private ServiceFactory() {
    }
    
    public static ServiceFactory getInstance() {
        return Inner.serviceFactory;
    }
    private static class Inner {
        private static ServiceFactory serviceFactory = new ServiceFactory();
    }
 
    public void setLoginService(LoginService loginService){
        this.loginService = loginService;
    }
       
    public LoginService getLoginService(){
        if(loginService == null){
            return new EmptyService();
        }else{
            return loginService;
        }
    }
}

2.实现接口

在login模块

public class AccountService implements LoginService {

    private boolean login;
    private String passWord;

    public AccountService(boolean login, String password) {
        this.login = login;
        this.password = password;
    }

    @Override
    public boolean isLogin() {
        return login;
    }

    @Override
    public String getPassword() {
        return password;
    }
}

这里新建一个Util类用来存储登录数据

public class LoginUtil {
    static boolean isLogin = false;
    static String password = null;
}

实现一下登录操作

login = (Button)findViewById(R.id.login);
        login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LoginUtil.isLogin = true;
                LoginUtil.password = "admin";
                ServiceFactory.getInstance().setLoginService(new AccountService(LoginUtil.isLogin,LoginUtil.password));
            }
        });

在login模块的application里定义ServiceFactory类

public class LoginApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        ServiceFactory.getInstance().setLoginService(new AccountService(LoginUtil.isLogin,LoginUtil.password));
    }
}

在分享模块获取登录信息

share = (Button)findViewById(R.id.share);
        share.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(ServiceFactory.getInstance().getLoginService().isLogin()){
                    Toast.makeText(ShareActivity.this,"分享成功!",Toast.LENGTH_SHORT).show();
                }else{
                    Toast.makeText(ShareActivity.this,"分享失败,请先登录!",Toast.LENGTH_SHORT).show();
                }
            }
        });

一个项目时只能有一个 Application 的,Login 作为组件时,主模块的 Application 类会初始化,而 Login 组件中的 Applicaiton 不会初始化。确实是存在这个问题的,我们这里先将 Service 的注册放到其活动里,稍后我们会解决 Login 作为组件时 Appliaciton 不会初始化的问题。

组件Application的动态切换

在主模块中有 Application 等情况下,组件在集中调试时其 Applicaiton 不会初始化的问题。而我们组件的 Service 在 ServiceFactory 的注册又必须放到组件初始化的地方。

为了解决这个问题可以将组件的 Service 类强引用到主 Module 的 Application 中进行初始化,这就必须要求主模块可以直接访问组件中的类。而我们又不想在开发过程中主模块能访问组件中的类,这里可以通过反射来实现组件 Application 的初始化。

1.定义抽象类 BaseApplication 继承 Application

Baselibs基础库模块

public abstract class BaseApplication extends Application {
    
    public abstract void initModuleApp(Application application);

    
    public abstract void initModuleData(Application application);              //其他需要调用的方法
}

2.所有的组件的 Application 都继承 BaseApplication

这里我们以Login模块为例

public class LoginApplication extends BaseApplication{
    @Override
    public void onCreate() {
        super.onCreate();
        initModuleApp(this);
        initModuleData(this);
    }
    @Override
    public void initModuleApp(Application application) {
        ServiceFactory.getInstance().setLoginService(new AccountService(LoginUtil.isLogin,LoginUtil.password));
    }
    @Override
    public void initModuleData(Application application) {

    }
}

3.定义 AppConfig 类

Baselibs模块定义一个静态的 String 数组,我们将需要初始化的组件的 Application 的完整类名放入到这个数组中。

public class AppConfig {
    private static final String LoginApp = "com.example.login.LoginApplication";
    public static String[] moduleApps = {
            LoginApp
    };
}

4.主模块application实现两个初始化方法

// 主 Module 的 Applicaiton
public class MainApplication extends BaseApp {
    @Override
    public void onCreate() {
        super.onCreate();
        
        // 初始化组件 Application
        initModuleApp(this);
        
        // 其他操作
        
        // 所有 Application 初始化后的操作
        initModuleData(this);
        
    }

    @Override
    public void initModuleApp(Application application) {
        for (String moduleApp : AppConfig.moduleApps) {
            try {
                Class clazz = Class.forName(moduleApp);
                BaseApp baseApp = (BaseApp) clazz.newInstance();
                baseApp.initModuleApp(this);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    public void initModuleData(Application application) {
        for (String moduleApp : AppConfig.moduleApps) {
            try {
                Class clazz = Class.forName(moduleApp);
                BaseApp baseApp = (BaseApp) clazz.newInstance();
                baseApp.initModuleData(this);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }
}

到这里我们就通过反射,完成了组件 Application 的初始化操作,也实现了组件与化中的解耦需求。

主模块使用其他组件的 Fragment

我们在开发过程中经常使用 Fragment。一般情况下,我们都是直接通过访问具体 Fragment 类的方式实现 Fragment 的实例化,但是现在为了实现模块与组件间的解耦,在移除组件时不会由于引用的 Fragment 不存在而编译失败,我们就不能模块中直接访问组件的 Fragment 类。
这里介绍两种方法

1.ARouter

这里可以采用ARouter直接调用

fragment = (Fragment) ARouter.getInstance().build("/login/fragment").navigation();

2.反射

我们还是以Login模块为例,假如在该模块创建一个用户界面,命名为UserFragment
首先,在 Login组件中创建 UserFragment,然后在 LoginService 接口中添加newUserFragment方法返回一个Fragment,在Login组件中的 AccountServiceBaselibsLoginService 的空实现类中实现这个方法,然后在主模块中通过 ServiceFactory 获取 LoginService 的实现类对象,调用其 newUserFragment 即可获取到 UserFragment 的实例。

// Baselibs 模块的 LoginService 
public interface LoginService {
//其他代码...
    Fragment newUserFragment(Activity activity, int containerId, FragmentManager manager, Bundle bundle, String tag);
}
// Login 组件中的 AccountService
public class AccountService implements LoginService {
    // 其他代码 ...

    @Override
    public Fragment newUserFragment(Activity activity, int containerId, FragmentManager manager, Bundle bundle, String tag) {
        FragmentTransaction transaction = manager.beginTransaction();
        // 创建 UserFragment 实例,并添加到 Activity 中
        Fragment userFragment = new UserFragment();
        transaction.add(containerId, userFragment, tag);
        transaction.commit();
        return userFragment;
    }
}
// 主模块的 FragmentActivity
public class FragmentActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment);
        
        // 通过组件提供的 Service 实现 Fragment 的实例化
        ServiceFactory.getInstance().getAccountService().newUserFragment(this, R.id.layout_fragment, getSupportFragmentManager(), null, "");
    }
}

到此这篇关于Android组件化原理详细介绍的文章就介绍到这了,更多相关Android组件化 内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Android组件化原理详细介绍

本文链接: https://www.lsjlt.com/news/165514.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Android组件化原理详细介绍
    目录什么是组件化?为什么使用组件化?一步步搭建组件化1.新建模块2.统一Gradle版本号3.创建基础库4.组件模式和集成模式转换5.AndroidManifest的切换6.*业务A...
    99+
    2022-11-13
  • Android四大组件之Activity详细介绍
    目录理论概述Activity的理解Activity的定义Activity的作用类比Activity与ServletIntent的理解Intent的分类Intent的使用IntentF...
    99+
    2022-11-12
  • Android Jetpack组件中LifeCycle作用详细介绍
    目录Jetpack1、那么Jetpack是什么呢2、为何使用Jetpack3、Jetpack与AndroidXLifeCycle1、LifeCycle的作用2、LifeCycle应用...
    99+
    2022-11-13
  • Android 开发音频组件(Vitamio FAQ)详细介绍
     一、Vitamio介绍   1.1 Vitamio是什么?    Vitamio是Android平台视音...
    99+
    2022-06-06
    vitamio Android
  • Node.js模块化原理与应用详细介绍
    目录什么是模块化模块化规范node.js中的模块分类加载模块node.js的模块作用域什么是模块作用域模块作用域的好处向外共享模块作用域中的成员module对象module.expo...
    99+
    2022-11-13
  • Jetpack Compose常用组件详细介绍
    目录1. Text2. Image3. LazyColumn1. Text 日常最常用的应该就是显示文字,所以有必要说一下Text控件。首先源码如下: @Composable fun...
    99+
    2022-11-13
    Jetpack Compose组件 Jetpack组件布局 Jetpack组件
  • C#控件数组的详细介绍
    这篇文章主要介绍“C#控件数组的详细介绍”,在日常操作中,相信很多人在C#控件数组的详细介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C#控件数组的详细介绍”的疑惑有所帮助!接下来,请跟着小编一起来学习吧...
    99+
    2023-06-17
  • PythonCountingBloomFilter原理与实现详细介绍
    目录前言原理一、BF 为什么不支持删除二、什么是 Counting Bloom Filter三、Counter 大小的选择简单的实现总结前言 标准的 Bloom Filter 是一种...
    99+
    2022-11-11
  • Android增量升级的方法和原理详细介绍
    总结:我们使用delta编码算法减少Android应用升级程序的大小。我们通过bsdiff和bspatch工具在android上实现delta编码算法。服务器软件和androi...
    99+
    2022-06-06
    方法 Android
  • Android 布局控件之LinearLayout详细介绍
    LinearLayout是线性布局控件,它包含的子控件将以横向或竖向的方式排列,按照相对位置来排列所有的widgets或者其他的containers,超过边界时,某些控件将缺失...
    99+
    2022-06-06
    布局 Android
  • React高阶组件使用详细介绍
    首先 我们要了解什么是高阶组件 第一 高阶组件必须是一个函数 第二 高阶组件接收一个参数,这个参数也必须是一个组件 第三 他的返回值 也是一个组件 至于高阶组件的作用 我们后续会讲解...
    99+
    2023-01-29
    React高阶组件 React高阶组件的使用 React高阶组件的作用
  • android listview优化几种写法详细介绍
    这篇文章只是总结下getView里面优化视图的几种写法,就像孔乙己写茴香豆的茴字的几种写法一样,高手勿喷,勿笑,只是拿出来分享,有错误的地方欢迎大家指正,谢谢。 listvie...
    99+
    2022-06-06
    listview Android
  • Android中卡顿优化布局详细介绍
    目录背景实践过程如何渲染界面什么是过度绘制如何查看绘制维度界面优化硬件加速原理总结背景 在当下移动互联网后半场,手机已经是人手必备的设备。App是离用户最近的应用,界面又是最直观影响...
    99+
    2022-11-13
  • Android自定义控件属性详细介绍
     Android自定义控件属性详细介绍1. reference:参考某一资源ID。     (1)属性定义: <declare-styleable name = "名称"> ...
    99+
    2023-05-31
    android 自定义 控件
  • React事件处理超详细介绍
    目录1. 事件绑定1.1 函数组件1.2 类组件2. 合成事件3. 事件传参的3种不同写法4. this 指向问题1. 事件绑定 React 元素的事件处理和 DOM 元素的很相似,...
    99+
    2022-11-13
  • React受控组件与非受控组件详细介绍
    目录1. 受控组件1.1 介绍1.2 受控组件简写1.3 在表单中使用受控组件1.4 综合案例2. 非受控组件介绍非受控组件的应用1. 受控组件 1.1 介绍 概述: 将 state...
    99+
    2022-11-13
  • Vue封装DateRangePicker组件流程详细介绍
    目录前言一、封装el-date-picker二、父组件中进行引用总结前言 日期选择器在实际使用中,可能会要求默认固定日期、回显日期或者是时间固定点(00:00:00到23:59:59...
    99+
    2022-11-16
    Vue DateRangePicker Vue封装DateRangePicker组件
  • 详细介绍 Vue 中如何划分组件
    随着前端技术的不断发展,Vue 受到越来越多的关注,成为了前端开发中备受推崇的框架之一。Vue 的高效性和采用的虚拟 DOM 技术,使其在实际开发中得到了有效的应用。Vue 中的划分是 Vue 进行组件划分的一个很重要的部分。本文将详细介绍...
    99+
    2023-05-14
  • 非常详细的 Ceph 介绍、原理、架构
    非常详细的 Ceph 介绍、原理、架构 1. Ceph架构简介及使用场景介绍 1.1 Ceph简介 Ceph是一个统一的分布式存储系统,设计初衷是提供较好的性能、可靠性和可扩展性。 Ceph项目最早起源于Sage就读博士期间的工作(最...
    99+
    2023-09-05
    ceph
  • golang RPC包原理和使用详细介绍
    目录工作流程工作模式http模式服务器模式本篇文章旨在通过学习rpc包和github上的一个rpc小项目,熟悉和学习golang中各个包的使用 工作流程 通过阅读官方文档,了解了rp...
    99+
    2022-11-11
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作