Dagger2-1

作者: OkCoco | 来源:发表于2017-12-03 15:38 被阅读0次

注:原文链接
  依赖注入(Dependency Injection简称DI)是在控制反转(Inversion of Control)的概念之上构建的。这表示说一个类应该从它的外部得到它的依赖。简单来说,一个类不应该实例化另外一个类(即所依赖的类),而是应该从一个配置类中得到它的实例。

  如果一个Java类通过new操作符创建了另外一个类的实例,那么该实例就不能被单独的使用和测试,这种依赖的方法称之为"强依赖"(Hard Dependency)。

  那么,从外部获取类的实例会给我们带来什么样的好处呢?最重要的优势就是增强了类的重用性和能够独立与其他类进行测试

  怎样才能做到DI呢?这可能需要我们回顾一下过去。

  一个我们称之为依赖容器的框架类通常被用于分析类的依赖关系。有了这样的分析,便能够通过反射(Reflections)来构建类的实例和将这个对象注入到定义的依赖关系里面去。这边消除了强依赖。这样也就可以单独地测试类,例如使用模拟对象。这是dagger1。

这个过程最主要的劣势有两点:

  • 反射效率低下
  • 运行时执行依赖解析,导致意外崩溃
    因此,Daggar2应运而生。

  Dagger2 最大的改变就是使用注解处理器(annotation processor)来生成依赖图。这些类提供了使用javax注解包在编译时生成的依赖关系,这有助于在应用程序运行之前进行可能的错误检查。生成的代码可读性很强。

  为了描述依赖关系的类的标准注解被定义在Java Specification Request 330 (JSR 330)

注入模式:

  • 构造器注入---注入方法参数
  • 字段注入 ---注入成员变量
  • 方法注入 ---注入方法参数

根据JSR 330的依赖注入顺序:
 1、Constructor
 2、Field
 3、Method

注:

  • 使用@Inject注解标注的方法或字段不存在顺序问题
  • 当字段或方法参数在构造方法调用之后后被注入,你不能在构造方法中使用注入的成员变量

我对于Daggar2的理解如下:
  A dependency consumer asks for the dependency(Object) from a dependency provider through a connector.
  一个依赖消费者(目标类中用@Inject注解标注的类对象)通过连接器从依赖提供者(Dependency provider)来获取依赖对象[给出原文,不太会翻译]

  • 依赖提供者:被@Module注解标注的类,负责提供被注入的类对象(即目标类所需的对象)。一些定义了被@Provides注解标注的方法的类。从这些方法返回的对象就是用来依赖注入的。
  • 依赖消费者:目标类中用@Inject注解标注的类对象
  • 连接消费者和生产者:使用@Component注解标注的接口或抽象类。它定义了一个对象生产者(Module)和依赖对象的连接。

Daggar2的限制

  • 不能自动注入字段(Field)
  • 不能注入使用private修饰的Field
  • 若想要使用Field注解,不得不在@Component注解标注的接口或抽象类中定义一个方法。

项目实战

项目地址
核心类

  • DataManager--App数据的入口类
  • DbHelper -- DataManager使用它去访问数据库
  • SharedPrefsHelper--DataManager使用它去访问SharedPreferences

步骤一
在build.gradle文件中添加依赖:

dependencies {
    ...
    compile "com.google.dagger:dagger:2.8"
    annotationProcessor "com.google.dagger:dagger-compiler:2.8"
    provided 'javax.annotation:jsr250-api:1.0'
    compile 'javax.inject:javax.inject:1'
}

步骤二
创建实体类:

public class User {

    private Long id;
    private String name;
    private String address;
    private String createdAt;
    private String updatedAt;

    public User() {
    }

    public User(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(String createdAt) {
        this.createdAt = createdAt;
    }

    public String getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(String updatedAt) {
        this.updatedAt = updatedAt;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", createdAt='" + createdAt + '\'' +
                ", updatedAt='" + updatedAt + '\'' +
                '}';
    }
}

步骤三
创建自定义注解ActivityContextApplicationContextDatabaseInfoPerActivity

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityContext {
} 

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationContext {
}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface DatabaseInfo {
}

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerActivity {
}


