Lifecycle+Retrofit+Room完美結合 領略架構之美

常興E站發表於2017-12-12

安卓開發技術發展到現在已經非常成熟,有很多的技術專項如外掛,熱修,加固,瘦身,效能優化,自動化測試等已經在業界有了完善的或者開源的解決方案。

作為一枚多年的安卓研發,有必要學習或瞭解下這些優秀的解決方案,領略那些行業開創者的思想魅力,然後轉化為自己的技術技能,爭取應用到日常的開發中去,提高自己研發水平。

Lifecycle+Retrofit+Room 雲端漫步飛一般的感覺

安卓專案的開發結構,有原來最初的mvc,到後來有人提出的mvp,然後到mvvm的發展,無非就是依著六大設計原則的不斷解耦,不斷演變,使得專案的開發高度元件化,滿足日常複雜多變的專案需求。

  • 依賴倒置原則-Dependency Inversion Principle (DIP)
  • 里氏替換原則-Liskov Substitution Principle (LSP)
  • 介面分隔原則-Interface Segregation Principle (ISP)
  • 單一職責原則-Single Responsibility Principle (SRP)
  • 開閉原則-The Open-Closed Principle (OCP)
  • 迪米特法則-Law of Demeter (LOD)

目前針對MVVM框架結構,安卓官方也給出了穩定的架構版本1.0。

本文也是圍繞者官方思想,試著從原始碼角度總結學習經驗,最後將這些控制元件二次封裝,更加便於理解使用。其也會涉及到其他優秀的的庫,如Gson,Glide,BaseRecyclerViewAdapterHelper等

DEMO涉及庫

一.起因

去年還在手機衛士團隊做垃圾清理模組時候,趕上模組化二次程式碼重構技術需求,專案分為多個程式,其中後臺程式負責從DB中獲取資料(DB可雲端更新升級),然後結合雲端配置資訊做相關邏輯操作,將資料回傳到UI程式,UI程式中在後臺執行緒中二次封裝,最後post到主UI執行緒Update到介面展示給使用者。

當時就和團隊的同學溝通交流,面對這種資料多層次複雜的處理邏輯,設想可以做一種機制,將UI層的更新繫結到一個資料來源,資料來源資料的更新可以自動觸發UI更新,實現與UI的解耦。 資料來源控制著資料的來源,每種來源有著獨立的邏輯分層,共享底層一些公共lib庫。

後來想想,其實差不多就是MVVM思想,直到谷歌官方宣佈Android 架構元件 1.0 穩定版的釋出,才下決心學習下官方這一套思想,感受優秀的架構。

引用官方一張結構圖如下:

architecture

二.各元件庫原理及基本用法

這裡主要探究下主要元件庫的基本用法和原理,以理解其優秀思想為主。

谷歌官方Android Architecture Components

Lifecycle+LiveData+ViewMode+Room

A collection of libraries that help you design robust, testable, and maintainable apps. Start with classes for managing your UI component lifecycle and handling data persistence.

Lifecyle

一個安卓元件生命週期感知回撥的控制元件,可以感知activity或fragment的生命週期變化,並回撥相應介面。

可以先從原始碼抽象類分析,如下:

public abstract class Lifecycle {
    @MainThread
    public abstract void addObserver(@NonNull LifecycleObserver observer);

    @MainThread
    public abstract void removeObserver(@NonNull LifecycleObserver observer);

    @MainThread
    public abstract State getCurrentState();

    @SuppressWarnings("WeakerAccess")
    public enum Event {
        ON_CREATE,
        ON_START,
        ON_RESUME,
        ON_PAUSE,
        ON_STOP,
        ON_DESTROY,
        ON_ANY
    }

    @SuppressWarnings("WeakerAccess")
    public enum State {

        DESTROYED,
        INITIALIZED,
        CREATED,
        STARTED,
        RESUMED;

        public boolean isAtLeast(@NonNull State state) {
            return compareTo(state) >= 0;
        }
    }
}

/**
 * Marks a class as a LifecycleObserver. It does not have any methods, instead, relies on
 * {@link OnLifecycleEvent} annotated methods.
 * <p>
 * @see Lifecycle Lifecycle - for samples and usage patterns.
 */
@SuppressWarnings("WeakerAccess")
public interface LifecycleObserver {
}
複製程式碼

