如何应对 Android 面试官 -> 玩转 JetPack Hilt

前言


在这里插入图片描述
本章我们来学习 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 方法中进行注入;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值