Android development technology has now been very mature, there are a lot of technology specific such as plug-ins, hot repair, reinforcement, thin, performance optimization, automated testing has been in the industry has a perfect or open source solution.

As a years of Android research and development, it is necessary to learn or understand these excellent solutions, appreciate the ideological charm of those who started the industry, and then translate into their own technical skills, strive to apply to the daily development, improve their research and development level.

Lifecycle+Retrofit+Room

The development structure of Android project, from the original MVC, to the MVP proposed by someone later, and then to the development of MVVM, is nothing more than the continuous decoupling and evolution of the six design principles, making the development of the project highly componentized, to meet the complex and changeable daily project needs.

  • 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)

Currently for the MVVM framework structure, Android official also gave a stable architecture version 1.0.

This article is also around the official thought, trying to summarize the learning experience from the perspective of source code, and finally these controls will be encapsulated twice, more easy to understand the use. It also involves other excellent library, such as Gson, Glide, BaseRecyclerViewAdapterHelper, etc

Cause a.

Last year in the mobile phone guard team do rubbish module, catch up with the modular secondary code refactoring technique requirements, project is divided into multiple processes, including background processes responsible for retrieving data from the DB can upgrade the clouds (DB), and then combined with the cloud configuration information to do relevant logic operation, the data back to the UI process, UI in background threads in the process of secondary packaging, Finally post to the main UI thread Update to the interface to display to the user.

At that time, I communicated with my classmates in the team. Faced with such multi-level and complex data processing logic, I imagined that a mechanism could be developed to bind the UPDATE of UI layer to a data source, so that the update of data source could automatically trigger UI update and realize the decoupling of UI. The data source controls the source of the data. Each source has its own logical layer and shares some underlying common Lib libraries.

After thinking about it, in fact, it was almost the MVVM idea, until Google officially announced the release of Android architecture components 1.0 stable version, I was determined to learn this set of official ideas, feel the excellent architecture.

The official structure diagram is as follows:

Two. Each component library principle and basic usage

Here we mainly explore the basic usage and principle of the main component library, in order to understand its excellent ideas.

Google’s official 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

An Android component lifecycle call-aware control that can sense changes in the lifecycle of an activity or fragment and call back to the corresponding interface.

Can be analyzed from the source abstract class, as follows:

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 {
}
Copy the code

The Lifecycle abstract class has three methods to add and remove observers and to get the current Lifecycle state of the component. The LifecycleObserver interface is just an empty interface for type verification. The specific events are given to OnLifecycleEvent, and the corresponding events are called back by injection.

In the latest support-V4 package, both activities and fragments implement the LifecycleOwner interface. This means that we can directly use getLifecyle() to retrieve the current Activity or Fragment’s Lifecycle object. Very convenient to add our listening method.

@SuppressWarnings({"WeakerAccess"."unused"})
public interface LifecycleOwner {
    @NonNull
    Lifecycle getLifecycle();
}
Copy the code

LiveData

LiveData is a data component that holds a generic type, associating App life components with data, sensing life changes, and calling back data changes with rules.

public abstract class LiveData<T> { private volatile Object mData = NOT_SET; Private static final LifecycleOwner ALWAYS_ON = newLifecycleOwner() {

        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() {
            returnmRegistry; }}; 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); // Final callback method place} 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()); // Can be added as a listener, here traversal to distribute data to each listenerif (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); } // Implement the class callback method protected voidonActive() {// Implement the class callback method protected voidonInactive() {
    }

    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); }}Copy the code

If you look at the source code, you can see that LiveData has an important method observe(LifecycleOwner owner, Observer Observer), which traverses the distribution of data to all listeners when there is a change in the data source, and finally calls back to the onChanged() method.

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

There are two implementation classes for LiveData: MediatorLiveData and MediatorLiveData, which are inherited as follows:

The MutableLiveData class is simple, exposing only two methods: postData() and setData(). The MediatorLiveData class has a **addSource()** method that allows you to listen for changes in one or more LiveData data sources. This allows you to easily implement the logic of multiple data sources with low coupling and associated to one MediatorLiveData. Realize automatic integration of multiple data sources.

    @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(); }}Copy the code