  为什么我们要自定义这些注解?@Qualifier@Scope又代表什么意思呢?
  @Qualifier注解是javax inject package提供的,是用来区分不同依赖的。例如:一个类需要查询两个Context,一个Application的Context,一个Activity的Context,但是两者的类型都是Context,就可以用@Qualifier来区分。所以,对于dagger2来说,找出哪些变量是要提供什么,我们必须明确指定的标识符。
  因此,@Qualifier被是用来区分同一对象的不同实例,在上面的代码中,我们有ApplicationContextActivityContext以至于需要被注入的Context能够指出各自的Context真正类型。使用DatabaseInfo来给依赖提供数据库名字。由于String类是被当作依赖项来提供的,对其进行限定,以至于Daggar能够清楚地找到并知道这个字符串是数据库名字。
  另外一个注解@Named是Daggar2提供的。@Named自身就是被@Qualifier注解标注的。使用@Named,我们必须为相似的类对象提供标识符,这个标识符用于映射类的依赖关系。下文还将继续对@Named进行探索。
  @Scope用于指定依赖对象持久化的范围。如果一个类获得依赖项,则向成员注入带有范围注释的类,则该类的每个实例通过请求依赖项都将得到自己的成员变量集。
步骤四
创建DbHelper 类来继承 SQLiteOpenHelper,负责所有的Db操作。

@Singleton
public class DbHelper extends SQLiteOpenHelper {

    //USER TABLE
    public static final String USER_TABLE_NAME = "users";
    public static final String USER_COLUMN_USER_ID = "id";
    public static final String USER_COLUMN_USER_NAME = "usr_name";
    public static final String USER_COLUMN_USER_ADDRESS = "usr_add";
    public static final String USER_COLUMN_USER_CREATED_AT = "created_at";
    public static final String USER_COLUMN_USER_UPDATED_AT = "updated_at";

    @Inject
    public DbHelper(@ApplicationContext Context context,
                    @DatabaseInfo String dbName,
                    @DatabaseInfo Integer version) {
        super(context, dbName, null, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        tableCreateStatements(db);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + USER_TABLE_NAME);
        onCreate(db);
    }

