Hilt 使用方法与隔离层实现

1、Hilt

Hilt 解决了一部分 Dagger2 不好用的问题,当有多个 Component 向类中注入对象时,Dagger2 并不好用。Hilt 在 Dagger2 的基础上做出了一些改进,使得在向 Application、Activity、Fragment、View、Service 和 BroadcastReceiver 这些类中注入对象时,仅仅通过简单的注解就可以完成对象注入(不用再写这些类的 Component 了)。

1.1 基本配置

实现是通过 Gradle 插件实现的,所以在顶层的 build.gradle 中要引入 Hilt 关联的 Gradle 插件:

classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'

模块的 build.gradle 添加插件和依赖:

apply plugin: 'dagger.hilt.android.plugin'
//hilt
implementation "com.google.dagger:hilt-android:2.28-alpha"
annotationProcessor "com.google.dagger:hilt-android-compiler:2.28-alpha"
//kotlin
//katp "com.google.dagger:hilt-android-compiler:2.28-alpha"

此外还需要添加 Java8 的支持:

// 尽量支持 Java8,因为有 Lambda
compileOptions {
	sourceCompatibility JavaVersion.VERSION_1_8
	targetCompatibility JavaVersion.VERSION_1_8
}

1.2 基本使用

假如我们要提供 HttpObject 对象,还是先来个 @Module 里面用 @Provides 定义提供对象的方法:

@InstallIn(ActivityComponent.class)
@Module
public class HttpModule {

    @Provides
    public HttpObject provideHttpObject() {
        return new HttpObject();
    }
}

但是在类上要多出一个 @InstallIn 指定服务的组件,比如说 HttpObject 要被注入到一个 Activity 中,那么 @InstallIn 注解的值就是 ActivityComponent.class,ActivityComponent 是 Hilt 已经帮我们建好的,前面提到的几个安卓类都有对应的 Component 不需要我们再手动新建 Component 了:

Hilt支持的Android组件

注入到 Activity 还是用 @Inject 标记被注入对象,还需要用 @AndroidEntryPoint 标记 Activity 的类,只不过不用再调用 Dagger2 的方法了:

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {

    @Inject
    HttpObject httpObject1;

    @Inject
    HttpObject httpObject2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d("Test", "httpObject1:" + httpObject1 + ",httpObject2:" + httpObject2);
    }
}

此外使用 Hilt 需要自定义一个 Application 并用 @HiltAndroidApp 标记(只要用 Hilt 就需要给 Application 标记 @HiltAndroidApp):

@HiltAndroidApp
public class MyApplication extends Application {
}

1.2.1 单例

修改 Module,给方法上加 @Singleton 注解,将类上的 @InstallIn 注解的值修改为 ApplicationComponent.class:

/**
* 如果想要局部单例,用 ActivityComponent.class 替换 ApplicationComponent.class,
* 用 @ActivityScoped 替换 @Singleton
*/
@InstallIn(ApplicationComponent.class)
@Module
public class HttpModule {

    @Singleton
    @Provides
    public HttpObject provideHttpObject() {
        return new HttpObject();
    }
}

@Singleton 与 @InstallIn(ApplicationComponent.class) 是配对的,并且是一个全局单例。如果要局部单例,则使用 @InstallIn(ActivityComponent.class) 与 @ActivityScoped 配合。

1.3 注入接口实例

先来个接口和它的实现类:

public interface TestInterface {
    void method();
}
=========================================
public class TestInterfaceImpl implements TestInterface {

    @Inject
    public TestInterfaceImpl() {
    }

    @Override
    public void method() {
        Log.d("Test", "method: 注入成功!");
    }
}

最后来个 Module,用来给接口注入的 Module 应该是一个抽象类:

@Module
@InstallIn(ActivityComponent.class)
public abstract class TestInterfaceModule {

    // 抽象方法,返回值类型是接口类型,方法参数是接口实现类
    @Binds
    public abstract TestInterface bindTestClass(TestInterfaceImpl impl);
}

在 Activity 中使用:

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
    @Inject
    TestInterface testInterface;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        testInterface.method(); // 会执行 TestInterfaceImpl 内的方法
    }   
}

如果实现类需要参数可以在其构造方法中增加参数。

1.4 实现原理

