前言
网上都说Dagger2是比较难上手的,我在看了大量资料和使用时也遇到了很多不懂或者模糊的知识点,而且大部分博客资料都比较古老。突然有那么一瞬间,突然明白了所以然,故总结了4篇文章。话说在java中使用还是很繁琐的,不要怕带你真正上手,并运用到我们的Android项目中去。

本次Dagger2讲解总共分4篇:
1、Dagger2基础知识及在Java中使用(1)
2、Dagger2基础知识及在Java中使用(2)
3、Dagger2进阶知识及在Android中使用
4、Dagger2华丽使用在MVP框架中

首先简单申明下,Dagger2的好处不是本文的重点。大家可以自行百度。Dagger2是依赖注解框架,像我们之间的butterknife也是这样的框架,想这样的框架依赖一般都有2行。第二行是以annotationProcessor开头的。这其实是apt的工具,而且这样的依赖注解框架不会影响性能(不是反射机制),在编译的时候,apt把要用的代码生成。所以大可放心使用
再举我理解的例子(大家不要全信,哈哈稍微不恰当):@Component相当于一个注射器(记住是接口);@Module相当于注射液,就是数据源(记住这里是类或者抽象类),此时要把注射液放入指定哪个注射器如:@Component( modules = … );@Inject 相当于标注被注射体。

之后的讲解都是走完简单的流程,实现功能,然后讲大概理解。贴在博客上的代码,可能会省略部分代码,便于理解。github上的Demo及注释,非常详细,接近完美0-0#
如果是完全没了解过,关于Dagger一些标注的具体介绍和理解,推荐这里有3篇介绍标注的意思和怎么工作的

首先添加依赖
implementation 'com.google.dagger:dagger:2.24'
annotationProcessor "com.google.dagger:dagger-compiler:2.24"
1
2

1、@Inject & @Component 的简单使用(不带@Module)
首先随便定义个类:Person,无参构造方法用@Inject标注:

public class Person {
    @Inject
    public Person() {

}
}
1
2
3
4
5
6

然后定义我们的 Component(这里稍微提一下,如果一个页面定义多个Component,你build的时候报错,是不是)
这里的命名规则最好是以我们页面类名+Component,这样比较清晰。用@Component标注,里面有个void方法,方法名随意定,建议用inject最好,当然也是清晰,参数是我们需要依赖注解的页面:
@Component
public interface AstudyActivityComponent {
    void injectTo(AstudyActivity astudyActivity);
}
1
2
3
4
做好上面步骤后,点开studio里build标签下的Make Project。让apt帮我们生成代码,一般生成代码为Dagger+你定义Component的类名。

之后这个步骤不再重复,就是你写完准备代码的时候一定要让apt生成代码,Make Project下

然后在我们的Activity里:

public class AstudyActivity extends BaseActivity {
    @Inject
    Person person;
    