    private void tableCreateStatements(SQLiteDatabase db) {
        try {
            db.execSQL(
                    "CREATE TABLE IF NOT EXISTS "
                            + USER_TABLE_NAME + "("
                            + USER_COLUMN_USER_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                            + USER_COLUMN_USER_NAME + " VARCHAR(20), "
                            + USER_COLUMN_USER_ADDRESS + " VARCHAR(50), "
                            + USER_COLUMN_USER_CREATED_AT + " VARCHAR(10) DEFAULT " + getCurrentTimeStamp() + ", "
                            + USER_COLUMN_USER_UPDATED_AT + " VARCHAR(10) DEFAULT " + getCurrentTimeStamp() + ")"
            );

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    protected User getUser(Long userId) throws Resources.NotFoundException, NullPointerException {
        Cursor cursor = null;
        try {
            SQLiteDatabase db = this.getReadableDatabase();
            cursor = db.rawQuery(
                    "SELECT * FROM "
                            + USER_TABLE_NAME
                            + " WHERE "
                            + USER_COLUMN_USER_ID
                            + " = ? ",
                    new String[]{userId + ""});

            if (cursor.getCount() > 0) {
                cursor.moveToFirst();
                User user = new User();
                user.setId(cursor.getLong(cursor.getColumnIndex(USER_COLUMN_USER_ID)));
                user.setName(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_NAME)));
                user.setAddress(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_ADDRESS)));
                user.setCreatedAt(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_CREATED_AT)));
                user.setUpdatedAt(cursor.getString(cursor.getColumnIndex(USER_COLUMN_USER_UPDATED_AT)));
                return user;
            } else {
                throw new Resources.NotFoundException("User with id " + userId + " does not exists");
            }
        } catch (NullPointerException e) {
            e.printStackTrace();
            throw e;
        } finally {
            if (cursor != null)
                cursor.close();
        }
    }

    protected Long insertUser(User user) throws Exception {
        try {
            SQLiteDatabase db = this.getWritableDatabase();
            ContentValues contentValues = new ContentValues();
            contentValues.put(USER_COLUMN_USER_NAME, user.getName());
            contentValues.put(USER_COLUMN_USER_ADDRESS, user.getAddress());
            return db.insert(USER_TABLE_NAME, null, contentValues);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    private String getCurrentTimeStamp() {
        return String.valueOf(System.currentTimeMillis() / 1000);
    }
}

:以下是这个类用到的一些注解

  • @Singleton - 保证一个类全局的单例
  • @Inject - 在构造函数构建时,构造函数指示Daggar所需的所有参数依赖项
  • @ApplicationContext@ApplicationContext Qualifier - 帮助DbHelper在Daggar的以来图中获得Application
  • @DatabaseInfo 帮助Daggar从存在相同类型的依赖图中去区分出StringInteger的限定符
    步骤五
    创建一个SharedPrefsHelper处理SharedPreferences
@Singleton
public class SharedPrefsHelper {

    public static String PREF_KEY_ACCESS_TOKEN = "access-token";

    private SharedPreferences mSharedPreferences;

    @Inject
    public SharedPrefsHelper(SharedPreferences sharedPreferences) {
        mSharedPreferences = sharedPreferences;
    }

    public void put(String key, String value) {
        mSharedPreferences.edit().putString(key, value).apply();
    }

    public void put(String key, int value) {
        mSharedPreferences.edit().putInt(key, value).apply();
    }

    public void put(String key, float value) {
        mSharedPreferences.edit().putFloat(key, value).apply();
    }

    public void put(String key, boolean value) {
        mSharedPreferences.edit().putBoolean(key, value).apply();
    }

    public String get(String key, String defaultValue) {
        return mSharedPreferences.getString(key, defaultValue);
    }

    public Integer get(String key, int defaultValue) {
        return mSharedPreferences.getInt(key, defaultValue);
    }

    public Float get(String key, float defaultValue) {
        return mSharedPreferences.getFloat(key, defaultValue);
    }

    public Boolean get(String key, boolean defaultValue) {
        return mSharedPreferences.getBoolean(key, defaultValue);
    }

    public void deleteSavedData(String key) {
        mSharedPreferences.edit().remove(key).apply();
    }
}

  这个类同样是被@Singleton注解所标注的,这样就保证了它在整个依赖图中是单例。
  这个类同样也是通过构造器被@Inject注解所标注的Daggar对象得到SharedPreferences依赖。
  那么这个依赖是怎样被提供的呢?下面会再做解释。
步骤六
创建DataManager类:

@Singleton
public class DataManager {

    private Context mContext;
    private DbHelper mDbHelper;
    private SharedPrefsHelper mSharedPrefsHelper;

    @Inject
    public DataManager(@ApplicationContext Context context,
                       DbHelper dbHelper,
                       SharedPrefsHelper sharedPrefsHelper) {
        mContext = context;
        mDbHelper = dbHelper;
        mSharedPrefsHelper = sharedPrefsHelper;
    }

    public void saveAccessToken(String accessToken) {
        mSharedPrefsHelper.put(SharedPrefsHelper.PREF_KEY_ACCESS_TOKEN, accessToken);
    }

    public String getAccessToken(){
        return mSharedPrefsHelper.get(SharedPrefsHelper.PREF_KEY_ACCESS_TOKEN, null);
    }

    public Long createUser(User user) throws Exception {
        return mDbHelper.insertUser(user);
    }

    public User getUser(Long userId) throws Resources.NotFoundException, NullPointerException {
        return mDbHelper.getUser(userId);
    }
}

注:这个类在构造方法中表达了需要Application ContextDbHelperSharedPrefsHelper依赖。它提供了所有访问应用api的数据入口。
步骤七
创建继承android.app.Application类的DemoApplication

public class DemoApplication extends Application {

    protected ApplicationComponent applicationComponent;

    @Inject
    DataManager dataManager;

    public static DemoApplication get(Context context) {
        return (DemoApplication) context.getApplicationContext();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        applicationComponent = DaggerApplicationComponent
                                    .builder()
                                    .applicationModule(new ApplicationModule(this))
                                    .build();
        applicationComponent.inject(this);
    }

    public ApplicationComponent getComponent(){
        return applicationComponent;
    }
}

添加这个类到Add this class in AndroidManifest.xml:

<application
    ...
    android:name=".DemoApplication"
    ...
</application>

这个类通过DI获取了Datamanager对象,最有趣的部分是ApplicationComponent。但是我们得一会再讨论它。
步骤八
创建MainActivity

public class MainActivity extends AppCompatActivity {

    @Inject
    DataManager mDataManager;

    private ActivityComponent activityComponent;

    private TextView mTvUserInfo;
    private TextView mTvAccessToken;

    public ActivityComponent getActivityComponent() {
        if (activityComponent == null) {
            activityComponent = DaggerActivityComponent.builder()
                    .activityModule(new ActivityModule(this))
                    .applicationComponent(DemoApplication.get(this).getComponent())
                    .build();
        }
        return activityComponent;
    }

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

        getActivityComponent().inject(this);

        mTvUserInfo = (TextView) findViewById(R.id.tv_user_info);
        mTvAccessToken = (TextView) findViewById(R.id.tv_access_token);
    }

    @Override
    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        createUser();
        getUser();
        mDataManager.saveAccessToken("ASDR12443JFDJF43543J543H3K543");

        String token = mDataManager.getAccessToken();
        if(token != null){
            mTvAccessToken.setText(token);
        }
    }

    private void createUser(){
        try {
            mDataManager.createUser(new User("Ali", "1367, Gurgaon, Haryana, India"));
        }catch (Exception e){e.printStackTrace();}
    }

    private void getUser(){
        try {
            User user = mDataManager.getUser(1L);
            mTvUserInfo.setText(user.toString());
        }catch (Exception e){e.printStackTrace();}
    }
}