ViewModel

LiveData and LiveCycle bind data and data, data and UI life together, and realize the automatic management and update of data. Then how to cache these data? Can you share this data across multiple pages? The answer is 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 is equivalent to a data isolation layer, which removes all the data logic from the UI layer and manages the acquisition mode and logic of the underlying data.

         ViewModel   viewModel = ViewModelProviders.of(this).get(xxxModel.class);
         ViewModel   viewModel = ViewModelProviders.of(this, factory).get(xxxModel.class);
Copy the code

Can get ViewModel instance through the above way, if you have a custom ViewModel constructor parameters, needed a ViewModelProvider. NewInstanceFactory, realize the create method.

So, how is the ViewMode saved? You can follow the ViewModelProviders source code to see.

    @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;
    }
Copy the code

If you don’t have a get method, you’ll construct a ViewModel using the Factory’s create method, and then put it in the cache for use next time.

Room

Room is an ORM (object relational Mapping) schema database framework. It is an abstract encapsulation of Android SQlite and provides a super convenient way to operate the database.

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

Also based on ORM schema encapsulated database, more famous and GreenDao. Compared with other orms, Room has the advantages of verifying the normalness of query statements at compile time and supporting the return of LiveData. We chose Room more because of the perfect support for LiveData, which can dynamically update DB data changes to LiveData and automatically refresh the UI through LiveData.

Here is a comparison of Room performance on the network:

Room usage:

    1. The abstract class that inherits RoomDatabase exposes the abstract method 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();
 }
Copy the code
    1. Obtaining a DB instance
ppDatabase db = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();
Copy the code
    1. Implement the Dao layer logic
@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);
}
Copy the code
    1. Add a table structure
@Entity
public class User {
    @PrimaryKey
    private int uid;

    @ColumnInfo(name = "first_name") private String firstName; public String date; // Default columnInfo to date}Copy the code

As simple as that, you can realize the operation of the database, completely isolated the underlying complex database operation, greatly save the project research and development of repeated labor.

From the analysis of usage instructions, UserDao and Db are interfaces, and the other is abstract class. The implementation of these logic is completely implemented by annotationProcessor dependency injection, which is actually the official replacement of open-source Android-APT. After compiling the project, you can see the class xxx_imp.class generated in the build directory.

Since Room supports LiveData data, then there can be analyzed under the source code, understand the specific principle, to facilitate later pit filling.

Select the Dao layer insert method in Demo to see how the data is loaded into memory. Our query method is as follows:

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertItem(ZhihuItemEntity products);
Copy the code

AnnotationProcessor code for annotationProcessor

private final RoomDatabase __db; private final EntityInsertionAdapter __insertionAdapterOfZhihuItemEntity; public ZhuhuDao_Impl(RoomDatabase __db) { this.__db = __db; // Anonymous inner class implementation of the EntityInsertionAdapter class,  this.__insertionAdapterOfZhihuItemEntity = new EntityInsertionAdapter<ZhihuItemEntity>(__db) { public StringcreateQuery() {
                return "INSERT OR REPLACE INTO `zhuhulist`(`id`,`date`,`stories`,`top_stories`) VALUES (nullif(? , 0),? ,? ,?) ";
            }