    @Override
    //这里是我封装的onCreate,省略部分代码,只为理解,之后都请忽略!
    protected void processLogic() {
        //第一种
        DaggerAstudyActivityComponent.create().injectTo(this);
        //第二种
        //DaggerAstudyActivityComponent.builder().build().injectTo(this);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
在我们的Activity里build下我们的Component,然后注册在我们的Activity里,就可以使用通过我们的@Inject使用我们的person了。
这里初始化有2种:
1、DaggerAstudyActivityComponent.create().injectTo(this);
2、DaggerAstudyActivityComponent.builder().build().injectTo(this); 这个使用module传值一定要使用

那么一个简单的使用就实现了,这里忽略了new的过程,从这个过程就知道他解耦的实现了。

简单使用大致步骤( 看懂请略过 ):
第一步:用Inject标注,告诉dagger2可以实例化这个类,如:Person
第二步:使用注解Component,表示要将依赖注入到AstudyActivity里
第三步:使用android studio build下的Make Project生成代码,使他自动生成DaggerComponent生成的类,类名是:Dagge+我们定义的Component的名字

2、带@Module的使用
为什么会有module的概念,比如上面的Person的构造方法可以用@Inject标注,但是引入的第三方库可是没有办法加的,所以这里使用@Module可以解决这个问题。
这里我们定义个Human,假装他是第三方类库,里面没有使用@Inject

public class Human {
    public Human() {

}
}
1
2
3
4
5

接下的步骤先定义我们的数据源Module,也就是先定义初始化的地方,之前Person的构造方法是用@Inject。首先命名规则最好加上Module,用@Module标注。然后里面定义个方法,用 @Provides标注。返回值为我们需要初始化的类,方法名最好是以Provides结尾。其实这里可以定义多个方法,后面说

@Module
public class BstudyActivityModule {
    @Provides
    Human humanProvides(){
       return new Human();
    }
}
1
2
3
4
5
6
7

然后是我们的Component。这里与之前不同的是(modules = BstudyActivityModule.class),这就相当于把注射液放进注射器。这里可以有多个Module,后面说

@Component(modules = BstudyActivityModule.class)
public interface BstudyActivityComponent {
    void injectTo(BstudyActivity bstudyActivity);
}
1
2
3
4
Make Project后,在Activity里操作与之前一模一样。

带Module使用大致步骤( 看懂请略过 )
1、假设Human不可随意更改,没有@Inject标注(第三方类库,不是你项目里的代码肯定没有@Inject)用@module标注BstudyActivityModule,用@Provides标注方法的返回值就是我们需要inject的类型
2、编写Component接口使用@Component标注这个接口,并使用modules=的方法链接上第一步中编写的Module类;
3、接下来就和AstudyActivity中的使用方式一样了

3、通过Module传参
这个其实不重要,重要的引出4,5的概念。明白这步,后面才好理解。
首先我们假设2个类,女人类,和灵魂类:且灵魂类有个钱的属性。灵魂类又是女人的属性。灵魂类如下:

public class Soul {
    private int money;
    public Soul() {

}
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
女人如下:

public class Woman {
    private Soul soul;

public Soul getSoul() {
        return soul;
    }

public Woman(Soul soul) {
        this.soul = soul;
    }
}
1
2
3
4
5
6
7
8
9
10
11

首先还是定义我们的Module先。既然可以传参,当然是有个money的属性。最终我们依赖注解是要使用Woman类。我们的providesWoman方法用@Provides标注,这个时候他回去找Soul的初始化,先通过@Provides去找Soul。这个时候找到了providesSoul。这样就形成了女人类。假如这个时候没有providesSoul。他会去找Soul类里有没有用@Inject标注的构造函数。如果还没有,那么不好意思。出错

@Module
public class CstudyModule {
    private int money;
    
    public CstudyModule(int money) {
        this.money = money;
    }

@Provides
    Soul providesSoul() {
        Soul soul = new Soul();
        soul.setMoney(this.money);
        return soul;
    }

@Provides
    Woman providesWoman(Soul soul) {
        return new Woman(soul);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
接下来是Component,没有变化

@Component(modules = CstudyModule.class)
public interface CstudyActivityComponent {
    void injectTo(CstudyActivity cstudyActivity);
}
1
2
3
4
Activity有些许变化,当然是传参了。我们给女人的灵魂传了100块,对,女人只值100块!

public class CstudyActivity extends BaseActivity {
    @Inject
    Woman woman;
    @Override
    protected void processLogic() {
        DaggerCstudyActivityComponent.builder()
            .cstudyModule(new CstudyModule(100))
            .build().injectTo(this);
    }
}
1
2
3
4
5
6
7
8
9
10
注意点( 看懂请略过 ):
在Module的构造函数带有参数且参数被使用的情况下,所生产的Component类就没有create()方法了。
在这里的module如果没有providesSoul()方法的话,还有一种情况只要在Soul的构造方法有@Inject也是可行的。

4、使用@Component.Builder(需先了解 3、通过Module传参)
我们把3、通过Module传参apt生成的代码点开DaggerCstudyActivityComponent;看下图是不是发现了一个Builder类,这是apt帮我们自动生成的,我们当然也能自己实现

还是以3、通过Module传参的例子,我们不用系统帮我们生成的Builder,自己定义。前面的步骤都一样,直接来看我们的Component。
自己定义个接口类Builder,并用@Component.Builder标注里面有2个方法:

方法一:是返回值Builder的方法,这里如果传module就会以我们传的为主,否则他会帮我们生成一个money为0的module。当然你也随意传数据类型,只不过无效。可以试试,
方法二:是返回值为当前Component的方法,方法名其实都可以自定义,当最好以规范为主,用习惯了就明白了
@Component(modules = CstudyModule.class)
public interface DstudyActivityComponent {
    void injectTo(DstudyActivity dstudyActivity);
    
    @Component.Builder
    interface Builder {
        Builder cstudyModule(CstudyModule cstudyModule);
        DstudyActivityComponent build();
    }
}
1
2
3
4
5
6
7
8
9
10

Activity里使用是一样的。只不过我们把系统自动帮我们生成的,自己去写了而已。还是贴下Activity代码吧

public class DstudyActivity extends BaseActivity {
    @Inject
    Woman dWoman;
    @Override
    protected void processLogic() {
        DaggerDstudyActivityComponent.builder()
            .cstudyModule(new CstudyModule(100))
            .build().injectTo(this);
    }
}
1
2
3
4
5
6
7
8
9
10
大致理解和总结为( 看懂请略过 ):
通过我们cstudy的内容,你可以点开cstudyModule查看源码,可以看到有个Builder cstudyModule(CstudyModule cstudyModule){}。这是dagger2自动生成的(你还可以通过,app/build/generated/source/apt/debug/你的包名/DaggerAppComponent.java 目录下找到)

所以@Component.Builder的用法,用module传参的例子。其他都不用变,要变的是Component,定义个Builder并用@Component.Builder标注。这里有2个方法:

方法一:是返回值Builder的方法,这里如果传module就会以我们传的为主,否则他会帮我们生成一个money为0的module。当然你也随意传数据类型,只不过无效。可以试试
方法二:是返回值为当前Component的方法,方法名其实都可以自定义,当最好以规范为主,用习惯了就明白了

5、使用@BindsInstance(需先了解 4、使用@Component.Builder)
这个时候你又说了,传参,我们总是要new CstudyModule(100)。本来说Dagger2在使用的时候省略new的过程,解耦。但这里还要new,很low是不是。不急不急,强大的google把一切都想好了。这个时候遇到一个新的标注@BindsInstance。
@BindsInstance 大致这里可以理解为帮我们省去写类的构造方法,而直接去赋值

省掉构造方法,那么当然是首先改我们的Module。我们去掉Module的构造方法及money成员变量属性,把money加到providesSoul里成型参。看到这里,这里又可理解为@BindsInstance 其实去找@Provides标记的方法的参数,假如类型一致就去初始化

@Module
public class EstudyModule {
    @Provides
    Soul providesSoul(int money) {
        Soul soul = new Soul();
        soul.setMoney(money);
        return soul;
    }

@Provides
    Woman providesWoman(Soul soul) {
        return new Woman(soul);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后是修改的Component,改完Module,当然是modules = EstudyModule.class。这些我就忽略了,看了上面的步骤你也明白,我就直接说关键地方了。用@BindsInstance标注我们返回值为Builder的方法。里面的参数改成我们的int Money。当然改成我们用@Provides标注的类型其实都可以,只不过这里你如果改成Soul soul,当然你初始化还是要传new Soul。过程就是这个过程

@Component(modules = EstudyModule.class)
public interface EstudyActivityComponent {
    void injectTo(EstudyActivity estudyActivity);
    
    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder initMoney(int money);
        EstudyActivityComponent build();
    }
}
1
2
3
4
5
6
7
8
9
10
11

最后是我们的Activity

public class EstudyActivity extends BaseActivity {
    @Inject
    Woman woman;
    @Override
    protected void processLogic() {
        DaggerEstudyActivityComponent.builder()
            .initMoney(100)
            .build().injectTo(this);
    }
}
1
2
3
4
5
6
7
8
9
10
看到这里,是不是觉得Dagger2还比较有意思。更有意思的在后面。当然也越来越绕了,但是你得兴奋起来,精髓啊。

6、Component依赖Component,使用dependence
这里我们以Activity和Fragment为例。假设我们再Activity依赖注入Human类,此时在Fragment里使用
先看定义Module,和之前一样,没什么区别

@Module
public class FstudyActivityModule {
    @Provides
    Human providesHuman() {
        return new Human();
    }
}
1
2
3
4
5
6
7

再建ActivityComponent当然这里,你也可以加上void inject(FstudyActivity fstudyActivity)。重要一点是我们要把依赖注入的类返回出去,定义方法provideHuman,因为是Component依赖Component。所以也能理解

@Component(modules = FstudyActivityModule.class)
public interface FstudyActivityComponent {
    Human provideHuman();
}
1
2
3
4

再使用dependencies建FragmentComponent暂且可以理解为子Component,因为后面真的有子Component。dependencies = FstudyActivityComponent.class写上我们的父Component。后面是注入到Fragment里

@Component(dependencies = FstudyActivityComponent.class)
public interface TestFragmentComponent {
    void inject(TestFragment testFragment);
}
1
2
3
4

在Activity里,要先生成ActivityComponent,然后提供个方法,把父Component提供给Fragment

public class FstudyActivity extends BaseActivity {
    private FstudyActivityComponent fstudyActivityComponent;
    @Override
    protected void processLogic() {
        fstudyActivityComponent = DaggerFstudyActivityComponent.create();
    }

public FstudyActivityComponent getFstudyActivityComponent() {
        return fstudyActivityComponent;
    }
}
1
2
3
4
5
6
7
8
9
10
11

在Fragment里

public class TestFragment extends BaseFragment {
    @Inject
    Human human;
    @Override
    protected void processLogic(Bundle savedInstanceState) {
        FstudyActivityComponent fstudyActivityComponent = ((FstudyActivity) getActivity()).getFstudyActivityComponent();
        DaggerTestFragmentComponent.builder()
                .fstudyActivityComponent(fstudyActivityComponent)
                .build().inject(this);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
好了,在fragment可以使用human了。在java里使用,确实很绕,代码多的让你难以接受。建议先理解,后面出的一篇在Android中使用,你会很爽。

大致理解和总结为( 看懂请略过 ):
1、假设我们用Human注入,这里的FstudyActivityModule 和之前的Module一样,正常
2、FstudyActivityComponent把要注入的类返回
3、在TestFragment方面,我们新建一个TestFragmentComponent 依赖 FstudyActivityComponent;
4、在FstudyActivity自定义一个方法把FstudyActivityComponent提供出去,供TestFragment使用
5、在TestFragment,注册下就OK了。很绕,个人建议先明白这个流程就好了

7、Component依赖Component,使用@subComponent(这个和 【标题6】 实现的是同一个效果)
虽然是实现同一个效果,但是方式不同,目的是让你更多了解Dagger2。同样以上面的例子。Module和上面一样不变(我这里是为了Demo区域化,虽然类名不同,但是内容是一致的)
首先建子Component,FragmenComponent,用@Subcomponent标注,并注入我们的Fragment里。为什么先建子Component呢。因为子Component要在父Component返回,绕不绕!!

@Subcomponent
public interface DemoFragmentComponent {
    void inject(DemoFragment demoFragment);
}
1
2
3
4

然后是父Component,ActivityComponent,父Component一切正常,返回值是子Component

@Component(modules = GstudyActivityModule.class)
public interface GstudyActivityComponent {
    DemoFragmentComponent demoFragmentComponent();
}
1
2
3
4

在Activity里的操作一样,初始化我们的父Component,并提供方法,返回父Component,供Fragment使用。

然后是Fragment里

public class DemoFragment extends BaseFragment {
    @Inject
    Human human;
    @Override
    protected void processLogic(Bundle savedInstanceState) {
        GstudyActivityComponent gstudyActivityComponent = ((GstudyActivity) getActivity()).getGstudyActivityComponent();
        gstudyActivityComponent
            .demoFragmentComponent()
            .inject(this);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
这样就成功了,可以在Fragment使用human了。看明白了标题6,其实标题7原理是一样的。

大致理解和总结为( 看懂请略过 ):
1、先建一个子类Component,用@subComponent标注,DemoFragmentComponent
2、然后建父类Component: GstudyActivityComponent,定义个方法,返回子类Component。
3、在GstudyActivity自定义一个方法把GstudyActivityComponent提供出去,供DemoFragment使用
4、在DemoFragment,注册下,就好了。大致和dependencies类似
注意:但注册的时候写法不同,之前是通过子Component传入父Component;而这里是从父Component中获取子Component,然后直接inject

8、Component依赖Component,使用 @Subcomponent.Builder(和【标题6】&【标题7】实现的是一样的效果)
效果一样,方式不同,目的还是更了解Dagger2。可以看到这里的标注是@Subcomponent.Builder。所以和使用@Subcomponent类似。

好了,还是以上面的例子为例。这里需要改的是父Component和子Componet。这个时候我们不免想到@Component.Builder的用法。是不是一样呢。这个时候我只能说类似,但是又不一样。毕竟这里多了个sub。我们先看下@Component.Builder的用法,拷贝之前代码(不知道再去回顾下【标题4】)

@Component(modules = CstudyModule.class。)
public interface DstudyActivityComponent {
    void injectTo(DstudyActivity dstudyActivity);

@Component.Builder
    interface Builder {
        Builder cstudyModule(CstudyModule cstudyModule);
        DstudyActivityComponent build();
    }
}
1
2
3
4
5
6
7
8
9
10

我们按照找个方式去写@Subcomponent.Builder。,@Subcomponent.Builder要使用肯定是在@Subcomponent下,毋庸置疑。首先发现没有modules = CstudyModule.class。被@Subcomponent取代了。没有Module我们就使用无参

@Subcomponent
public interface OtherFragmentComponent {
    void inject(OtherFragment otherFragment);
    @Subcomponent.Builder
    interface Builder {
        Builder noModule();    
        OtherFragmentComponent build();
    }
}
1
2
3
4
5
6
7
8
9
我先告诉告诉你运行结果把,运行结果报错了。

@Subcomponent.Builder types must have exactly one zero-arg method,
and that method must return the @Subcomponent type. Already found: hstudyActivityModule()

报错信息如下:意思是不需要Build返回值方法,通过Already found: hstudyActivityModule()知道,已经发现了我们的Module。我们再想想这个标注的名称sub,不就是子Component继承父Componet吗。而且Dagger2内部已经默认了,所以这里没有Builder返回值方法。所以正确的子Component

@Subcomponent
public interface OtherFragmentComponent {
    void inject(OtherFragment otherFragment);
    @Subcomponent.Builder
    interface Builder {
        OtherFragmentComponent build();
    }
}
1
2
3
4
5
6
7
8

接下来是父Component,返回值当然是我们的Builder。

@Component(modules = HstudyActivityModule.class)
public interface HstudyActivityComponent {
    OtherFragmentComponent.Builder sonbuilder();
}
1
2
3
4
有人就疑惑了不可以返回子Component吗。我们假如此时返回子Component,我先告诉你运行报错,信息如下:

Components may not have factory methods for subcomponents that define a builder.

大概意思是:用了@Subcomponent.Builder的话,Component没有工厂模式方法去创建我们的子Component。好了,就这样,请原谅我的英语四级!!

Activity还是和之前一样,初始化我们的父Component,并通过方法返回。Fragment里使用依赖如下

public class OtherFragment extends BaseFragment {
    @Inject
    Human human;
    @Override
    protected void processLogic(Bundle savedInstanceState) {
        HstudyActivityComponent hstudyActivityComponent = ((HstudyActivity) getActivity()).getHstudyActivityComponent();
        hstudyActivityComponent.
                sonbuilder().build().inject(this);
    }

}
1
2
3
4
5
6
7
8
9
10
11
好了,绕来绕去,功能实现了!看到这里对Dagger2大致了解了吧。

由于在java里使用比较多。临时决定java分2篇。不然博客太长,也没人看。
本文github Demo地址
————————————————
版权声明:本文为CSDN博主「岩浆李的游鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/leol_2/article/details/100546108

google四件套之Dagger2相关推荐

  1. google官方mvp+dagger2架构详解

    原文链接:http://www.jianshu.com/p/01d3c014b0b1 1 前言 前段时间分享了一篇文章:google官方架构MVP解析与实战 ,针对这是对google官方示例架构的一个 ...

  2. Google官方MVP+Dagger2架构 dagger2详解

    前言: dagger2是大家项目中使用比较频繁的一个google开源框架,它旨在解决Android 中的依赖注入, 降低层级之间的耦合性,简化了我们在项目中对象实例化操作: dagger2 在Andr ...

  3. Dagger2 使用详解

    简书地址 个人博客地址http://zpayh.xyz/ 前言 Dagger2 是一款使用在Java和Android上的依赖注入的一个类库. 配置信息 使用Android Studio 创建一个新的项 ...

  4. Android Jetpack组件之Hilt使用

    前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. And ...

  5. 从零开始搭建Android框架系列

    原文链接:http://www.jianshu.com/nb/3767449 开篇介绍和工程目录结构[从零开始搭建android框架系列(1)] 不容错过,最全的安卓架构合集[从零开始搭建androi ...

  6. Android开发架构规范

    前言 在开发中,一个良好的开发习惯以及一个开发规范可能会让你少走很多弯路,也会一定程度上的提高代码的可读性,可维护性和可拓展性.当随着需求的不断变更,需要维护项目的时候.当随着项目的代码量的提升,需要 ...

  7. android 开发规范

    前言 在开发中,一个良好的开发习惯以及一个开发规范可能会让你少走很多弯路,也会一定程度上的提高代码的可读性,可维护性和可拓展性.当随着需求的不断变更,需要维护项目的时候.当随着项目的代码量的提升,需要 ...

  8. Android 优秀文章收集整理集合

    转载 自    https://github.com/jiang111/awesome-android-tips 记录自己遇到的比较有价值的Android相关的blog MaHua是online md ...

  9. Android基础知识梳理

    文章目录 系统架构 应用层 应用框架层 系统运行库层 硬件抽象层(HAL) Linux内核层 补充 通信方式 Binder IPC原理 Binder原理 Socket handler 主线程中 子线程 ...

最新文章

  1. 爬虫之selenium替换user-agent
  2. python中的1怎么用的_python中的[:-1]和[::-1]的具体使用
  3. 【Notes7】Samba/NFS服务器,Ntp,导出log,modprobe,进入ME刷bios,树莓派
  4. 手机只能签荣耀!最忠诚代言人胡歌喊你去天猫超品日
  5. 消息中间件Rabbitmq核心概念讲解
  6. java代码发送http请求时DnsResolver使用问题
  7. git修改commit注释_【Slog】Git之多人同feature的同分支开发
  8. VMware visio制图形状大全
  9. AHCI和IDE区别,和在目前系统中设置 AHCI - 摘自网络
  10. (原创)[短小精悍系列]为什么蓝色光比红色光看起来更刺眼?而日常生活中反而说绿色和蓝色更柔和?
  11. 『危机领导力』告诉我们如何带好团队
  12. php 继承 父类使用子类,在PHP中使用 来 实现子类和父类之间的继承 。
  13. 网赚项目分享:八条可以在线上做的副业兼职
  14. sap 新手入门第一课(新装SAP IDES必须做的操作)
  15. 内存,外存,运存,显存,闪存,硬盘,SSD等概念
  16. 【Flask】 Not Found: /favicon.ico 项目logo图标加载
  17. python可以实现的功能_Python功能点实现:数据热更新
  18. oracle ORA-28000: the account is locked 28000. 00000
  19. 运放-单电源运放和双电源运放
  20. 数据中台方向创业者上海小胖的采访记

热门文章

  1. java实验原理和图例_图例解析JDK,JRE,JVM概念及使用
  2. ironpython使用dictionary_在C#环境中动态调用IronPython脚本(一)
  3. Nginx+Tomcat负载均衡、动静分离集群
  4. c语言补全 subilme_Sublime Text3 C语言插件
  5. docker容器简介及安装
  6. python笔记之function函数
  7. Java Socket重要参数讲解
  8. php转字,php汉字如何转数字
  9. java与jquery的选择器区别_java day44【JQuery 基础:概念,快速入门,JQuery对象和JS对象区别与转换,选择器,DOM操作,案例】...
  10. vs最好的版本_Win10 环境下,LightGBM GPU 版本的安装