Lifecycle抽象類有三個方法,可以新增、刪除觀察者,並且可以獲取當前元件的生命週期的狀態。 而LifecycleObserver介面只是個空介面做型別校驗,具體事件交給了OnLifecycleEvent,通過注入回撥相應事件。

二在最新的support-V4包中,ActivityFragment都實現了LifecycleOwner介面,意味著我們可以直接使用getLifecyle()獲取當前ActivityFragment的Lifecycle物件,很方便的新增我們的監聽方法。

@SuppressWarnings({"WeakerAccess", "unused"})
public interface LifecycleOwner {
    @NonNull
    Lifecycle getLifecycle();
}
複製程式碼

LiveData

LiveData是一個持有範型型別的資料元件,將App生命元件與資料關聯到一起,可以感知生命變更,規則回撥資料變化。

public abstract class LiveData<T> {
	        private volatile Object mData = NOT_SET;//資料容器類

    //一個寫死的處於Resume狀態的LifecycleOwner,用於資料回撥無限制情況
    private static final LifecycleOwner ALWAYS_ON = new LifecycleOwner() {

        private LifecycleRegistry mRegistry = init();

        private LifecycleRegistry init() {
            LifecycleRegistry registry = new LifecycleRegistry(this);
            registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
            registry.handleLifecycleEvent(Lifecycle.Event.ON_START);
            registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
            return registry;
        }

        @Override
        public Lifecycle getLifecycle() {
            return mRegistry;
        }
    };
    private SafeIterableMap<Observer<T>, LifecycleBoundObserver> mObservers =
            new SafeIterableMap<>();
   
    private void considerNotify(LifecycleBoundObserver observer) {
        if (!observer.active) {
            return;
        }

        if (!isActiveState(observer.owner.getLifecycle().getCurrentState())) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.lastVersion >= mVersion) {
            return;
        }
        observer.lastVersion = mVersion;
        //noinspection unchecked
        observer.observer.onChanged((T) mData);//最終的回撥方法地方
    }

    private void dispatchingValue(@Nullable LifecycleBoundObserver initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<T>, LifecycleBoundObserver>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());//可以新增做個監聽者,這裡遍歷分發資料到每個監聽者
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

    @MainThread
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
        LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && existing.owner != wrapper.owner) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }

    @MainThread
    public void observeForever(@NonNull Observer<T> observer) {
        observe(ALWAYS_ON, observer);
    }

    @MainThread
    public void removeObserver(@NonNull final Observer<T> observer) {
        assertMainThread("removeObserver");
        LifecycleBoundObserver removed = mObservers.remove(observer);
        if (removed == null) {
            return;
        }
        removed.owner.getLifecycle().removeObserver(removed);
        removed.activeStateChanged(false);
    }

    //實現類回撥方法
    protected void onActive() {

    }

    //實現類回撥方法
    protected void onInactive() {
    }

    class LifecycleBoundObserver implements GenericLifecycleObserver {
        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            if (owner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(observer);
                return;
            }
            // immediately set active state, so we'd never dispatch anything to inactive
            // owner
            activeStateChanged(isActiveState(owner.getLifecycle().getCurrentState()));
        }

        void activeStateChanged(boolean newActive) {
            if (newActive == active) {
                return;
            }
            active = newActive;
            boolean wasInactive = LiveData.this.mActiveCount == 0;
            LiveData.this.mActiveCount += active ? 1 : -1;
            if (wasInactive && active) {
                onActive();
            }
            if (LiveData.this.mActiveCount == 0 && !active) {
                onInactive();
            }
            if (active) {//只有生命元件處於前臺時,才觸發資料的變更通知
                dispatchingValue(this);
            }
        }
    }

    static boolean isActiveState(State state) {
        return state.isAtLeast(STARTED);
    }
}
複製程式碼

看原始碼,會發現LiveData有個重要的方法observe(LifecycleOwner owner, Observer observer), 在資料來源資料有變更時,遍歷分發資料到所有監聽者,最後會回撥onChanged()方法。

public interface Observer<T> {
    /**
     * Called when the data is changed.
     * @param t  The new data
     */
    void onChanged(@Nullable T t);
}
複製程式碼

LiveData有兩個實現類:MediatorLiveDataMediatorLiveData,繼承關係如下:

LiveData