            public void bind(SupportSQLiteStatement STMT, ZhihuItemEntity Value) {// Pass SQLiteStatementbindMethod can be very clever to class object data into the database to operate on the data type. stmt.bindLong(1, (long)value.getId()); // Put the SQLiteStatement objects in sequence.if(value.date == null) {
                    stmt.bindNull(2);
                } else{ stmt.bindString(2, value.date); } / / through the DB class into a custom converter, we can be any object type persisted in the database, and is very convenient from the String database deserialization _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 {// use SQLiteStatement class to operate the databasebindObject type data. this.__insertionAdapterOfZhihuItemEntity.insert(products); this.__db.setTransactionSuccessful(); } finally {// This is important. When we operate a database or stream, we must do a finally block to close the resource. this.__db.endTransaction(); }}Copy the code

You can see from the implementation class that insert is done through the EntityInsertionAdapter class, and the EntityInsertionAdapter holds a SupportSQLiteStatement inside, SQLiteStatement is an abstract wrapper for the SQLiteStatement class. The example fetch is obtained through the RoomData internal method compileStatement().

RoomData abstract class source code:

public abstract class RoomDatabase {
    // setby the generated open helper. protected volatile SupportSQLiteDatabase mDatabase; Private SupportSQLiteOpenHelper mOpenHelper; Private Final InvalidationTracker mInvalidationTracker; // Private Final InvalidationTracker mInvalidationTracker; // Bind the data change listener, Notify LiveData protected Abstract SupportSQLiteOpenHelper createOpenHelper(databaseconfigconfig) when data changes; protected abstract InvalidationTracker createInvalidationTracker(); public Cursor query(String query, @Nullable Object[] args) {returnmOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(query, args)); } public Cursor query(SupportSQLiteQuery query) { assertNotMainThread(); // Check the thread for each database operationreturn 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 doit. 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) {/ / the default SupportSQLiteOpenHelper create factory mFactory = new FrameworkSQLiteOpenHelperFactory (); / / SupportSQLiteOpenHelper implementation class, SQLiteOpenHelper} databaseconfigconfiguration = new DatabaseConfiguration configuration (mContext, mName, mFactory, mMigrationContainer, mCallbacks, mAllowMainThreadQueries, mRequireMigration); / / in the end by reflecting loading system to help us to achieve the real 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) {
        }
    }
    }
Copy the code

DB is obtained by the Build design pattern. During Build, you can add onCreate and onOpen for CallBack data. The *onUpgrade()* method can be called back to the *onUpgrade()* method. How do database upgrades add their own logic? The mystery is in the MigrationContainer class.

    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<>();
            returnfindUpMigrationPath(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); }}Copy the code

In the room.DatabaseBuilder procedure, you can set multiple or one Migration using the *addMigration()* method.

In the onUpgrade() method of RoomOpenHelper, the Migration within the upgrade scope is called in turn:

	@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); }}}}Copy the code

The basic principles of Room have been understood here, and we can encapsulate our own Callback interface, distribute onCreate and onUpgrade methods to business modules in turn, and centrally manage the creation and upgrade of the database.

Retrofit

A very popular and excellent open source network library based on OkHttp.

A type-safe HTTP client for Android and Java

Retrofit is a highly abstract, low-coupling network library. Through various data converters or adapters, the data returned from the network can be converted directly to the desired type. It is highly seamless with the caching and persistence of local data, greatly reducing the development cost. And make the project research and development more modular and iterative upgrade.

The default return type is as follows:

	XXXService service = retrofit.create(XXXService.class);
	Call<List<Repo>> repos = service.listRepos("xxx");
Copy the code
 public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return(T) Proxy.newProxyInstance(service.getClassLoader(), new Class<? >[] { service }, newInvocationHandler() {
          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);
            returnserviceMethod.callAdapter.adapt(okHttpCall); }}); }Copy the code

The retrofit.create method internally links interface methods through Java dynamic proxies, replacing conversion paradigms and return types. Retrofit.builder has two important methods that affect the return value type of the * service.listrepos ()* method and the unordered type. They are:

    /** Add converter factory forPublic Builder addConverterFactory(Convert.factory) public Builder addConverterFactory(Convert.factory) public Builder addConverterFactory(Convert.factory factory) { converterFactories.add(checkNotNull(factory,"factory == null"));
      return this;
    }

    /**
     * Add a call adapter factory for supporting service method returnTypes other than {@link * Call}. */ public Builder addCallAdapterFactory(CallAdapter.Factory Factory)  { adapterFactories.add(checkNotNull(factory,"factory == null"));
      return this;
    }
Copy the code

With the addConverterFactory method, we can directly convert network return data to local concrete entity types, and Retrofit has provided us with a library of common protocol data types, as follows:

