Android Framework Building and Sharing

Keywords: Retrofit Android network Gradle

Preface:
I haven't written an article for a long time. It's almost 8 months since I last wrote an article. I deeply feel how lazy I am. I returned to Guangzhou from Shenzhen in early August to work, join a new team and learn a new way. Record, summarize and share here.

1. Component development

Component development is not a new term, but what is component development? You can see many articles by searching the Internet. In my concept, modular development is to separate many modules, basic modules, business modules and so on. What is the basic module? The basic module is some sdk commonly used by the company. Some encapsulated base classes. The business module is developed based on the basic module. In the past development, I did not really use component development, until joining a new team can be said to open the door to the new world, give me the feeling, component development, thieves, why cool?

I have summed up several points:

1. They are responsible for the independent development of business modules, which are developed by application, and then introduced into the project by library in the later stage.
2. Because each module is independently developed with the smallest module, the compilation speed is fast.
3. Favorable for unit testing, unit testing business modules
4. Reduce coupling and improve module reuse

The following are the basic module packages:


package.png

Whole project structure:


Android Framework Module Distribution Map. png

Android studio:


Picture 2.png

In gradle.properties, we can set a variable to control whether modularity is used for development.

#Whether to use modular development or not
isModule=false

Then set up the project introduction package in settings.gradle

//Open the base module by default
include ':sdk', ':model', ':widget', ':module-basic'
//Introduce corresponding modules according to their own responsibility.
include ':module-user'
include ':module-business'
//According to whether the module is developed or not, whether the app module is introduced or not
if (!isModule.toBoolean()) {
    include ':app'
}

Business module gradle for module judgment


Picture 3.png
//Set the application or library by using the previously set variables
if (isModule.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

According to the structure diagram, we introduce sdk, model, widget, module-baisc by default.
Then according to the business module I am responsible for, I introduce different business. If I am responsible for the user module, I only need to introduce the user module in the development. In this way, when developing each module, I can improve the compilation efficiency of each module.

Finally, when the modules are merged, the module development is closed in gradle.properties, the corresponding module package of the project is introduced in settings.gradle, and the build-gradle of app is set up:



Picture 4.png

build-gradle:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:design:26.+'
    testCompile 'junit:junit:4.12'

    //If you don't use modular development, introduce all business modules
    if (!isModule.toBoolean()) {
        compile project(':module-business')
        compile project(':module-user')
    }
}

Now, the question is how to jump the activity of different modules. In the past, I used to write a static method in each activity and set the input parameters.

/**
 * Jump
 *
 * @param context context
 * @param param   parameter
 */
public static void toAcitivty(Context context, String param) {
    Intent intent = new Intent(context, MainActivity.class);
    intent.putExtra("param", param);
    context.startActivity(intent);
}

Because using modular development, different business modules can not call their activities, so we use Ali Router.
Annotations are used to jump in each activity header, just like Spring mvc controller, routing is used to set jumps. In modular development, this is critical. On the one hand, using around can reduce the coupling between activities, on the other hand, unit testing can be carried out on modules.

The specific use of Arouter:
https://github.com/alibaba/ARouter

2.Retrofit+RxJava+MVP mode

Regarding Retrofit and RxJava, specific and detailed usage is not introduced here. There are many existing articles on the Internet. Why use Retrofit and RxJava? Retrofit is based on Okhttp to encapsulate a layer of client, cooperate with RxJava thread scheduling, and control network requests well. Using RxJava can improve the readability of code. Here I share retrofit+Rxjava encapsulation.

1. Api Factory Based on Retrofit

ApiFactory is shown below:


api factory. png

The function of ApiFactory in the figure is to provide all business Api interfaces. The specific Api is to provide each business interface through the interface ApiProvider. If the user interface, transaction interface, token interface, etc., ApiFactory obtains the API provider by singleton. The specific implementation class of ApiProvider, ApiProvideImpl, inherits from the network engine Retrofit Api, and Retrofit Api is used to initialize some networks. Engine. ApiProvideImpl uses retrofit to initialize various Api interfaces.

ApiProviderImpl.java:

class ApiProviderImpl extends RetrofitApi implements ApiProvider {

    private OkHttpClient httpClient;

    ApiProviderImpl(Context applicationContext) {
        super(applicationContext);
    }

    private <T> T create(Class<T> cls) {
        return mRetrofit.create(cls);
    }


    @Override
    public ITokenApi getTokenApi() {
        return create(ITokenApi.class);
    }

