一场由dagger2引发的一些思考

个人博客:haichenyi.com。感谢关注

  用了好几年的dagger2,从dagger2.android。就只有当时刚开始用dagger的时候深入的了解过,后来就再也没有深入的时候研究过。这几天又研究了一下,做个总结。

  就拿之前写的kotlin的框架来说,它用的是dagger2.android。从dagger2.android。最明显的就是,需要我们自己写的东西越来越少了,框架帮我们做的事情越来越多了,以至于,我们对这个过程越来越看不懂了。也就有了我这篇博客。

先说这嵌入过程吧。

1.依赖

    //dagger2_start
    def dagger = "2.23.2"
    implementation "com.google.dagger:dagger-android:$dagger"
    implementation "com.google.dagger:dagger-android-support:$dagger" // if you use the support libraries
    kapt "com.google.dagger:dagger-android-processor:$dagger"
    implementation "com.google.dagger:dagger:$dagger"
    kapt "com.google.dagger:dagger-compiler:$dagger"
    //dagger2_end

2.DaggerApplication

  新建类AppMoudle,并用注解@Moudle标记,暂时先不添加任何内容

@Module
class AppModule {

}

  新建接口AppComponent,并实现AndroidInjector接口,泛型先空着,添加如下代码:

@Singleton
@Component(modules = [AndroidSupportInjectionModule::class,AppModule::class])
interface AppComponent : AndroidInjector {

    @Component.Builder
    abstract class Builder : AndroidInjector.Builder()
}

  这里的MyAPP是我们下面新建的Application

  新建MyApp,继承DaggerApplication并实现它的抽象方法,添加如下代码:

class MyApp : DaggerApplication() {

    override fun applicationInjector(): AndroidInjector =
        DaggerAppComponent.builder().create(this)

    override fun onCreate() {
        super.onCreate()
    }
}

  这个时候,你的项目应该报错,因为你没有DaggerAppComponent这个类,你现在编译一遍,应该就是能生成

DaggerAppCompatActivity

  让你的BaseActivity类,继承这个DaggerAppCompatActivity

  新建ActComponent接口,并且实现AndroidInjector接口,泛型传你的BaseActivity,如下:

@Subcomponent(modules = [AndroidSupportInjectionModule::class])
interface ActComponent : AndroidInjector>> {
    @Subcomponent.Builder
    abstract class Builder : AndroidInjector.Builder>>()
}

  新建AllActivityModule类,添加如下代码:

@Module(subcomponents = [ActComponent::class])
abstract class AllActivitiesModule {
    @ContributesAndroidInjector
    internal abstract fun contributeMainActivityInjector(): MainActivity
}

  这个MainActivity是你的主页面,并且继承你的BaseActivity。

  至此,配置就完成了,你就可以像之前一样,用@Inject标记构造方法,然后,定义变量的地方用@Inject就可以直接用了。

PS: 我这里用的kotlin,kotlin参数默认是private,dagger2需要参数是public,这里需要加上JvmField注解。并且,要var类型,不能val,因为,val不能二次赋值。如下:

    @JvmField
    @Inject
    var presenter: P? = null

  我们之前用dagger2,没有用dagger2.android的时候,application和activity并不是实现的DaggerApplication,DaggerAppCompatActivity这两个,我们做了很多额外的操作,现在,我们都没有做了,为什么一样可以能运行?因为,我们之前做的额外的操作,现在都是这两个继承的类帮我们做了,可以点进去看一下源码。

引发的思考

  如标题,引发的思考是什么呢?它这个注解到底是怎么做到的呢?

问,这里为什么会分AppComponent,ActivityComponent,FragmentComponent,写一个component不好吗?

  答: 我不知道对不对,我的理解是:与整个APP生命周期同步即放在AppComponent中,与Activity生命周期同步即放在ActComponent中,
与Fragment生命周期同步即放在FragComponent中。

  从这里引申出什么问题呢?那就是生命周期,我的kotlin框架整个生命周期都有处理,框架的README也有说明。说到生命周期同步,就要说到不同步,不同步造成的结果就是内存泄漏,至于常见的什么情况下回造成内存泄漏我就不说了,想到内存泄漏,就联想到了内存,想到内存,我就想到了内存分配。这就是我想说的,内存分配。

内存分配

  说到内存分配,我们就先说三个名词:栈,堆,方法区

  1. 栈:存放变量的引用
  2. 堆:存放new的对象,堆是最占内存的
  3. 方法区:存储字节码信息(类,方法等等字节码),常量,静态变量等等。常量池在方法区中

举个栗子:String b = new String("a")String c = "a",他们内存是怎么分配的?如图:

内存分配1.png
  1. 等于号左边 String b,String c这里的b,c都是引用,所以放在栈中。栈存放引用
  2. 等于号右边,new的过程,就是在堆中创建内存的过程,这里的0x123456789就是我们常说的内存地址
  3. 常量池里面才是放字符串a

  很多面试题都会出这个问题,b==c和b.equal(a) ,他们两个的结果结果都知道,前面是false,后面是true。== 比较的是整个对象,而equals比较的是值。这里b指向的是堆内存中的地址,这个地址才是指向常量池中的字符串a,而c是直接指向常量池中的字符串a

  想多说一句就是 == 是怎么比较物理地址的呢?通过比较hashCode的值,在java中是获取不到地址的,不过Object中有个identityHashCodeNative方法,它虽然不是地址,但你可以理解为一个地址对应一个值,这个值就能过identityHashCodeNative来获取,具体怎么算了,这是个native方法我们不知道。不过没关系,只要知道一个地址对应一个值就行了,地址相同这个值就相同。而hashCode默认就是返回这个值,那么如果我们重写了hashCode,按我们的规则来写,可以达到不同的地址,hashCode的值相同,从而得到两个不同的地址用==比较是相同的。

   PS: 我们平时说的 把某某对象置为空,释放内存。这种说法是错误的。 比方说这里,b=null,首先,我们只是把某个对象的引用置为空,并不是把某个对象置为空。释放内存,是java的GC回收机制,释放内存,并不是我们释放内存。当GC扫描到,某个对象没有引用指向它了,它就会释放这个对象占用的内存。说到这里,就又想到GC回收机制与强软弱虚四大引用

GC回收和强软弱虚四大引用

强软弱虚四大引用

   其实我也不知道说啥,就是上面理解了内存分配,这里再看这几句话,应该印象更加深刻一些。

  1. 强引用:不论什么情况下,GC回收机制扫描到强引用,都不会管,如果内存不足,则抛出OOM异常
  2. 软引用(SoftReference):GC回收机制扫描到软引用时,当内存足够的时候,不会管,当内存不足的时候,则会回收对应内存
  3. 弱引用(WeakReference):不论什么情况下,只要GC回收机制扫描到弱引用,都会回收对应的内存。
  4. 虚引用(PhantomReference):顾名思义,形同虚设,前面三种都是与生命周期相关,而虚引用不会决定对象的生命周期,如果,一个对象仅持有虚引用,那这个对象就跟没有引用是一样的,不管什么时候,GC回收机制扫描到了这个对象,都有可能会回收对应的内存。为什么说是有可能呢?虚引用必须和引用队列连用,当GC回收机制扫描到一个对象,并准备回收它的时候,发现它还存在虚引用,那么,GC会把这个虚引用先加入到与它关联的引用队列中。所以,当GC发现一个对象有虚引用,并且,这个虚引用已经存在与之关联的引用队列当中了,就会回收这个对象。

   GC回收机制判断是否回收内存,都是先判断对象的引用是否存在,引用存放在栈中。

GC回收机制

   说到GC回收机制,就先聊聊JVM堆的相关知识,一说要JAVA虚拟机,那就要聊聊java是跨平台语言了,它是怎么实现跨平台的呢?

   java程序是跨平台的语言,它是怎么实现跨平台的?

  java程序依赖于JVM(java虚拟机),java程序必须运行在JVM上,JVM是用C、C++开发的,不同的平台是不同的JVM,但是,不管是什么类型的JVM都能运行java程序,这就是所谓的java跨平台。但是,不同JVM编译java程序生成的字节码是一样的,但是编译生成的机器码是不一样的,所以跨平台的是java程序,而不是编译生成的机器码。

  JVM堆,也就是我们上面那个图的堆,JVM堆分为三部分

  1. 新域:也就是年轻代,分为三部分,一部分:Eden,另两个部分是辅助空间分别是:From Space,To Space
  2. 旧域:也就是老年代
  3. 永久域:从配置角度看,永久域是独立于JVM的,大小为4M

  程序员无法手动释放内存,只能释放引用,内存释放只能由GC释放,程序员可以手动触发GC:System.gc(), 或者是当内存不够用的时候,GC会自动启动,或者是APP空闲的时候,也会启动回收机制。

  GC执行的过程:

  1. 新建的对象都首先存放在Eden中,如果对象太大,可能直接进入老年代,也就是旧域中。
  2. GC开始执行,都是从Eden或者是From Space把对象copy到To Space中,至于中间的算法,有好几种,标记算法,标记幸存次数,还有复制算法,具体怎么实现的,我不知道。当把对象移动到To Spce之后,此时的To Space变成了From Space,之前的From Space变成了To Space。然后,继续循环
  3. 循环一次次数之后,对象达到了移动到旧域的条件,就把对象移动到旧域。
  4. 最后,GC就会释放旧域的对象所占用的内存

PS:以上都是个人观点,不保证完全正确,没有漏洞。

你可能感兴趣的:(一场由dagger2引发的一些思考)