辅助android开发者搭建基于JetPack组件构建MVVM框架的注解处理框架。通过注解自动生成ViewModel的Factory类、lazy方法等;支持在项目的任意位置注入ROOM的dao层接口与Retrofit库中的api接口。
github
android开发者可以将brick理解为一个轻量级的注入框架,使用非常简单,使用4-6个注解即可工作。brick主要在编译期工作, 不会在App运行时产生任何额外的性能消耗 ,并且只有1个注解库会打包到你的android工程中,不用担心体积增大的问题。
使用androidx而非androidxsupport库。
使用JetPack的ViewModel组件。
使用Retrofit作为网络请求库。
使用ROOM数据库框架。
(可选) 服务端为多端口、多IP的项目。(可选)
在你的android工程的根目录下的build.gradle文件中的适当的位置添加以下代码:
buildscript {
...
ext {
brick_version = '0.2.0'
}
repositories {
...
maven { url 'https://jitpack.io' }
}
dependencies {
classpath "com.gitee.numeron.brick:plugin:$brick_version"
}
}
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
在你的android工程中要启用brick的android模块的build.gradle文件中的适当位置添加以下代码:
...
apply plugin: 'kotlin-kapt'
apply plugin: 'brick'
...
dependencies {
...
implementation "com.gitee.numeron.brick:annotation:$brick_version"
kapt "com.gitee.numeron.brick:compiler:$brick_version"
}
...
在你编写好的ViewModel子类上添加@Provide注解
@Provide
class WxAuthorViewModel: ViewModel() {
...
}
- 在Terminal终端上输入gradlew :[ModuleName]:kaptDebugKotlin运行脚本;
- 在AndroidStudio右侧Gradle扩展栏中依次找到[PrjectName] -> [ModuneName] -> Tasks -> other -> kaptDebugKotlin并双击运行脚本;
- Ctrl + F9编译整个项目。
以上三种方式任选其一即可运行brick注解处理器。
. lazyWxAuthorViewModel()扩展方法,在Activity或Fragment中直接调用即可。
. get()方法,在不方便使用lazy方法时,可使用此方法获取ViewModel的实例。
注:lazyWxAuthorViewModel方法就是对get()方法的包装。
直接使用生成的方法,即可创建对应的ViewModel实例:
private val wxAuthorViewModel by lazyWxAuthorViewModel()
或在onCreate()之后,通过get创建:
private lateinit var wxAuthorViewModel: WxAuthorViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
wxAuthorViewModel = get(this)
}
-2. (必需) 在获取Retrofit实例的方法上添加@RetrofitInstance,如:
@RetrofitInstance
val retrofit: Retrofit by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
Retrofit.Builder()
.client(okHttpClient)
.baseUrl(WANDROID_BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
val okHttpClient: OkHttpClient by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
val logInterceptor = HttpLoggingInterceptor()
logInterceptor.level = HttpLoggingInterceptor.Level.BODY
OkHttpClient.Builder()
.addInterceptor(logInterceptor)
.callTimeout(15, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.connectTimeout(15, TimeUnit.SECONDS)
.build()
}
注:@RetrofitInstance注解只能标记在public修饰的val属性上或方法上,val属性上或方法可以在object 单例或companion object中,也可以是包级属性/方法。
-1. (可选) 在获取RoomDatabase实例的属性或方法上标记@RoomInstance,如:
@RoomInstance
val wandroidDatabase: WandroidDatabase by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
Room.databaseBuilder(CONTEXT, WandroidDatabase::class.java, "wandroid.db")
.build()
}
注:@RoomInstance注解只能标记在public修饰的val属性上或方法上,val属性上或方法可以在object 单例或companion object中,也可以是包级属性/方法。
interface WxAuthorApi {
@GET("wxarticle/chapters/json ")
suspend fun getWxAuthorList(): List
}
class WxAuthorRepo {
...
}
class WxAuthorRepo {
@Inject
lateinit var wxAuthorApi: WxAuthorApi
}
@Provide
class WxAuthorViewModel: ViewModel() {
@Inject
private lateinit var wxAuthorRepo: WxAuthorRepo
}
标记后,继续编写业务代码即可,所有被@Inject标记的字段,都会在编译期自动获取或创建实例,无需担心它们在何时被赋值。
注:虽然是lateinit var修饰的字段,但是不要尝试为它们赋值,这会导致致命的错误。
注:@Inject可以注入的类型只有Retrofit的api接口和ROOM的dao接口、以及有无参构造的类。
假设有另一个Retrofit api接口,它的访问地址或端口与baseUrl中的不一样,此时,可以在Retrofit的api接口上添加@Port和@Url注解来设置它们的url或port。
@Port的使用:
@Port(1080)
interface ArticleApi {
@GET("wxarticle/list/{chapterId}/{page}/json")
suspend fun getArticleList(@Path("chapterId") chapterId: Int, @Path("page") page: Int): Paged
}
添加此注解后,brick会在编译期根据@RetrofitInstance注解标记的Retrofit实例和@Port的端口号,重新创建一个Retrofit实例,并使用新的Retrofit实例创建ArticleApi的实例。
@Url的使用:
@Url("http://www.wanandroid.com:1080/")
interface ArticleApi {
@GET("wxarticle/list/{chapterId}/{page}/json")
suspend fun getArticleList(@Path("chapterId") chapterId: Int, @Path("page") page: Int): Paged
}
与@Port的使用基本一致,实现的原理也是一样的。
生成的WxAuthorViewModels.kt文件:
//kotlin 扩展方法,在Activity/Fragment中通过by调用
fun ViewModelStoreOwner.lazyWxAuthorViewModel(): Lazy =
LazyWeChatAuthorViewModel(this)
//包级方法,在Activity/Fragment的onCreate方法之后调用
fun get(owner: ViewModelStoreOwner): WxAuthorViewModel {
val factory = WxAuthorViewModelFactory()
return ViewModelProvider(owner, factory).get(WxAuthorViewModel::class.java)
}
private class WxAuthorViewModelFactory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun create(clazz: Class): VM = WxAuthorViewModel() as VM
}
private class LazyWxAuthorViewModel(
private val owner: ViewModelStoreOwner
) : Lazy {
private var _value: WxAuthorViewModel? = null
override val value: WxAuthorViewModel
get() {
if(_value == null) {
_value = get(owner)
}
return _value!!
}
override fun isInitialized(): Boolean = _value != null
}
反编译后的WxAuthorViewModel.class:
class WxAuthorViewModel extends ViewModel {
private final WxAuthorRepo wxAuthorRepo = new WxAuthorRepo();
}
反编译后的WxAuthorRepo.class:
class WxAuthorRepo {
private final WxAuthorApi wxAuthorApi = RuntimeKt.getRetrofit().create(WxAuthorApi.class);
public final WxAuthorApi getWxAuthorApi() {
...
return wxAuthorApi;
}
}
WxAuthorApi添加@Port注解后的WxAuthorRepo.class:
class WxAuthorRepo {
private final WxAuthorApi wxAuthorApi = newRetrofit(RuntimeKt.getRetrofit(), 1080, null).create(WxAuthorApi.class);
public final WxAuthorApi getWxAuthorApi() {
...
return wxAuthorApi;
}
private final Retrofit newRetrofit(Retrofit retrofit, int port, String url) {
if (port > 0) {
HttpUrl httpUrl = retrofit.baseUrl().newBuilder().port(port).build();
return retrofit.newBuilder().baseUrl(httpUrl).build();
} else if(url != null && url.length() != 0) {
return retrofit.newBuilder().baseUrl(url).build();
}
return retrofit;
}
}
通过反编译class后的代码以及整篇文章后可以得出一个大概的结论:brick就是在java编译成class后,class编译成dex之前,对class的字节码进行修改,给@Inject标记的字段赋值,实现的注入框架。
目前对ViewModel的注入还需要手动调用生成的方法来初始化,这在编译代码之前,AS上都会有红色的错误标记,接下来,让@Inject支持ViewModel的创建就是主要工作啦,等完成后,再发文章吧。