    @Override
    public IUserApi getUserApi() {
        return create(IUserApi.class);
    }

    @Override
    public IProductApi getProductApi() {
        return create(IProductApi.class);
    }

    @Override
    public IPushApi getPushApi() {
        return create(IPushApi.class);
    }

    @Override
    public IQuotationApi getQuotationApi() {
        return create(IQuotationApi.class);
    }

    @Override
    public ITradeApi getTradeApi() {
        return create(ITradeApi.class);
    }

    .....
}

2.MVP

mvp can be decoupled and its structure is clear. for complex business scenarios, it can improve code readability, structure is clear, and reduce maintenance costs in the later stage. As shown in the login module below:


mvp.png

View and presenter are abstracted into interfaces, which are independent of each other's details, easy to do unit testing and reduce coupling. There are two basic interfaces, LoginView and LoginPresenter, which are inherited from IView and IPresenter, LoginViewImpl and LoginPresenterImpl, respectively, to implement LoginView and LoginPresenter, which depend on abstraction and do not depend on implementation details.

/**
 * Login contract class
 */
public interface LoginContract {

    /**
     * Presentation Layer Interface
     */
    interface Presenter extends IPresenter {

        /**
         * Login operation
         */
        void login();
    }

    /**
     * View Layer Interface
     */
    interface View extends IPresenterView {

        /**
         * Get password
         *
         * @return return
         */
        String getPassword();

        /**
         * Getting User Information
         *
         * @return return
         */
        String getUsername();

        /**
         * Login successfully
         */
        void loginSuccess();

        /**
         * Login failed
         *
         * @param msg msg
         */
        void loginFailed(String msg);
    }
}

By defining a Contract contract class, we can formulate the interface. While defining the interface between Presenter and View, we can clearly know what the presentation layer needs and what the view layer needs to provide, including the corresponding response after the network request, so that when we make a business logic, we can have a clearer idea, colleagues can reuse presenter and units. Testing will be more convenient.

3. Combining Retrofit+RxJava+Mvp

Combining the Api and mvp mentioned before, we encapsulate the basic class of Presenter implementation on this basis.

/**
 * presenter Encapsulation of basic implementation classes
 * 1.Binding and unbinding with view view
 * 2.Processing rx events and releasing resources
 */
public class BasicPresenterImpl<T extends IPresenterView> implements IPresenter {

    /**
     * view
     */
    protected T mView;

    /**
     * context
     */
    protected Context mContext;

    /**
     * Record identification, used to identify all tasks in this presenter
     */
    private String mTag = this.getClass().getName();

    public BasicPresenterImpl(Context context, T view) {
        this.mView = view;
        this.mContext = context;
    }

    public void start() {
    }

    /**
     * Destroy resources, usually for view unbinding operations
     * For example, when an activity is destroyed as a view, it is called
     * Avoid incorrect references and memory leaks
     */
    public void destroy() {
        this.mView = null;
        this.mContext = null;
        this.cancelRequest();
    }

    /**
     * Remove tasks according to tag, such as uncompleted network requests
     */
    protected void cancelRequest() {
        RxObservable.dispose(this.mTag);
        RxObservable.dispose("PageDataObservable");
    }


    /**
     * rxJava  Most are used to create network requests
     * For example, create Observable (mUser. login ())
     * retorfit Combined with rxJava
     *
     * @param observable observable
     * @param <T>        t
     * @return return
     */
    protected <T> Observable<T> createObservable(Observable<T> observable) {
        //Create tasks
        return RxObservable.create(observable, this.mTag);
    }
}

Basic Presenter encapsulates the operation of binding and unbinding. When presenter unbinding with view, it calls destory to release resources, and clears all the events handled by rxJava in this presenter, releasing resources, such as some network requests. When the network requests are unbinding with presenter, the operation of view empty pointer is easy to occur.

Then we introduce the encapsulation of RxObservable:

/**
 * Used to encapsulate rx events and save tasks by key-value pairs
 * Initialization and release of tasks
 */
public final class RxObservable {

    /**
     * Global variables, using tag identifiers to save Disposable collections
     * Disposable?Observer(Observer disconnects from Observable
     */
    private static final Map<String, List<Disposable>> sObservableDisposableList = new WeakHashMap();

    public RxObservable() {
    }