MutableLiveData類很簡單,只是暴露了兩個方法:postData()和setData()。 MediatorLiveData類有個**addSource()**方法,可以實現監聽另一個或多個LiveData資料來源變化,這樣我們就可以比較便捷且低耦合的實現多個資料來源的邏輯,並且關聯到一個MediatorLiveData上,實現多資料來源的自動整合。

    @MainThread
    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<S> onChanged) {
        Source<S> e = new Source<>(source, onChanged);
        Source<?> existing = mSources.putIfAbsent(source, e);
        if (existing != null && existing.mObserver != onChanged) {
            throw new IllegalArgumentException(
                    "This source was already added with the different observer");
        }
        if (existing != null) {
            return;
        }
        if (hasActiveObservers()) {
            e.plug();
        }
    }
複製程式碼

ViewModel

LiveData和LiveCycle將資料與資料,資料與UI生命繫結到了一起,實現了資料的自動管理和更新,那這些資料如何快取呢?能否在多個頁面共享這些資料呢?答案是ViewMode。

A ViewModel is always created in association with a scope (an fragment or an activity) and will be retained as long as the scope is alive. E.g. if it is an Activity, until it is finished.

ViewMode相當於一層資料隔離層,將UI層的資料邏輯全部抽離乾淨,管理制底層資料的獲取方式和邏輯。

         ViewModel   viewModel = ViewModelProviders.of(this).get(xxxModel.class);
         ViewModel   viewModel = ViewModelProviders.of(this, factory).get(xxxModel.class);
複製程式碼

可以通過以上方式獲取ViewModel例項,如果有自定義ViewModel構造器引數,需要藉助ViewModelProvider.NewInstanceFactory,自己實現create方法。

那麼,ViewMode是怎麼被儲存的呢? 可以順著ViewModelProviders原始碼進去看看。

    @NonNull
    @MainThread
    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }

        viewModel = mFactory.create(modelClass);
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }
複製程式碼

發現get方法會先從快取中獲取,沒有的化就會通過Factory的create方法構造一個ViewModel,然後放入快取,下次直接使用。

Room

Room是一種ORM(物件關係對映)模式資料庫框架,對安卓SQlite的抽象封裝,從此運算元據庫提供了超便捷方式。

The Room persistence library provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.

同樣基於ORM模式封裝的資料庫,比較有名還有GreenDao。而Room和其他ORM對比,具有編譯時驗證查詢語句正常性,支援LiveData資料返回等優勢。 我們選擇room,更多是因為對LiveData的完美支援,可以動態的將DB資料變化自動更新到LiveData上,在通過LiveData自動重新整理到UI上。

這裡引用網路上的一張Room與其他同類效能對比圖片:

效能對比

Room用法:

    1. 繼承RoomDatabase的抽象類, 暴露抽象方法getxxxDao()。
@Database(entities = {EssayDayEntity.class, ZhihuItemEntity.class}, version = 1)
@TypeConverters(DateConverter.class)
public abstract class AppDB extends RoomDatabase {
    private static AppDB sInstance;
    @VisibleForTesting
    public static final String DATABASE_NAME = "canking.db";
    public abstract EssayDao essayDao();
 }
複製程式碼
    1. 獲取db例項
ppDatabase db = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();
複製程式碼
    1. 實現Dao層邏輯
@Dao
public interface ZhuhuDao {
    @Query("SELECT * FROM zhuhulist  order by id desc, id limit 0,1")
    LiveData<ZhihuItemEntity> loadZhuhu();

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertItem(ZhihuItemEntity products);
}
複製程式碼
    1. 新增一張表結構
@Entity
public class User {
    @PrimaryKey
    private int uid;

    @ColumnInfo(name = "first_name")
    private String firstName;

    public String date;//預設columnInfo 為 date

}
複製程式碼

就這麼簡單,就可以實現資料庫的操作,完全隔離的底層複雜的資料庫操作,大大節省專案研發重複勞動力。

從使用說明分析,UserDao和Db一個是介面,一個是抽象類,這些邏輯的實現完全是由annotationProcessor依賴注入幫我們實現的, annotationProcessor其實就是開源的android-apt的官方替代品。 那麼編譯專案後,可以在build目錄下看到生成相應的類xxx_impl.class。

impl

既然Room支援LiveData資料,那麼有可以分析下原始碼,瞭解下具體原理,方便以後填坑。