Hilt 框架生成的代码在 build/intermediates/transforms/AndroidEntryPointTransform/debug/1/包名下面,执行入口也在这个路径下的 MainActivity 中:

@AndroidEntryPoint
public class MainActivity extends Hilt_MainActivity {
	protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
    }
}

Hilt 给 MainActivity 添加了一个父类 Hilt_MainActivity,然后在 onCreate() 中执行父类的 onCreate():

    @CallSuper
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        inject();
        super.onCreate(savedInstanceState);
    }

在这里调用 Inject() 执行注入:

  protected void inject() {
      ((MainActivity_GeneratedInjector) generatedComponent()).injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this));
  }

最后调用 MainActivity_GeneratedInjector 接口的 injectMainActivity():

    @GeneratedEntryPoint
    @InstallIn(ActivityComponent.class)
    @Generated("dagger.hilt.android.processor.internal.androidentrypoint.InjectorEntryPointGenerator")
    public interface MainActivity_GeneratedInjector {
    	void injectMainActivity(MainActivity mainActivity);
    }

看到这里就很眼熟了……(Application 也会添加一个父类继承 Hilt_Application)

2、用代理模式实现隔离层

隔离层的设计在 app 中会经常用到。以 app 使用网络框架为例,最早会使用 Volley,后续不断产生了 OkHttp、XUtil 和 Retrofit 等框架,app 在更换框架时(或者是更换同一框架的新版本、SDK 的试用版与正式版切换时),如果不做特殊处理,所有使用到网络框架的代码都要进行更新,假如有 1000 处需要更改,那将会是一个很大的工作量,而且也毫无技术含量。通过添加隔离层,可以实现更改一行代码就更换一个网络框架的“壮举”。

隔离层可以通过代理模式实现,也可以通过注入型框架实现。

先看代理模式的实现方式,代理模式类图:

隔离层示意图:

开始代码流程,先定义网络访问接口:

/**
 * 代理模式中的接口,定义访问网络的各种功能方法
 */
public interface IHttpProcessor {

    void post(String url, Map<String, Object> params, ICallback iCallback);

    // 其他网络请求,可以继续扩展...
}

网络请求方法中的回调接口以及实现类:

public interface ICallback {
    void onSuccess(String result);
    void onFailure(String e);
}
--------------------------------------------
/**
 * 将网络访问结果的原始数据转换成用户需要的数据类型并传递给用户
 *
 * @param <T> 用户需要的结果类型,是一个 JavaBean
 */
public abstract class HttpCallback<T> implements ICallback {

    /**
     * @param result 网络访问返回的原始字符串
     */
    @Override
    public void onSuccess(String result) {
        // 1.获取用户接收结果的 JavaBean 类型
        Class<?> clazz = analyseClassInfo(this);
        // 2.将 result 转换成对应的 JavaBean
        Gson gson = new Gson();
        T resultObj = (T)gson.fromJson(result, clazz);
        // 3.通过抽象方法将 resultObj 传递给用户
        onSuccess(resultObj);
    }

    public abstract void onSuccess(T resultObj);

    // 获取 object 的真实类型
    private Class<?> analyseClassInfo(Object object) {
        // 获取 object 的原始类型,以及参数化类型(注解尖括号里面的)
        Type type = object.getClass().getGenericSuperclass();
        Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
        // 因为 HttpCallback<T> 只定义了一个参数化类型,所以直接取数组第 0 位即可
        return (Class<?>) typeArguments[0];
    }

    @Override
    public void onFailure(String e) {
        // todo...
    }
}

然后是代理角色,需要持有真实网络框架对象:

/**
 * 代理角色,持有真实执行任务的对象,并将客户要执行的任务交给真实执行任务的对象
 */
public class HttpHelper implements IHttpProcessor {

    private static HttpHelper instance;
    // 真实对象先用接口表示,通过改变该对象实现框架切换
    private static IHttpProcessor delegate;

    private HttpHelper() {
    }

    public static void init(IHttpProcessor iHttpProcessor) {
        delegate = iHttpProcessor;
    }

    public static HttpHelper getInstance() {
        if (instance == null) {
            synchronized (HttpHelper.class) {
                if (instance == null) {
                    instance = new HttpHelper();
                }
            }
        }
        return instance;
    }