    /**
     * Create network requests returned by observers, such as retrofit collection rxJava,
     * This method is used to process the event at initialization and save the event to the sObservableDisposableList collection.
     * With tag as key and List < Disposable > as value, you can get Disposable when subscribing to an observer
     */
    public static <T> Observable<T> create(Observable<T> observable, final String tag) {
        return observable.doOnSubscribe(new Consumer() {
            public void accept(@NonNull Disposable disposable) throws Exception {
                //Judging the existence of a set in a set
                //If not, create it and save it in sObservable Disposable List with key-tag
                List disposables = (List) RxObservable.sObservableDisposableList.get(tag);
                if (disposables == null) {
                    ArrayList disposables1 = new ArrayList();
                    RxObservable.sObservableDisposableList.put(tag, disposables1);
                }
                //Add Disposable for this event to the set of corresponding Tags
                ((List) RxObservable.sObservableDisposableList.get(tag)).add(disposable);
            }
            //The subscription process is processed in the Io thread and sent to the main thread for processing.
        }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
    }

    /**
     * Release all resources
     */
    public static void dispose() {
        try {
            Iterator e = sObservableDisposableList.values().iterator();
            while (e.hasNext()) {
                List disposables = (List) e.next();
                Iterator var2 = disposables.iterator();

                while (var2.hasNext()) {
                    Disposable disposable = (Disposable) var2.next();
                    if (disposable != null && !disposable.isDisposed()) {
                        disposable.dispose();
                    }
                }

                disposables.clear();
            }
        } catch (Exception var7) {
            Log.e("rae", "release HTTP The request failed!", var7);
        } finally {
            sObservableDisposableList.clear();
        }

    }

    /**
     * Release resources according to tag identity
     *
     * @param tag tag
     */
    public static void dispose(String tag) {
        try {
            if (!TextUtils.isEmpty(tag) && sObservableDisposableList.containsKey(tag)) {
                List e = (List) sObservableDisposableList.get(tag);
                Iterator var2 = e.iterator();

                while (var2.hasNext()) {
                    Disposable disposable = (Disposable) var2.next();
                    if (disposable != null && !disposable.isDisposed()) {
                        disposable.dispose();
                    }
                }

                e.clear();
                sObservableDisposableList.remove(tag);
                return;
            }
        } catch (Exception var7) {
            Log.e("rae", "release HTTP The request failed!", var7);
            return;
        } finally {
            sObservableDisposableList.remove(tag);
        }

    }
}

In RxObservable, create a sObservable Disposable List to save the events processed in each presenter. Create it by tag as an identifier. Each presenter will find the corresponding Disposable set by tag. Disposable set saves all tasks in this presenter, such as network requests, io operations, etc. Through this method, the task of tag can be managed uniformly. When nter unbinds, it can destroy resources in time to avoid memory leak.

A small example of login:

public class LoginPresenterImpl extends BasicPresenterImpl<LoginContract.View> implements LoginContract.Presenter {

    IUserApi mUserApi;

    public LoginPresenterImpl(Context context, LoginContract.View view) {
        super(context, view);
        //Initialize variables...
    }

    @Override
    public void login() {
        //Get the phone number and password in view layer
        final String mobile = mView.getMobile();
        final String password = mView.getPassword();
        if (TextUtils.isEmpty(mobile)) {
            mView.onLoginFailed("Please enter your cell phone number.");
            return;
        }
        if (TextUtils.isEmpty(password)) {
            mView.onLoginFailed("Please input a password");
            return;
        }
        if (!mPhoneValidator.isMobile(mobile)) {
            mView.onLoginFailed("Please enter the correct mobile phone number.");
            return;
        }
        createObservable(mUserApi.login(mobile, password)).subscribe(new ApiDefaultObserver<UserInfo>() {
            @Override
            protected void onError(String msg) {
                //Login failed
                mView.onLoginFailed(msg);
            }

            @Override
            protected void accept(UserInfo userInfo) {
                //Successful login and other operations
            }
        });
    }

    
}

Anyway:

This article is used to record learning experience and some gains. Thank teammates Keegan Xiaogang and Rae for their help. Interested friends can follow their blogs and learn a lot about the stack.

For me, blogging is like writing a diary. When I came back to Guangzhou, I found that I felt very full. Although the team was not as efficient as 996 in Shenzhen in the past, the research and development efficiency of the whole team was very high. A very dynamic team still felt that work, efficient communication, efficient work, happy work and happy life.

Keegan small steel

http://keeganlee.me/
Wechat Subscription Number: keeganlee_me

Rae

http://www.raeblog.com

Posted by moreshion on Sun, 26 May 2019 14:23:54 -0700