先選Demo中Dao層的insert方法,看看資料如何載入到記憶體的。我們的query方法如下:

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertItem(ZhihuItemEntity products);
複製程式碼

annotationProcessor幫我嗎生成後的實現主要程式碼如下:

    private final RoomDatabase __db;
    private final EntityInsertionAdapter __insertionAdapterOfZhihuItemEntity;
    
    public ZhuhuDao_Impl(RoomDatabase __db) {
        this.__db = __db;
        //EntityInsertionAdapter類的匿名內部類實現方式,
        this.__insertionAdapterOfZhihuItemEntity = new EntityInsertionAdapter<ZhihuItemEntity>(__db) {
            public String createQuery() {
                return "INSERT OR REPLACE INTO `zhuhulist`(`id`,`date`,`stories`,`top_stories`) VALUES (nullif(?, 0),?,?,?)";
            }

            public void bind(SupportSQLiteStatement stmt, ZhihuItemEntity value) {
            	//通過SQLiteStatement的bind方法,可以很巧妙的將類物件資料轉化為資料庫要操作的資料型別。
                stmt.bindLong(1, (long)value.getId());//按順序依次放入SQLiteStatement物件。
                if(value.date == null) {
                    stmt.bindNull(2);
                } else {
                    stmt.bindString(2, value.date);
                }

                //通過DB類注入的自定義轉化器,我們可以將任何物件型別持久化到資料庫中,並且很便捷的從資料庫反序列化出來
                String _tmp = DateConverter.toZhihuStoriesEntity(value.stories);
                if(_tmp == null) {
                    stmt.bindNull(3);
                } else {
                    stmt.bindString(3, _tmp);
                }

                String _tmp_1 = DateConverter.toZhihuStoriesEntity(value.top_stories);
                if(_tmp_1 == null) {
                    stmt.bindNull(4);
                } else {
                    stmt.bindString(4, _tmp_1);
                }

            }
        };
    }

    
    public void insertItem(ZhihuItemEntity products) {
        this.__db.beginTransaction();

        try {
        	//藉助SQLiteStatement類運算元據庫,既優化了資料庫操作效能,又巧妙的bind了物件型別資料。
            this.__insertionAdapterOfZhihuItemEntity.insert(products);
            this.__db.setTransactionSuccessful();
        } finally {
        	//這裡很重要,我們平時運算元據庫或流必須要做 finally塊 關閉資源。
            this.__db.endTransaction();
        }
    }
複製程式碼

實現類中可以看出insert是通過EntityInsertionAdapter類完成操作的,而EntityInsertionAdapter內部會持有個SupportSQLiteStatement,其實就是SQLiteStatement類的抽象封裝。 其例項獲取是通過RoomData內部方法compileStatement()得到的。

研究下RoomData抽象類原始碼:

public abstract class RoomDatabase {
    // set by the generated open helper.
    protected volatile SupportSQLiteDatabase mDatabase;//SQLiteDatabase類的封裝抽象層
    private SupportSQLiteOpenHelper mOpenHelper;//SQLiteOpenHelper類的封裝抽象層
    private final InvalidationTracker mInvalidationTracker;//繫結資料變更監聽器,如在資料變化時通知LiveData
    
    protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config);
    protected abstract InvalidationTracker createInvalidationTracker();
    
    public Cursor query(String query, @Nullable Object[] args) {
        return mOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(query, args));
    }

    public Cursor query(SupportSQLiteQuery query) {
        assertNotMainThread();//每次資料庫操作檢查執行緒
        return mOpenHelper.getWritableDatabase().query(query);
    }


    public SupportSQLiteStatement compileStatement(String sql) {
        assertNotMainThread();
        return mOpenHelper.getWritableDatabase().compileStatement(sql);
    }

    public void beginTransaction() {
        assertNotMainThread();
        mInvalidationTracker.syncTriggers();
        mOpenHelper.getWritableDatabase().beginTransaction();
    }

    public void endTransaction() {
        mOpenHelper.getWritableDatabase().endTransaction();
        if (!inTransaction()) {
            // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last
            // endTransaction call to do it.
            mInvalidationTracker.refreshVersionsAsync();
        }
    }
    
    public static class Builder<T extends RoomDatabase> {
        private MigrationContainer mMigrationContainer;//資料庫升級輔助類

    @NonNull
        public Builder<T> addCallback(@NonNull Callback callback) {
            if (mCallbacks == null) {
                mCallbacks = new ArrayList<>();
            }
            mCallbacks.add(callback);
            return this;
        }
        
    @NonNull
        public T build() {
            //noinspection ConstantConditions
            if (mContext == null) {
                throw new IllegalArgumentException("Cannot provide null context for the database.");
            }
            //noinspection ConstantConditions
            if (mDatabaseClass == null) {
                throw new IllegalArgumentException("Must provide an abstract class that"
                        + " extends RoomDatabase");
            }
            if (mFactory == null) {
            //預設的SupportSQLiteOpenHelper建立工廠
                mFactory = new FrameworkSQLiteOpenHelperFactory();//SupportSQLiteOpenHelper的實現類,通過mDelegate帶來類操作真正的SQLiteOpenHelper
            }
            DatabaseConfiguration configuration =
                    new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
                            mCallbacks, mAllowMainThreadQueries, mRequireMigration);
            //最終通過反射載入系統幫我們實現的真正RoomData
            T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
            db.init(configuration);
            return db;
        }
        
        public abstract static class Callback {

        public void onCreate(@NonNull SupportSQLiteDatabase db) {
        }

        public void onOpen(@NonNull SupportSQLiteDatabase db) {
        }
    }
    }
複製程式碼

DB是通過Build設計模式獲取例項的,在build過程中,可以新增CallBack抽象類回撥資料的onCreateonOpen。 這裡發現個問題,抽象層封裝那麼深,*onUpgrade()*方法怎麼回撥呢?資料庫的升級怎麼新增自己的邏輯呢?奧祕在MigrationContainer類。

    public static class MigrationContainer {
        private SparseArrayCompat<SparseArrayCompat<Migration>> mMigrations =
                new SparseArrayCompat<>();

        public void addMigrations(Migration... migrations) {
            for (Migration migration : migrations) {
                addMigration(migration);
            }
        }

        private void addMigration(Migration migration) {
            final int start = migration.startVersion;
            final int end = migration.endVersion;
            SparseArrayCompat<Migration> targetMap = mMigrations.get(start);
            if (targetMap == null) {
                targetMap = new SparseArrayCompat<>();
                mMigrations.put(start, targetMap);
            }
            Migration existing = targetMap.get(end);
            if (existing != null) {
                Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration);
            }
            targetMap.append(end, migration);
        }

        @SuppressWarnings("WeakerAccess")
        @Nullable
        public List<Migration> findMigrationPath(int start, int end) {
            if (start == end) {
                return Collections.emptyList();
            }
            boolean migrateUp = end > start;
            List<Migration> result = new ArrayList<>();
            return findUpMigrationPath(result, migrateUp, start, end);
        }
    }
    
    public abstract class Migration {
    	public final int startVersion;
		public final int endVersion;

    	public Migration(int startVersion, int endVersion) {
       	 this.startVersion = startVersion;
       	 this.endVersion = endVersion;
    	}

    	public abstract void migrate(@NonNull SupportSQLiteDatabase database);
    }
}
複製程式碼

在Room.databaseBuilder過程中,可以通過*addMigration()*方法,設定多個或一個Migration。

在RoomOpenHelper的onUpgrade()方法中會依次呼叫升級範圍內的Migration:

	@Override
    public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
        boolean migrated = false;
        if (mConfiguration != null) {
            List<Migration> migrations = mConfiguration.migrationContainer.findMigrationPath(
                    oldVersion, newVersion);
            if (migrations != null) {
                for (Migration migration : migrations) {
                    migration.migrate(db);
                }
            }
        }
    }
複製程式碼

分析Room到這裡基本原理已瞭解,並且我們可以封裝自己的Callback介面,對業務模組依次分發onCreate、onUpgrade方法,統一管理資料庫的建立和升級。

Retrofit

當前業界很流行,且很優秀的開源網路庫,基於OkHttp之前開發。

A type-safe HTTP client for Android and Java

個人理解Retrofit是高度抽象,且和業務耦合度很低的網路庫,通過各種資料轉化器或介面卡,使得網路返回資料可以很奇妙的直接轉化為我們想要的型別,與本地資料的快取及持久化高度無縫對接,大大減少了開發投入。並且使得專案研發更易模組化和迭代升級。

