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

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