    @Override
    public void post(String url, Map<String, Object> params, ICallback callback) {
        // post 请求交给具体的网络框架实现
        String finalUrl = appendParams(url, params);
        delegate.post(finalUrl, params, callback);
    }
    ...
}

最后是各个网络框架,需要实现 IHttpProcessor 接口,以 OkHttp 为例:

public class OkHttpProcessor implements IHttpProcessor {

    private OkHttpClient mOkHttpClient;
    private Handler myHandler;

    public OkHttpProcessor() {
        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 onFailure(Call call, IOException e) {
                myHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        callback.onFailure("onFailure");
                    }
                });
            }

            @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);
                        }
                    });
                }
            }
        });
    }
    ...
}

其余的网络框架,Volley 和 XUtils 的实现类分别是 VolleyProcessor 和 XUtilsProcessor,我们只给出初始化这些类的代码,具体实现就不贴了:

public class VolleyProcessor implements IHttpProcessor {

    private static RequestQueue mQueue = null;

    public VolleyProcessor(Context context) {
        mQueue = Volley.newRequestQueue(context);
    }
    
    // post()...
}
------------------------------------------------------------
public class XUtilsProcessor implements IHttpProcessor {

    /**
     * 使用 XUtils 需要创建一个自定义 Application
     */
    public XUtilsProcessor(MyApplication app) {
        x.Ext.init(app);
    }
    
    // post()...
}

在 Application 中对这些框架初始化:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // 在这里更换网络框架
//        HttpHelper.init(new OkHttpProcessor());
//        HttpHelper.init(new VolleyProcessor(this));
        HttpHelper.init(new XUtilsProcessor(this));
    }
}

发送网络请求时是通过 HttpHelper 发送:

	HttpHelper.getInstance().post(url, params, new HttpCallback<ResponceData>() {
        @Override
        public void onSuccess(ResponceData objResult) {
            Toast.makeText(MainActivity.this, objResult.getResultcode(), Toast.LENGTH_SHORT).show();
        }
    });

3、使用 Hilt 实现框架切换

下面我们使用 Hilt 的注解实现与隔离层相同的效果,对上一节中的代码做出修改。

首先,用 @Inject 注解标记 IHttpProcessor 接口的三个框架实现类的构造方法,表示提供这些类的对象:

	@Inject
    public OkHttpProcessor() {
        mOkHttpClient = new OkHttpClient();
        myHandler = new Handler();
    }
-----------------------------------------------
	/**
     * 使用 Hilt 如果参数是 Application 的 Context 需要用 @ApplicationContext 标记,
     * 这样 Hilt 会自动为我们注入这个 Context 不需要我们手动处理。此外,当 Context 
     * 是 Activity、Fragment 时也有对应的注解 @ActivityContext、@FragmentContext
     */
	@Inject
    public VolleyProcessor(@ApplicationContext Context context) {
        mQueue = Volley.newRequestQueue(context);
    }
-----------------------------------------------
	/**
     * 将参数中原本的 MyApplication 先写成 Application,然后在传递
     * 它时再强转,这样就不用特意为 MyApplication 做单独处理
     */
    @Inject
    public XUtilsProcessor(Application app) {
        x.Ext.init((MyApplication) app);
    }

然后创建提供上述三个对象的 Module:

@Module
@InstallIn(ApplicationComponent.class)
public abstract class HttpProcessorModule {

    @Binds
    @Singleton
    public abstract IHttpProcessor bindOkHttp(OkHttpProcessor okHttpProcessor);

    @Binds
    @Singleton
    public abstract IHttpProcessor bindVolley(VolleyProcessor volleyProcessor);

    @Binds
    @Singleton
    public abstract IHttpProcessor bindXUtils(XUtilsProcessor xUtilsProcessor);
}

由于三个方法返回类型都是一样的,所以需要自定义限定符进行区分:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface BindOkHttp {
}
---------------------------------------
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface BindVolley {
}
---------------------------------------
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface BindXUtils {
}

然后将这三个限定符注解加在 Module 对应的方法上,注入工作就算完成了。

最后看如何使用。还是将网络框架对象放在 MyApplication 中,只不过这次不需要通过 HttpHelper 的初始化方法切换框架了,而是修改注入对象上的注解就行:

@HiltAndroidApp
public class MyApplication extends Application {

	// 现在注入的是 OkHttpProcessor,想换成其他框架更换这个注解为其他
	// 限定符即可,如 @BindVolley、@BindXUtils
	@BindOkHttp
    @Inject
    IHttpProcessor iHttpProcessor;

    public IHttpProcessor getHttpProcessor() {
        return iHttpProcessor;
    }
}

在 Activity 中发送网络请求:

@AndroidEntryPoint
public class MainActivity extends AppCompatActivity {

    private IHttpProcessor httpProcessor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        httpProcessor = ((MyApplication) getApplication()).getHttpProcessor();
    }

    /**
     * 点击按钮发送网络请求
     */
    public void click(View view) {
        // 构造 post 请求需要的地址 url 和参数 params... 
      
        httpProcessor.post(url, params, new HttpCallback<ResponceData>() {
            @Override
            public void onSuccess(ResponceData objResult) {
                Toast.makeText(MainActivity.this, objResult.getResultcode(), Toast.LENGTH_SHORT).show();
            }
        });
    }
}

这样一来,原本使用代理模式通过隔离层控制网络框架切换的方式就不需要了,只需要修改 IHttpProcessor iHttpProcessor 上的注解就可以更换网络框架,非常方便。

4、实现原理

Hilt 使用 AGP 编写了一个任务 transformClassesWithAndroidEntryPointTransformForDebug(做了字节码插桩),我们要想找到程序的入口点,需要先找到该任务的输出文件,路径在 build/intermediates/transforms/AndroidEntryPointTransform/[release/debug]/1/[Package Name] 下,能找到 MainActivity.class:

@AndroidEntryPoint
public class MainActivity extends Hilt_MainActivity {
    @Inject
    @BindOkHttp
    IHttpProcessor iHttpProcessor;

    public MainActivity() {
    }

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(2131361820);
    }

    public void click(View view) {
        ...
    }
}

注意 class 文件中让 MainActivity 继承了 Hilt_MainActivity,onCreate() 中又是先执行 super.onCreate(),所以我们去看 Hilt_MainActivity 里都做了什么:

@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator")
abstract class Hilt_MainActivity extends AppCompatActivity implements GeneratedComponentManager<Object> {
  private volatile ActivityComponentManager componentManager;

  @CallSuper
  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    inject();
    super.onCreate(savedInstanceState);
  }

  protected void inject() {
    ((MainActivity_GeneratedInjector) generatedComponent()).injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this));
  }
}

generatedComponent() 会通过 ActivityComponentManager 生成一个 ActivityComponent,然后强转成 MainActivity_GeneratedInjector 对 MainActivity 进行注入:

    private final class ActivityCImpl extends MyApplication_HiltComponents.ActivityC {
	  @Override
      public void injectMainActivity(MainActivity arg0) {
        injectMainActivity2(arg0);
      }

	  private MainActivity injectMainActivity2(MainActivity instance) {
        MainActivity_MembersInjector.injectIHttpProcessor(instance, new OkHttpProcessor());
        return instance;
      }
    }

最后从 MainActivity_GeneratedInjector 的实现类再到 MainActivity_MembersInjector 执行对 IHttpProcessor 的注入:

  @InjectedFieldSignature("com.demo.hilt.MainActivity.iHttpProcessor")
  @BindOkHttp
  public static void injectIHttpProcessor(MainActivity instance, IHttpProcessor iHttpProcessor) {
    instance.iHttpProcessor = iHttpProcessor;
  }

实际上就是 new 了一个 OkHttpProcessor 给到 MainActivity 的对应字段。

Hilt 相对 Dagger2 的主要变化在于让 Activity 都继承了 Hilt_XxxActivity,让 Application 继承 Hilt_MyApplication,并且让可以自动创建组件的那些类(Application、Activity、Fragment、View、Service 和 BroadcastReceiver)对应的组件作为 MyApplication_HiltComponents 的子组件。

最后说一嘴,如果 A 类、B 类都是自己写的,然后想将 A 注入到 B,Hilt 是不能完成这样的操作的,只能用 Dagger,注入插件不是万能的,只是方便常用功能的使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值