基本用法可以移步官網學習研究,這裡只分析下如何構造自定義返回型別,預設通用的請求返回如下:

	XXXService service = retrofit.create(XXXService.class);
	Call<List<Repo>> repos = service.listRepos("xxx");
複製程式碼
 public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }
複製程式碼

retrofit.create方法內部通過java動態代理,連結介面方法,替換轉化範型型別及返回型別。 Retrofit.Builder有兩個重要方法,影響著*service.listRepos()*方法的返回值型別及反序型別。它們分別是:

    /** Add converter factory for serialization and deserialization of objects. */
    //影響者Call介面中的範型型別
    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    /**
     * Add a call adapter factory for supporting service method return types other than {@link
     * Call}.
     * 影響者Call介面的具體實現型別
     */
    public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
      adapterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }
複製程式碼

通過addConverterFactory方法,可以將網路返回資料直接轉化為本地的具體實體型別,並且retrofit已經為我們提供了常見協議資料型別的封裝庫,如下:

Converter 依賴
Gson com.squareup.retrofit2:converter-gson:xxx
Jackson com.squareup.retrofit2:converter-jackson:xxx
Moshi com.squareup.retrofit2:converter-moshi:xxx
Protobuf com.squareup.retrofit2:converter-protobuf:xxx
Wire com.squareup.retrofit2:converter-wire:xxx
Simple XML com.squareup.retrofit2:converter-simplexml:xxx
Scalars com.squareup.retrofit2:converter-scalars:xxx

Builder每新增一個轉化器會儲存在*List<Converter.Factory>*型別列表中去。通過以下程式碼轉化為目標型別。

 	for (int i = start, count = converterFactories.size(); i < count; i++) {
      Converter.Factory factory = converterFactories.get(i);
      Converter<?, RequestBody> converter =
          factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, this);
      if (converter != null) {
        //noinspection unchecked
        return (Converter<T, RequestBody>) converter;
      }
    }
複製程式碼

當然也可以自定義Converter型別:

public interface Converter<F, T> {
  T convert(F value) throws IOException;

  abstract class Factory {
    // 這裡建立從ResponseBody其它型別的Converter,如果不能處理返回null
    // 主要用於對響應體的處理
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
      return null;
    }

    // 在這裡建立 從自定型別到ResponseBody 的Converter,不能處理就返回null,
    public Converter<?, RequestBody> requestBodyConverter(Type type,
    Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    //在這裡實現具體轉化邏輯
    }

    // Retrfofit對於上面的幾個註解預設使用的是呼叫toString方法
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
        //在這裡實現具體轉化邏輯
    }
  }
}
複製程式碼

Retrofit通過addCallAdapterFactory方法可以支援返回型別Java8rxjava的處理(也需要新增gradle依賴庫)。

	new Retrofit.Builder()
      .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
      .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 
      .build();
複製程式碼

三. 封裝、整合各框架到專案中去

主要是用LiveData將各框架的資料獲取及頁面更新,按照MVVM思想整合起來, 使得專案結構符合官方給出的架構圖建議,搭建一層邏輯結構,使得更加方便的使用各個元件庫。

引自官方architecture

從上到下的邏輯順序,依次構建各個業務層 需要的邏輯控制元件:

1.編寫需要資料初始化或更新UI的介面方法,並在Observer中更新。

	viewModel.getEssayData().observe(this, new Observer<Resource<ZhihuItemEntity>>() {
            @Override
            public void onChanged(@Nullable Resource<ZhihuItemEntity> essayDayEntityResource) {
            //資料來源內資料變動後自動回撥該介面,然後更新到UI上
                        updateUI(essayDayEntityResource.data);
            }
        });
複製程式碼

2.構建UI層需要的ViewModel

public class EssayViewModel extends AndroidViewModel {
    private EssayRepository mRepository;
    private MediatorLiveData<Resource<ZhihuItemEntity>> mCache;

    public EssayViewModel(Application app) {
        super(app);
        mRepository = new EssayRepository(app);
    }

    public LiveData<Resource<ZhihuItemEntity>> getEssayData() {
        if (mCache == null) {
        //初始化後,從快取讀取
            mCache = mRepository.loadEssayData();
        }
        return mCache;
    }