Converter Rely on
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

Each Converter added to the Builder is saved in the *List<Converter.Factory>* type List. Convert to the target type with the following code.

 	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 uncheckedreturn(Converter<T, RequestBody>) converter; }}Copy the code

It is also possible to customize the Converter type:

public interface Converter<F, T> { T convert(F value) throws IOException; Public class Converter<ResponseBody,? Public class Converter<ResponseBody,? > responseBodyConverter(Typetype, Annotation[] annotations,
    Retrofit retrofit) {
      returnnull; } // Create a Converter from the custom type to the ResponseBody, return null if it cannot be treated, public Converter<? , RequestBody> requestBodyConverter(Typetype, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit Retrofit) {// implement the concrete transformation logic here} // Retrfofit defaults to calling the toString method for the above annotations public Converter<? , String> stringConverter(Typetype}}, Annotation[] annotations, Retrofit Retrofit) {}}}Copy the code

Retrofit can support handling of return types Java8 or RxJava via the addCallAdapterFactory method (you also need to add Gradle dependencies).

	new Retrofit.Builder()
      .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
      .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 
      .build();
Copy the code

3. Encapsulate and integrate each framework into the project

It mainly uses LiveData to obtain the data and update the page of each framework, integrates the project structure according to the MVVM idea, makes the project structure conform to the architectural diagram suggestion given by the official, builds a logical structure, and makes it more convenient to use each component library.

Build the logical controls required by each business layer in the logical order from top to bottom:

1. Write interface methods that require data initialization or UI updates and update them in the Observer.

viewModel.getEssayData().observe(this, new Observer<Resource<ZhihuItemEntity>>() { @Override public void onChanged(@Nullable Resource<ZhihuItemEntity> EssayDayEntityResource) {/ / data source data changes after the automatic callback the interface, and then update to the UI updateUI (essayDayEntityResource. Data); }});Copy the code

2. Build the ViewModel required by the UI layer

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) {/ / after the initialization, read from the cache 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 voidaddMore(){//TODO: load more}}Copy the code

3. Implement the Repository class to manage data acquisition channels.

Here according to the official know, write an abstract data source class, each time first from the local DB data, and then get network data update to the database, through the LiveData update to the UI layer.

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 ConstantConditionsif (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 VoiddoInBackground(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,
                // whichmay 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() {
        returnresult; }}Copy the code

4. Encapsulate Room database using helper classes

In this paper, the database callback interface is encapsulated twice, which is convenient for unified management of multiple logic modules and multiple databases.

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) {// distribute onCreate interface 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() {// Each database upgrade configuration here can be automatically distributed to each service module onUpgrade() methodreturnnew 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); }}}; }}Copy the code

5. Secondary encapsulation of network library data processing

Define a generic data return interface to facilitate abstract business construction and replace the network request mode.

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() {
                returnresponse.isSuccessful(); }}; result.postValue(requestApi);return result;
    }

Copy the code

Define a *Resource* type packaging unified transfer data, facilitate the unified processing of UI business.

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) {
        returnnew Resource<>(MORE_ADD, data, null); }}Copy the code

The above secondary packaging Demo source code has been uploaded to GitHub, interested students can learn to exchange and Star.

4. To summarize

Reviewing the official framework structure, LivaData personally thinks it is the most important one. It links data with data, data with UI, automatically manages data and decouples multiple business logic, which is an excellent programming idea.

But is LiveData the best fit for android architecture development? Here’s the official response:

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).

The same official did not ignore the excellent RxJava, but because of the personal knowledge of RxJava only to view network information to understand, did not appreciate its power, interested students can understand.

RxJava is reactive programming implemented in the Java language to create event-based asynchronous programs

Great frameworks focus on learning their unique ideas, understanding their basic implementation principles, and then translating them into your own programming ideas. In my opinion, this process is very slow, and only by constantly perceiving different excellent frameworks can qualitative change gradually occur.


Welcome to reprint, please indicate the source: Changxing E station Canking.win