同样创建activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_user_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:padding="10dp"/>

    <TextView
        android:id="@+id/tv_access_token"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/black"
        android:padding="10dp"/>
</LinearLayout>

现在来复习一下Daggar2:

  • 为了给一个类提供依赖,,我们不得不创建一个Module类。这个类定义了提供依赖的方法。一个Module类被@Module注解标注,它提供依赖的方法被@ProViders注解所标注。
  • 然后,我们必须提供一个类来作为一个桥梁,连接一个被@Inject注解所标注的作为依赖的类和一个被@Module注解所标注的提供依赖的类。
  • 为了指出一个Module要提供的依赖,我们必须扫描依赖图中国所有 提供了关系依赖的类,然后找出至少需要提供的依赖对象数量。

现在,根据上面的例子来理解刚才的这三段话。

步骤九
我们不得不提供被注解DemoApplication所标注的依赖。这个类需要DataManager,为了得到这个类,我们不得不提供被DataManager注解所标注的依赖,如前所述构造函数中的ContextDbHelperSharedPrefsHelper

  • Context必须是ApplicationContext
  • DbHelper 需要ContextdbNameversion
  • SharedPrefsHelper需要SharedPreferences
    现在累计一下所需要的依赖的超集,包括ContextDbHelperSharedPreferencesSharedPrefsHelper



现在,创建ApplicationModule来提供这些依赖:


@Module
public class ApplicationModule {

    private final Application mApplication;

    public ApplicationModule(Application app) {
        mApplication = app;
    }

    @Provides
    @ApplicationContext
    Context provideContext() {
        return mApplication;
    }

