前言
本章我们来学习 Hilt 的相关使用和原理;
我们在实际的开发中搭建工程架构的时候,通常都是基于当下最新的框架进行业务封装,例如我们在进行网络请求的封装的时候,早期我们基于 Volley 进行封装,后来我们基于 xUtils 进行封装,当 OKHttp 横空出世的时候,我们又基于 OKHttp 进行封装,每升级一次框架,我们可能要面临业务的大范围改动,那么如何进行彻底的封装,从而让让我们的业务和实际的网络框架进行解耦呢?
那么 Hilt 来了~
- 采用 Hilt 反射实现隔离层架构;
Hilt 就是 Android 团队专门联系了 Dagger2 团队,一起开发了一个专门面向 Android 的依赖注入框架,相比 Dagger2,它更加简单,提供了 Android 专属 API;
Hilt 目前支持以下的 Android 类的注入
Application -> ApplicationComponent
Activity -> ActivityComponent
Fragment -> FragemntComponent
Service -> ServiceComponent
ViewModel -> ActivityRetainedComponent
View -> ViewComponent
带有 @WithFragmentBinds 注释的 View -> ViewWithFragmentComponent
Hilt 的基本原理: APT(注解处理器) + ASM(字节码插桩)
基本配置
project 下的 build.gradle 中添加 classpath 插件配置
classpath 'com.google.dagger:hilt-android-gradle-plugin:$VERSION' // 导入 gradle-plugin
app 下的 build.gradle 中添加 hilt 依赖
apply plugin: 'dagger.hilt.android.plugin' // gradle-plugin
// hilt 依赖导入
implementation "com.google.dagger:hilt-android:$VERSION"
annotationProcessor "com.google.dagger:hilt-android-compiler:$VERSION" // 注解处理器依赖使用
基础用法
我们先来声明一个需要被注入的类
public class HttpObject {
}
接着我们来声明 Module 来对外提供这个对象
// ActivityComponent.class 能注入到Activity,不能注入到Application
// ApplicationComponent.class 能注入到Activity, 能注入到Application
@InstallIn(ActivityComponent.class) // 注入到Activity里面去
@Module
public class HttpModule {
@Provides // 暴露对象
public HttpObject getHttpObject(){
return new HttpObject();
}
}
这里要使用到三个注解 @InstallIn @Module 和 @Provides
接下来声明接收注入的 Activity
@AndroidEntryPoint // 我是被注解的 被注入的
public class MainActivity extends AppCompatActivity {
@Inject
HttpObject httpObject;
@Inject
HttpObject httpObject2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("mars", httpObject.hashCode() + "");
Log.i("mars", httpObject2.hashCode() + "");
Toast.makeText(this, httpObject.hashCode() + "", Toast.LENGTH_SHORT).show();
Toast.makeText(this, httpObject2.hashCode() + "", Toast.LENGTH_SHORT).show();
}
}
通过 @AndroidEntryPoint 来标记接收 @InstallIn(ActivityComponent.class) 的 Activity;
通过 @Inject 来接收 @Provides 暴露的对象;
Hilt 的使用必须要依赖 Application,我们来声明 Application;
// hilt 基本上都要 用 Application来辅助
@HiltAndroidApp
public class MyApplication extends Application {
}
Application上 使用 @HiltAndroidApp 标记,所有使用 Hilt 的应用都必须包含一个带有 @HiltAndroidApp 注释的 Application 类;
@HiltAndroidApp 会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类充当应用级依赖项容器;
在 Application 类中设置了 Hilt 且有了应用级组件后,Hilt 可以为带有 @AndroidEntryPoint 注释的其他 Android 类提供依赖项:
运行,就完成了最基础的使用;
Hilt - 单例声明
全局单例,我们使用 @Singleton 来标记对象是全局单例
// ActivityComponent.class 能注入到Activity,不能注入到Application
// ApplicationComponent.class 能注入到Activity, 能注入到Application
@InstallIn(ApplicationComponent.class) // 注入到Activity里面去
@Module
public class HttpModule {
@Provides // 暴露对象
@Singleton // 标记这个对象是一个全局单例对象
public HttpObject getHttpObject(){
return new HttpObject();
}
}
但是需要注意的是:使用全局单例的时候,必须要使用 @InstallIn(ApplicationComponent.class) 注入到 Application 中,因为单例的生命周期是和 Application 对齐的;
局部单例,我们使用 @ActivityScoped 来标记对象是局部单例
// ActivityComponent.class 能注入到Activity,不能注入到Application
// ApplicationComponent.class 能注入到Activity, 能注入到Application
@InstallIn(ActivityComponent.class) // 注入到Activity里面去
@Module
public class HttpModule {
@Provides // 暴露对象
@ActivityScope // 标记这个对象是一个局部单例对象
public HttpObject getHttpObject(){
return new HttpObject();
}
}
但是需要注意的是:使用全局单例的时候,必须要使用 @InstallIn(ActivityComponent.class) 注入到 Activity 中,因为局部单例的生命周期是和 Activity 对齐的;
Hilt - 接口和实现类的注入
我们先来定义一个接口
// 接口
public interface TestInterface {
void method();
}
接下来我们来实现这个接口
public class TestClassImpl implements TestInterface {
@Inject
TestClassImpl() {}
@Override
public void method() {
Log.i("derry", "恭喜恭喜你,注入成功√");
}
}
提供 Module
@Module
@InstallIn(ActivityComponent.class) // 注入到 Activity
public abstract class TestInterfaceModule {
@Binds // 接口与实现类的注入工作
public abstract TestInterface bindTestClass(TestClassImpl testClass);
}
通过 @Binds 接口来实现接口和接口实现类的注入工作,将接口和实现类绑定到一起;
这里需要注意的是必须提供 抽象类 和 抽象方法 以及使用 @Module 注解和 @Binds 注解
注入到 Activity 中
@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
@Inject
TestInterface testInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View view) {
testInterface.method();
}
}
实战
使用 Hilt 实现三个网络框架的封装
首先我们先来定义顶层的回调接口
/**
* 顶层的回调接口 string---->json,xml,protobuff
*/
public interface ICallback {
void onSuccess(String result); // InputStream,String,Object,T
void onFailure(String e);
}
再来定义一个它的基础实现
/**
* 回调接口的一种实现
*/
public abstract class HttpCallback<Result> implements ICallback {
@Override
public void onSuccess(String result) {
// result就是网络访问第三方框架返回的字符串
// 1.得到调用者用什么样的javaBean接收数据
Class<?> clz = analysisClassInfo(this);
// 2.把String result转成对应的javaBean
Result objResult = (Result) new Gson().fromJson(result, clz);
// 3.objResult交给程序员
onSuccess(objResult);
}
public abstract void onSuccess(Result objResult);
private Class<?> analysisClassInfo(Object object) {
// getGenericSuperclass();
// 可以得到包含原始类型,参数化类型,数组,类型变量,基本数据类型
Type getType = object.getClass().getGenericSuperclass();
Type[] params = ((ParameterizedType) getType).getActualTypeArguments();
return (Class<?>) params[0]; // <>里面只有一个,所以取0号元素即可
}
@Override
public void onFailure(String e) {
}
}
接下来定义 post 和 get 请求
public interface IHttpRequest {
// post 请求接口
void post(String url, Map<String, Object> params, ICallback callback);
// get 请求接口
void get(String url, ICallback callback);
}
接下来使用 OkHttp、Volley、Xutils 来分别实现 post 和 get 请求;
public class OkHttpRequest implements IHttpRequest {
private OkHttpClient mOkHttpClient;
private Handler myHandler; // 此Handler是为了切换到主线程用的,处理成果
@Inject
public OkHttpRequest() {
mOkHttpClient = new OkHttpClient();
myHandler = new Handler();
}
@Override
public void post(String url, Map<String, Object> params, final ICallback callback) {
final RequestBody requestBody = appendBody(params);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
final String result = response.body().string();
if (response.isSuccessful()) {
myHandler.post(new Runnable() {
@Override
public void run() {
callback.onSuccess(result);
}
});
} else {
myHandler.post(new Runnable() {
@Override
public void run() {
callback.onFailure(result);
}
});
}
}
@Override
public void onFailure(Call call, IOException e) {
myHandler.post(new Runnable() {
@Override
public void run() {
callback.onFailure("onFailure");
}
});
}
});
}
@Override
public void get(String url, ICallback callback) {
Request request = new Request.Builder()
.url(url)
.get()
.build();
mOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
final String result = response.body().string();
if (response.isSuccessful()) {
myHandler.post(new Runnable() {
@Override
public void run() {
callback.onSuccess(result);
}
});
} else {
myHandler.post(new Runnable() {
@Override
public void run() {
callback.onFailure(result);
}
});
}
}
@Override
public void onFailure(Call call, IOException e) {
myHandler.post(new Runnable() {
@Override
public void run() {
callback.onFailure("onFailure");
}
});
}
});
}
private RequestBody appendBody(Map<String, Object> params) {
FormBody.Builder body = new FormBody.Builder();
if (params == null || params.isEmpty()) {
return body.build();
}
for (Map.Entry<String, Object> entry : params.entrySet()) {
body.add(entry.getKey(), entry.getValue().toString());
}
return body.build();
}
}
另外两个,自行参考 API 实现即可 XUtilsRequest 和 VolleyRequest;
接下来,我们在 Application 中初始化网络框架,来提供 Application 的级别的注入
@HiltAndroidApp
public class MyApplication extends Application {
}
因为要实现切换不同的网络框架,我们依然可以接住 Hilt 来实现,通过注解的方式来实现不同网络框架的注入,我们先来声明三个框架的注解
@BindOKHttp 注解
@Qualifier // 此注解就是为了让 Hilt 区分这个注解(做自己的逻辑处理)自己定义标识,限定符号
@Retention(RetentionPolicy.RUNTIME)
public @interface BindOkhttp {}
@BindVolley 注解
@Qualifier // 此注解就是为了让 Hilt 区分这个注解(做自己的逻辑处理) 自己定义标识,限定符号
@Retention(RetentionPolicy.RUNTIME)
public @interface BindVolley {}
@BindXUtils 注解
@Qualifier // 此注解就是为了让 Hilt 区分这个注解(做自己的逻辑处理) 自己定义标识,限定符号
@Retention(RetentionPolicy.RUNTIME)
public @interface BindXUtils {}
接下来,我们来声明 Moudle 来实现依赖注入的绑定
@Module
@InstallIn(ApplicationComponent.class) // 单例 注入到 MyApplication
public abstract class HttpRequestModule {
// @Binds 接口、实现类、绑定关系
@BindOkhttp
@Binds
@Singleton // 全局单例 必须用 @InstallIn(ApplicationComponent.class)
abstract IHttpRequest bindOkHttp(OkHttpRequest okHttpRequest);
// @Binds 接口、实现类、绑定关系
@BindVolley
@Binds
@Singleton // 全局单例 必须用 @InstallIn(ApplicationComponent.class)
abstract IHttpRequest bindVolley(VolleyRequest volleyRequest);
// @Binds 接口、实现类、绑定关系
@BindXUtils
@Binds
@Singleton // 全局单例 必须用 @InstallIn(ApplicationComponent.class)
abstract IHttpRequest bindXUtils(XUtilsRequest xUtilsRequest);
}
因为是注入到 Application 级别的,所以我们需要修改 Application;
@HiltAndroidApp
public class MyApplication extends Application {
// @BindVolley
// @BindXUtils
@BindOkhttp
@Inject
IHttpRequest iHttpRequest; // @Inject Hilt 框架就已经帮你注入进来了
// 暴露给Activity用
public IHttpRequest getHttpRequest() {
return iHttpRequest;
}
}
接下来我们来看下如何调用,我们在 Activity 中声明一个按钮,点击事件中发起网络请求
public class MainActivity extends AppCompatActivity {
IHttpRequest iHttpRequest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iHttpRequest = ((MyApplication) getApplication()).getHttpRequest();
}
public void click(View view) {
// 公网地址
String url = "https://v.juhe.cn/historyWeather/citys";
HashMap<String, Object> params = new HashMap<>();
params.put("province_id", "2");
params.put("key", "your_key");
iHttpRequest.post(url, params, new HttpCallback<ResponseData>() {
@Override
public void onSuccess(ResponseData objResult) {
Toast.makeText(MainActivity.this, objResult.toString(), Toast.LENGTH_SHORT).show();
}
});
}
}
Activity 不需要添加 @ActivityEntryPoint 因为 iHttpRequest 并没有被 @Inject 注解标记,也就是不需要注入到 Activity;
Hilt 有什么用?
看到这里的时候,可能好多人都会产生疑问了,这到底有什么用呢?那么就需要先来说下依赖注入有什么用?
什么是依赖?
假设 MainActivity 中有一个 iHttpRequest 变量,那么就表示这个 Activity 依赖这个 iHttpRequest
什么是依赖注入?
类中所依赖的对象值不关心,交给外部初始化,这就叫依赖注入;
依赖注入的好处,作用就是:自动加载;自动加载的关键好处就是:数据共享。需要共享或者可能被共享的数据,就需要依赖注入;不需要被共享的数据,是不需要被依赖注入的;
Hilt 原理
前面也说过,本质原理就是 APT + 字节码插桩 技术来实现的;我们进入 app 下的 build 下的 intermediates 下的 transforms 文件夹,这个就是 gradle transfroms 产物;
Hilt 编译之后的产物;
@AndroidEntryPoint
public class MainActivity extends Hilt_MainActivity {
@Inject
HttpObject httpObject;
@Inject
HttpObject httpObject2;
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131427356);
Log.i("mars", this.httpObject.hashCode() + "");
Log.i("mars", this.httpObject2.hashCode() + "");
Toast.makeText(this, this.httpObject.hashCode() + "", 0).show();
Toast.makeText(this, this.httpObject2.hashCode() + "", 0).show();
}
}
我们进入编译产物看下,发现 MainActivity 的继承关系变成了 Hilt_MainActivity,其他地方都和 MainActivity 一样,那么我们进入 Hilt_MainActivity 看下是怎么帮我们完成注入的;
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator")
abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManager<Object> {
@CallSuper
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// 这里调用了 inject 方法
inject();
super.onCreate(savedInstanceState);
}
}
在 onCreate 方法中调用了 inject 方法,我们进入这个方法看下
protected void inject() {
((MainActivity_GeneratedInjector) generatedComponent()).injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this));
}
最终调用了 Dagger2 的逻辑实现了注入,Hilt 通过 ASM 帮我们省略了这块;
Activity 中的变量是如何赋值的?
我们进入这个 injectMainActivity 方法看下,最终调用到 injectMainActivity3
private MainActivity injectMainActivity3(MainActivity instance) {
MainActivity_MembersInjector.injectHttpObject(instance, com.derry.hilt_baseuse.simple1.module.HttpModule_GetHttpObjectFactory.getHttpObject(httpModule));
MainActivity_MembersInjector.injectHttpObject2(instance, com.derry.hilt_baseuse.simple1.module.HttpModule_GetHttpObjectFactory.getHttpObject(httpModule));
return instance;
}
可以看到,调用了 injectHttpObject 方法,我们进入看下:
@InjectedFieldSignature("com.derry.hilt_baseuse.simple1.ui.MainActivity.httpObject")
public static void injectHttpObject(MainActivity instance, HttpObject httpObject) {
instance.httpObject = httpObject;
}
可以看到 httpObject 的赋值,而 httpObject 是从 HttpModule_GetHttpObjectFactory.getHttpObject(httpModule) 获取的,我们进入这个方法看下
public static HttpObject getHttpObject(HttpModule instance) {
return Preconditions.checkNotNull(instance.getHttpObject(), "Cannot return null from a non-@Nullable @Provides method");
}
核心就是 instance.getHttpObject() 最终调用到,我们声明的 Moudle 中的 getHttpObject 方法;
总结:通过注解处理器,解析到 @ActivityEntryPoint 之后,将 MainActivity 的继承关系替换成 Hilt_MainActivity,并通过 ASM 生成 Hilt_MainActivity 之后,在 onCreate 方法中进行注入;