    public void updateCache() {
        final LiveData<Resource<ZhihuItemEntity>> update = mRepository.update();
        mCache.addSource(update, new Observer<Resource<ZhihuItemEntity>>() {
            @Override
            public void onChanged(@Nullable Resource<ZhihuItemEntity> zhihuItemEntityResource) {
                mCache.setValue(zhihuItemEntityResource);
            }
        });

    }
    
    public void addMore(){
    	//TODO: 載入更多
    }
}
複製程式碼

3.實現Repository類,管理資料獲取渠道。

這裡按照官方知道,寫了個抽象的資料來源類,每次先從本地DB取資料,然後獲取網路資料更新到資料庫,通過LiveData更新到UI層。

public abstract class AbsDataSource<ResultType, RequestType> {
    private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();

    @WorkerThread
    protected abstract void saveCallResult(@NonNull RequestType item);

    @MainThread
    protected abstract boolean shouldFetch(@Nullable ResultType data);

    // Called to get the cached getDate from the database
    @NonNull
    @MainThread
    protected abstract LiveData<ResultType> loadFromDb();

    @NonNull
    @MainThread
    protected abstract LiveData<IRequestApi<RequestType>> createCall();

    @MainThread
    protected abstract void onFetchFailed();

    @MainThread
    public AbsDataSource() {
        final LiveData<ResultType> dbSource = loadFromDb();
        result.setValue(Resource.loading(dbSource.getValue()));

        result.addSource(dbSource, new Observer<ResultType>() {
            @Override
            public void onChanged(@Nullable ResultType resultType) {
                result.removeSource(dbSource);
                if (shouldFetch(resultType)) {
                    fetchFromNetwork(dbSource);
                } else {
                    result.addSource(dbSource, new Observer<ResultType>() {
                        @Override
                        public void onChanged(@Nullable ResultType resultType) {
                            result.setValue(Resource.success(resultType));
                        }
                    });
                }
            }
        });
    }

    private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
        final LiveData<IRequestApi<RequestType>> apiResponse = createCall();
        result.addSource(dbSource, new Observer<ResultType>() {
            @Override
            public void onChanged(@Nullable ResultType resultType) {
                result.setValue(Resource.loading(resultType));
            }
        });

        result.addSource(apiResponse, new Observer<IRequestApi<RequestType>>() {
            @Override
            public void onChanged(@Nullable final IRequestApi<RequestType> requestTypeRequestApi) {
                result.removeSource(apiResponse);
                result.removeSource(dbSource);
                //noinspection ConstantConditions
                if (requestTypeRequestApi.isSuccessful()) {
                    saveResultAndReInit(requestTypeRequestApi);
                } else {
                    onFetchFailed();

                    result.addSource(dbSource, new Observer<ResultType>() {
                        @Override
                        public void onChanged(@Nullable ResultType resultType) {
                            result.setValue(
                                    Resource.error(requestTypeRequestApi.getErrorMsg(), resultType));
                        }
                    });
                }
            }
        });
    }

    @MainThread
    private void saveResultAndReInit(final IRequestApi<RequestType> response) {
        new AsyncTask<Void, Void, Void>() {

            @Override
            protected Void doInBackground(Void... voids) {
                saveCallResult(response.getBody());
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                // we specially request a new live getDate,
                // otherwise we will get immediately last cached value,
                // which may not be updated with latest results received from network.

                result.addSource(loadFromDb(), new Observer<ResultType>() {
                    @Override
                    public void onChanged(@Nullable ResultType resultType) {
                        result.setValue(Resource.success(resultType));
                    }
                });
            }
        }.execute();
    }

    public final MediatorLiveData<Resource<ResultType>> getAsLiveData() {
        return result;
    }
}
複製程式碼

4.封裝Room資料庫使用輔助類

這裡二次封裝了資料庫回撥介面,便於多個邏輯模組多資料庫的統一管理使用。

public abstract class AbsDbCallback {
    public abstract void create(SupportSQLiteDatabase db);

    public abstract void open();

    public abstract void upgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion);
}

public class DbCallbackHelper {
    private static ArrayList<AbsDbCallback> mDbCallbacks = new ArrayList<>();

    public static void init() {
        mDbCallbacks.add(new EssayDbCallback());
    }

    public static void dispatchOnCreate(SupportSQLiteDatabase db) {
        for (AbsDbCallback callback : mDbCallbacks) {
        //分發onCreate介面
            callback.create(db);
        }
    }