    @Provides
    Application provideApplication() {
        return mApplication;
    }

    @Provides
    @DatabaseInfo
    String provideDatabaseName() {
        return "demo-dagger.db";
    }

    @Provides
    @DatabaseInfo
    Integer provideDatabaseVersion() {
        return 2;
    }

    @Provides
    SharedPreferences provideSharedPrefs() {
        return mApplication.getSharedPreferences("demo-prefs", Context.MODE_PRIVATE);
    }
}

注:我们使用了@Module标注了该类和使用了@Providers来标注了他的提供依赖的方法

关于这个类:

  • 在这个构造方法,我们已经有了一个Application实例,这个实例被用来协助提供其他依赖实例;
  • 这个类提供了我们在上面步骤中列举的所有依赖。

步骤十
我们创建一个Component来链接DemoApplication依赖和ApplicationModule

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

    void inject(DemoApplication demoApplication);

    @ApplicationContext
    Context getContext();

    Application getApplication();

    DataManager getDataManager();

    SharedPrefsHelper getPreferenceHelper();

    DbHelper getDbHelper();

}

注:ApplicationComponent是一个被Daggar2实现的接口,我们区分一个Component类就是看看是否被@Component注解所标注。

  这里我们写了一个inject方法,携带参数DemoApplication。为什么要这样做呢?

  当通过字段注入,即@Inject注解使用在成员变量,提供依赖时,我们必须告诉Daggar通过这个接口的实现去扫描这个类。

  这个类还提供了用于访问依赖图中已经存在的依赖关系的方法。
步骤十一
同样地,我们不得不为MainActivity创建一个Module和对应的Compoonent

@Module
public class ActivityModule {

    private Activity mActivity;

    public ActivityModule(Activity activity) {
        mActivity = activity;
    }

    @Provides
    @ActivityContext
    Context provideContext() {
        return mActivity;
    }

    @Provides
    Activity provideActivity() {
        return mActivity;
    }
}
@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {

    void inject(MainActivity mainActivity);

}

注:ActivityComponent指定了ApplicationComponentActivityModule.ApplicationComponent被添加来使用前面步骤中已经生成的或者是存在的依赖图,因为DemoApplication在Application运行之前都会一直存在???
  @PerActivity被用来告诉Daggar ContextActivity是通过ActivityModule提供的,Activity每被创建一次便会初始化一次,所以,这些对象会被保存在整个Activity的生命周期中,且每一个Activity对象都持有一份。
  我们可能会问DataManager会随Activity的创建而创建。但是这是不对的,因为DataManager是被@Singleton注解所标注的。
  让我们再次回到DemoApplicationMianActivity类。我们会在onCreate方法中取得依赖。

applicationComponent = DaggerApplicationComponent
                            .builder()
                            .applicationModule(new ApplicationModule(this))
                            .build();
applicationComponent.inject(this);

  DaggerApplicationComponent是Daggar自动生成的,实现了ApplicationComponent。我们提供了ApplicationModule去创建需要的依赖。
  我们也通过DemoApplication去调用了applicationComponentinject方法,这样做是为了用它来提供DataManagerApplicationComponent实例会被保存起来,用作访问以来途中所有类的入口。

public ActivityComponent getActivityComponent() {
    if (activityComponent == null) {
        activityComponent = DaggerActivityComponent.builder()
                .activityModule(new ActivityModule(this))
                .applicationComponent(DemoApplication.get(this).getComponent())
                .build();
    }
    return activityComponent;
}

  类似的过程也被用于MainActivity,唯一不同的是,我们还提供了ActivityModule中提及的需要依赖解析的ApplicationComponent
  
  
  
  

相关文章

  • Dagger2-1

    注:原文链接  依赖注入(Dependency Injection简称DI)是在控制反转(Inversion of...

网友评论

      本文标题:Dagger2-1

      本文链接:https://www.haomeiwen.com/subject/elqwbxtx.html