官方App 架构指南的学习到实践

       作为一名安卓应用开发者来说,构建出稳定、高效、合理、易维护的安卓App一直都是我们追求的目标。对此老大哥谷歌给我们做出了很好的指导。废话不多说,先来了解一下官方对构建一个高质量的APP提出的建议。

  1. 整体框架图

    可以看出采用的是MVVM的构架模式,Repository及其分支是数据中心(Model),Activity/Fragment是(View),ViewModel就是图中的ViewModel。数据中心采用Retrofit进行网络请求,Room来操作数据库做数据持久化。而ViewModel采用具有生命感知力的LiveData来刷新View层。每个模块分工明确、各施其职,符合官方提出的两大原则:1.关注点分离;2.持久化的Model驱动UI。所以我想我们的项目目录结构应该是这样的
     

    我多了一个di目录这是Dagger2依赖注入,我是一个爱动手不爱说话的人,接下来我会记录下我是怎么一步一步实现这个框架的。

  2. 实战操作
  3. 在项目中新建一个versions.gradle文件用来管理用到的Java库,内容如下

  4. /**
     * Shared file between builds so that they can all use the same dependencies and
     * maven repositories.
     **/
    ext.deps = [:]
    def versions = [:]
    versions.arch_core = "2.0.0"
    versions.room = "2.1.0-alpha03"
    versions.lifecycle = "2.0.0"
    versions.support = "1.0.0"
    versions.dagger = "2.16"
    versions.junit = "4.12"
    versions.espresso = "3.1.0-alpha4"
    versions.retrofit = "2.3.0"
    versions.okhttp_logging_interceptor = "3.9.0"
    versions.mockwebserver = "3.8.1"
    versions.apache_commons = "2.5"
    versions.mockito = "2.7.19"
    versions.mockito_all = "1.10.19"
    versions.mockito_android = "2.22.0"
    versions.dexmaker = "2.2.0"
    versions.constraint_layout = "2.0.0-alpha2"
    versions.glide = "4.8.0"
    versions.timber = "4.5.1"
    versions.android_gradle_plugin = '3.3.2'
    versions.rxjava2 = "2.1.3"
    versions.rx_android = "2.0.1"
    versions.atsl_runner = "1.1.0-alpha4"
    versions.atsl_rules = "1.1.0-alpha4"
    versions.hamcrest = "1.3"
    versions.kotlin = "1.3.0"
    versions.paging = "2.1.0-rc01"
    versions.navigation = "1.0.0-alpha08"
    versions.work = "1.0.0-alpha12"
    def deps = [:]
    
    def support = [:]
    support.annotations = "androidx.annotation:annotation:$versions.support"
    support.app_compat = "androidx.appcompat:appcompat:$versions.support"
    support.recyclerview = "androidx.recyclerview:recyclerview:$versions.support"
    support.cardview = "androidx.cardview:cardview:$versions.support"
    support.design = "com.google.android.material:material:$versions.support"
    support.v4 = "androidx.legacy:legacy-support-v4:$versions.support"
    support.core_utils = "androidx.legacy:legacy-support-core-utils:$versions.support"
    deps.support = support
    
    def room = [:]
    room.runtime = "androidx.room:room-runtime:$versions.room"
    room.compiler = "androidx.room:room-compiler:$versions.room"
    room.rxjava2 = "androidx.room:room-rxjava2:$versions.room"
    room.testing = "androidx.room:room-testing:$versions.room"
    deps.room = room
    
    def lifecycle = [:]
    lifecycle.runtime = "androidx.lifecycle:lifecycle-runtime:$versions.lifecycle"
    lifecycle.extensions = "androidx.lifecycle:lifecycle-extensions:$versions.lifecycle"
    lifecycle.java8 = "androidx.lifecycle:lifecycle-common-java8:$versions.lifecycle"
    lifecycle.compiler = "androidx.lifecycle:lifecycle-compiler:$versions.lifecycle"
    deps.lifecycle = lifecycle
    
    def arch_core = [:]
    arch_core.testing = "androidx.arch.core:core-testing:$versions.arch_core"
    deps.arch_core = arch_core
    
    def retrofit = [:]
    retrofit.runtime = "com.squareup.retrofit2:retrofit:$versions.retrofit"
    retrofit.gson = "com.squareup.retrofit2:converter-gson:$versions.retrofit"
    retrofit.mock = "com.squareup.retrofit2:retrofit-mock:$versions.retrofit"
    deps.retrofit = retrofit
    deps.okhttp_logging_interceptor = "com.squareup.okhttp3:logging-interceptor:${versions.okhttp_logging_interceptor}"
    
    def dagger = [:]
    dagger.runtime = "com.google.dagger:dagger:$versions.dagger"
    dagger.android = "com.google.dagger:dagger-android:$versions.dagger"
    dagger.android_support = "com.google.dagger:dagger-android-support:$versions.dagger"
    dagger.compiler = "com.google.dagger:dagger-compiler:$versions.dagger"
    dagger.android_support_compiler = "com.google.dagger:dagger-android-processor:$versions.dagger"
    
    deps.dagger = dagger
    
    def espresso = [:]
    espresso.core = "androidx.test.espresso:espresso-core:$versions.espresso"
    espresso.contrib = "androidx.test.espresso:espresso-contrib:$versions.espresso"
    espresso.intents = "androidx.test.espresso:espresso-intents:$versions.espresso"
    deps.espresso = espresso
    
    def atsl = [:]
    atsl.runner = "androidx.test:runner:$versions.atsl_runner"
    atsl.rules = "androidx.test:rules:$versions.atsl_runner"
    deps.atsl = atsl
    
    def mockito = [:]
    mockito.core = "org.mockito:mockito-core:$versions.mockito"
    mockito.all = "org.mockito:mockito-all:$versions.mockito_all"
    mockito.android = "org.mockito:mockito-android:$versions.mockito_android"
    deps.mockito = mockito
    
    def kotlin = [:]
    kotlin.stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin"
    kotlin.test = "org.jetbrains.kotlin:kotlin-test-junit:$versions.kotlin"
    kotlin.plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"
    kotlin.allopen = "org.jetbrains.kotlin:kotlin-allopen:$versions.kotlin"
    
    deps.kotlin = kotlin
    
    deps.paging_ktx = "androidx.paging:paging-runtime-ktx:$versions.paging"
    
    def glide = [:]
    glide.runtime = "com.github.bumptech.glide:glide:$versions.glide"
    glide.compiler = "com.github.bumptech.glide:compiler:$versions.glide"
    deps.glide = glide
    deps.dexmaker = "com.linkedin.dexmaker:dexmaker-mockito:$versions.dexmaker"
    deps.constraint_layout = "androidx.constraintlayout:constraintlayout:$versions.constraint_layout"
    deps.timber = "com.jakewharton.timber:timber:$versions.timber"
    deps.junit = "junit:junit:$versions.junit"
    deps.mock_web_server = "com.squareup.okhttp3:mockwebserver:$versions.mockwebserver"
    deps.rxjava2 = "io.reactivex.rxjava2:rxjava:$versions.rxjava2"
    deps.rx_android = "io.reactivex.rxjava2:rxandroid:$versions.rx_android"
    deps.hamcrest = "org.hamcrest:hamcrest-all:$versions.hamcrest"
    deps.android_gradle_plugin = "com.android.tools.build:gradle:$versions.android_gradle_plugin"
    ext.deps = deps
    
    def build_versions = [:]
    build_versions.min_sdk = 23
    build_versions.target_sdk = 28
    build_versions.build_tools = "28.0.3"
    ext.build_versions = build_versions
    
    def work = [:]
    work.runtime = "android.arch.work:work-runtime:$versions.work"
    work.testing = "android.arch.work:work-testing:$versions.work"
    work.firebase = "android.arch.work:work-firebase:$versions.work"
    work.runtime_ktx = "android.arch.work:work-runtime-ktx:$versions.work"
    deps.work = work
    
    def navigation = [:]
    navigation.runtime = "android.arch.navigation:navigation-runtime:$versions.navigation"
    navigation.runtime_ktx = "android.arch.navigation:navigation-runtime-ktx:$versions.navigation"
    navigation.fragment = "android.arch.navigation:navigation-fragment:$versions.navigation"
    navigation.fragment_ktx = "android.arch.navigation:navigation-fragment-ktx:$versions.navigation"
    navigation.safe_args_plugin = "android.arch.navigation:navigation-safe-args-gradle-plugin:$versions.navigation"
    navigation.testing = "android.arch.navigation:navigation-testing:$versions.navigation"
    deps.navigation = navigation
    
    ext.deps = deps
    
    def addRepos(RepositoryHandler handler) {
        handler.google()
        handler.jcenter()
        handler.maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
    }
    ext.addRepos = this.&addRepos
    

    这里包含了几乎所有的谷歌开发库,都是亲儿子,不管用没用到直接复制过去就好了。

  5. 然后在build.gradle文件(project下的)写入以下内容

  6. /*
     * Copyright (c) 2018. 代码著作权归卢声波所有。
     */
    
    // Top-level build file where you can add configuration options common to all sub-projects/modules.
    
    buildscript {
        apply from: 'versions.gradle'
        addRepos(repositories)
        dependencies {
            classpath deps.android_gradle_plugin
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
        repositories {
            google()
            jcenter()
        }
    }
    
    allprojects {
        addRepos(repositories)
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    在App下的build.gradle文件中写入以下内容

  7. apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion build_versions.target_sdk
        buildToolsVersion build_versions.build_tools
        defaultConfig {
            applicationId "com.jinkan.www.fastandroid"
            minSdkVersion 15
            targetSdkVersion 27
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
        dataBinding {
            enabled = true
        }
        compileOptions {
            targetCompatibility 1.8
            sourceCompatibility 1.8
        }
        buildTypes {
            release {
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
    
    
        // Support libraries
        implementation deps.support.app_compat
        implementation deps.support.v4
        implementation deps.support.design
        implementation deps.support.cardview
        implementation deps.constraint_layout
    
        // Architecture components
        implementation deps.lifecycle.runtime
        implementation deps.lifecycle.extensions
        annotationProcessor deps.lifecycle.compiler
        implementation deps.room.runtime
        implementation deps.paging_ktx
        annotationProcessor deps.room.compiler
        // Android Testing Support Library's runner and rules
        androidTestImplementation deps.atsl.runner
        androidTestImplementation deps.atsl.rules
        androidTestImplementation deps.room.testing
        androidTestImplementation deps.arch_core.testing
        //dagger2
        implementation deps.dagger.runtime
        implementation deps.dagger.android
        implementation deps.dagger.android_support
        annotationProcessor deps.dagger.android_support_compiler
        annotationProcessor deps.dagger.compiler
        //Retrofit2
        implementation deps.retrofit.runtime
        implementation deps.retrofit.gson
        implementation deps.okhttp_logging_interceptor
        // Espresso UI Testing
        androidTestImplementation deps.espresso.core
        androidTestImplementation deps.espresso.contrib
        androidTestImplementation deps.espresso.intents
    
        // Resolve conflicts between main and test APK:
        androidTestImplementation deps.support.annotations
        androidTestImplementation deps.support.v4
        androidTestImplementation deps.support.app_compat
        androidTestImplementation deps.support.design
    
        implementation fileTree(include: ['*.jar'], dir: 'libs')
    }
    

    其中 dataBinding {  enabled = true  }启用了dataBingding,使得ViewModel和View层的交互更加便捷高效。

  8. 接下来创建项目目录如图2,为了较少重复代码,一些基本的封装是必要的,下面就介绍我封装的一些基类。如果要使用dagger2来注入依赖的话,需要做一下封装方便使用。说明:为了文理的流畅性,这里只是简单的走下流程,我用到的组件都会写一篇相关的文章的,敬请期待。想了解细节的童鞋去这里看看:https://blog.csdn.net/qq_17766199/article/details/73030696

  9.  

  10. 这里我写了四个类,第一个ActivityBindingModule的作用是指明要把什么(依赖)注入到哪个Activity的,代码如下:

  11. package com.jinkan.www.fastandroid.di;
    
    import com.jinkan.www.fastandroid.view.MainActivity;
    
    import dagger.Module;
    import dagger.android.ContributesAndroidInjector;
    
    /**
     * We want Dagger.Android to create a Subcomponent which has a parent Component of whichever module ActivityBindingModule is on,
     * in our case that will be AppComponent. The beautiful part about this setup is that you never need to tell AppComponent that it is going to have all these subcomponents
     * nor do you need to tell these subcomponents that AppComponent exists.
     * We are also telling Dagger.Android that this generated SubComponent needs to include the specified modules and be aware of a scope annotation @ActivityScoped
     * When Dagger.Android annotation processor runs it will create 4 subcomponents for us.
     */
    @Module
    public abstract class ActivityBindingModule {
        @ActivityScoped
        @ContributesAndroidInjector()
        abstract MainActivity mainActivity();
    }
    

    第二个类ActivityScoped是一个注解类,指明依赖注入的作用范围。

  12. package com.jinkan.www.fastandroid.di;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    import javax.inject.Scope;
    
    /**
     * In Dagger, an unscoped component cannot depend on a scoped component. As
     * {@link AppComponent} is a scoped component {@code @Singleton}, we create a custom
     * scope to be used by all fragment components. Additionally, a component with a specific scope
     * cannot have a sub component with the same scope.
     */
    @Documented
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ActivityScoped {
    }
    

    这里指明是单例的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值