    private static void dispatchUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
        for (AbsDbCallback callback : mDbCallbacks) {
            callback.upgrade(db, oldVersion, newVersion);
        }
    }

    public static Migration[] getUpdateConfig() {
    	//每次資料庫升級配置這裡就可以自動分發到各業務模組的onUpgrade()方法
        return new Migration[]{
                new Migration(1, 2) {

                    @Override
                    public void migrate(@NonNull SupportSQLiteDatabase database) {
                        dispatchUpgrade(database, 1, 2);
                    }
                },
                new Migration(2, 3) {
                    @Override
                    public void migrate(@NonNull SupportSQLiteDatabase database) {
                        dispatchUpgrade(database, 2, 3);
                    }
                }
        };
    }
}

複製程式碼

5.對網路庫資料處理的二次封裝

定義一個範型的資料返回介面,便於抽象業務構造及替換網路請求方式。

public interface IRequestApi<ResultType> {
    ResultType getBody();
    String getErrorMsg();
    boolean isSuccessful();
}

	@WorkerThread
    public <ResultType> LiveData<IRequestApi<ResultType>> getEssay(@EssayWebService.EssayType String type) throws IOException {
        EssayWebService api = mRetrofit.create(EssayWebService.class);

        Call<ZhihuItemEntity> essayCall = api.getZhihuList("latest");
        MediatorLiveData<IRequestApi<ResultType>> result = new MediatorLiveData<>();
        final Response<ZhihuItemEntity> response = essayCall.execute();

        IRequestApi<ResultType> requestApi = new IRequestApi<ResultType>() {
            @Override
            public ResultType getBody() {
                ZhihuItemEntity entity = response.body();
                return (ResultType) entity;
            }

            @Override
            public String getErrorMsg() {
                return response.message();
            }

            @Override
            public boolean isSuccessful() {
                return response.isSuccessful();
            }
        };
        result.postValue(requestApi);
        return result;
    }

複製程式碼

定義一個*Resource*的型別包裝統一的傳遞資料,便於UI業務的統一處理。

public class Resource<T> {
    public enum Status {
        LOADING, MORE_ADD, SUCCEED, ERROR
    }

    @NonNull
    public final Status status;
    @Nullable
    public final T data;
    @Nullable
    public final String message;

    private Resource(@NonNull Status status, @Nullable T data, @Nullable String message) {
        this.status = status;
        this.data = data;
        this.message = message;
    }

    public static <T> Resource<T> success(@NonNull T data) {
        return new Resource<>(SUCCEED, data, null);
    }

    public static <T> Resource<T> error(String msg, @Nullable T data) {
        return new Resource<>(ERROR, data, msg);
    }

    public static <T> Resource<T> loading(@Nullable T data) {
        return new Resource<>(LOADING, data, null);
    }

    public static <T> Resource<T> moreSucceed(@Nullable T data) {
        return new Resource<>(MORE_ADD, data, null);
    }
}
複製程式碼

以上二次封裝Demo原始碼已上傳GitHub, 有興趣同學可以學習交流及Star。

四. 總結

回顧官方的這一套框架結構,其中LivaData個人覺得最重要,她將資料與資料、資料與UI連結在一起,起到了資料的自動管理,解耦多個業務邏輯,是一種優秀的程式設計思想。

但是LiveData是否是最適合用到android架構開發中取呢?官方給出了這樣一句話:

Note: If you are already using a library like RxJava or Agera, you can continue using them instead of LiveData. But when you use them or other approaches, make sure you are handling the lifecycle properly such that your data streams pause when the related LifecycleOwner is stopped and the streams are destroyed when the LifecycleOwner is destroyed. You can also add the android.arch.lifecycle:reactivestreams artifact to use LiveData with another reactive streams library (for example, RxJava2).

同樣官方沒有忽略RxJava的優秀,但是由於個人對RxJava的認識只是檢視網路資料瞭解,並未領略到其威力,有興趣同學可以瞭解下。

RxJava就是一種用Java語言實現的響應式程式設計,來建立基於事件的非同步程式

優秀的框架重在學習其獨特的思想,瞭解其基本實現原理,然後轉化為自己的程式設計思想。個人覺的這個過程是很緩慢的,只有不斷的感悟不同的優秀框架,慢慢的才能產生質變。


歡迎轉載,請標明出處:常興E站 canking.win

相關文章