Android项目基类封装ViewBinding、MVP、ViewModel

都会需要有用到BaseActivity,从最开始的initData、initView,到后来需要承载监听推送、监听网络变化的广播、延迟锁定等等各种需求,BaseActivity的功能越来越杂,越来越密集。相对实际的页面上的功能需求,基类的封装经过这样长时间的无脑堆砌,到最后看起来会更匪夷所思。所以从一开始,Base的封装就要足够清晰、稳健、可扩展。
AndroidBase
我的思路是分层继承,每一层只做和这一层功能相关的事,变动修改单一功能都是清晰的,同时在最后的一层功能又是完整的。
AppCompatActivity
             |
BaseViewActivity
             |
BaseFunctionsActivity
             |
BaseViewModelActivity
             |
BaseMVPActivity
             |
BaseToolbarActivity

1.BaseViewActivity(View层)

主要代码:

  public abstract class BaseViewActivity<VB extends ViewBinding> extends AppCompatActivity
{
    private View rootView;
    protected VB vBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(getScreenOrientation());//竖屏
        vBinding = ViewBindingCreator.createViewBinding(getClass(), getLayoutInflater());
        rootView = generateContentView(vBinding == null ? getContentView() : vBinding.getRoot());
        setContentView(rootView);
    }

    protected View getContentView()
    {
        return null;
    }

    protected View generateContentView(View contentView)
    {
        return contentView;
    }

    protected int getScreenOrientation()
    {
        return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
    }
    public void showLoadingDialog()
    {
    }
    public void hideLoadingDialog()
    {
    }
}

ViewBindingCreator主要代码:

@SuppressWarnings("unchecked")
public static <VB extends ViewBinding> VB createViewBinding(Class targetClass,
                                                                LayoutInflater layoutInflater)
    {
        Type type = targetClass.getGenericSuperclass();

        if (type instanceof ParameterizedType)
        {
            try
            {
                Type[] types = ((ParameterizedType) type).getActualTypeArguments();

                for (Type type1 : types)
                {
                    if (type1.getTypeName()
                             .endsWith("Binding"))
                    {
                        Method method = ((Class<VB>) type1).getMethod("inflate",
                                                                      LayoutInflater.class);
                        return (VB) method.invoke(null, layoutInflater);
                    }
                }

            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        } return null;
    }
public abstract class BaseViewFragment<VB extends ViewBinding> extends Fragment
{

    protected VB vBinding;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState)
    {
        if (vBinding == null)
        {
            vBinding = ViewBindingCreator.createViewBinding(getClass(), inflater);
            View rootView = generateContentView(
                    vBinding == null ? getContentView() : vBinding.getRoot());
            rootView.setBackgroundColor(getResources().getColor(R.color.colorBackgroundTint));
            onCreateView(rootView);
        }
        return vBinding.getRoot();
    }

    protected abstract void onCreateView(View rootView);

    protected View getContentView()
    {
        return null;
    }

    protected View generateContentView(View contentView)
    {
        return contentView;
    }

    protected void setStatusBarTextDark(boolean isStatusBarTextDark)
    {
        Activity activity = getActivity();
        if (activity instanceof BaseViewActivity)
        {
            ((BaseViewActivity) activity).setStatusBarTextDark(isStatusBarTextDark);
        }
    }

    protected void showLoadingDialog()
    {
        Activity activity = getActivity();
        if (activity instanceof BaseViewActivity)
        {
            ((BaseViewActivity) activity).showLoadingDialog();
        }
    }

    protected void hideLoadingDialog()
    {
        Activity activity = getActivity();
        if (activity instanceof BaseViewActivity)
        {
            ((BaseViewActivity) activity).hideLoadingDialog();
        }
    }
}

继承自AppCompatActivity不用说什么了。用ViewModel也是因为之前用ButterKnife,更新了Android Studio4.0之后提示R.id.xxxxx不再是静态常量,所以推荐使用ViewBinding的方式,ButterKnife官方也说了不再维护,转用ViewBinding。相对ButterKnife就是少了那个对输入框 点击事件的注解方式写法。我在另一个文章吐槽过。也还行吧,官方都推荐了 ,应该不会有太大问题。BaseViewActivity内主要通过ViewBinding或者getContentView获取页面内容View,优先看有没有ViewBinding,没有的话再用getContentView。ViewBinding这块用了反射去调inflate方法来创建对象。反射是不好,但是为了可以偷懒避免在每个具体xxxActivity里都写一遍xxxViewBinding.inflate,只需要在泛型声明一下xxxViewBinding就可以了。还提供了一个generateContentView方法,便于子类重载后扩充contentView,例如BaseToolbarActivity就是实现了这个方法,对contentView进一步包装后再返回。
在这个类内也可以封装loadingDialog,Toast什么有关View的基础功能。
同时在写具体业务的Activity或者Fragment时,不需要重载什么新的方法,只需要传一个泛型ViewBinding就可以了,减少写代码的负担。

2.BaseFunctionsActivity(基础功能层)

主要代码:

public abstract class BaseFunctionsActivity<VB extends ViewBinding> extends BaseViewActivity<VB>
{
    public static ArrayList<Class<? extends BaseActivityFunction>> functionsClasses = new ArrayList<>();

    private LinkedHashMap<String, BaseActivityFunction> functions = new LinkedHashMap<>();

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        for (Class<? extends BaseActivityFunction> functionClass : functionsClasses)
        {
            try
            {
                functions.put(functionClass.getName(), functionClass.newInstance());
            }
            catch (IllegalAccessException | InstantiationException e)
            {
                e.printStackTrace();
            }
        }

        for (BaseActivityFunction baseActivityFunction : functions.values())
        {
            baseActivityFunction.onActivityCreated(this, savedInstanceState);
        }
    }

    @Override
    protected void onNewIntent(Intent intent)
    {
        super.onNewIntent(intent);
    }

    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState)
    {
        super.onSaveInstanceState(outState);
        for (BaseActivityFunction baseActivityFunction : functions.values())
        {
            baseActivityFunction.onActivitySaveInstanceState(this, outState);
        }
    }

    @Override
    protected void onStart()
    {
        super.onStart();
        for (BaseActivityFunction baseActivityFunction : functions.values())
        {
            baseActivityFunction.onActivityStarted(this);
        }
    }

    @Override
    protected void onRestart()
    {
        super.onRestart();
        for (BaseActivityFunction baseActivityFunction : functions.values())
        {
            baseActivityFunction.onActivityRestarted(this);
        }
    }

    @Override
    protected void onResume()
    {
        super.onResume();
        for (BaseActivityFunction baseActivityFunction : functions.values())
        {
            baseActivityFunction.onActivityResumed(this);
        }
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        for (BaseActivityFunction baseActivityFunction : functions.values())
        {
            baseActivityFunction.onActivityPaused(this);
        }
    }

    @Override
    protected void onStop()
    {
        super.onStop();
        for (BaseActivityFunction baseActivityFunction : functions.values())
        {
            baseActivityFunction.onActivityStopped(this);
        }
    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        Iterator<BaseActivityFunction> iterator = functions.values()
                                                           .iterator();
        while (iterator.hasNext())
        {
            BaseActivityFunction baseActivityFunction = iterator.next();
            baseActivityFunction.onActivityDestroyed(this);
            baseActivityFunction = null;
            iterator.remove();
        }
        functions = null;
    }

    @Override
    public void onBackPressed()
    {
        boolean canBackPressed = true;
        for (BaseActivityFunction value : functions.values())
        {
            if (!value.onBeforeBackPressed(this))
            {
                canBackPressed = false;
            }
        }
        if (canBackPressed)
        {
            super.onBackPressed();
        }
    }

    @Override
    public void finish()
    {
        super.finish();
        for (BaseActivityFunction value : functions.values())
        {
            value.onFinish(this);
        }
    }

    public BaseActivityFunction getFunction(Class<? extends BaseActivityFunction> fClass)
    {
        return functions.get(fClass.getName());
    }
}
public abstract class BaseActivityFunction implements Application.ActivityLifecycleCallbacks
{
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState)
    {
    }

    @Override
    public void onActivityStarted(Activity activity)
    {
    }

    public void onActivityRestarted(Activity activity)
    {
    }

    @Override
    public void onActivityResumed(Activity activity)
    {
    }

    @Override
    public void onActivityPaused(Activity activity)
    {
    }

    @Override
    public void onActivityStopped(Activity activity)
    {
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState)
    {
    }

    @Override
    public void onActivityDestroyed(Activity activity)
    {
    }

    public void onFinish(Activity activity)
    {
    }

    public boolean onBeforeBackPressed(Activity activity)
    {
        return true;
    }
}

因为在很多场景,都需要基类Activity具有一些功能,检查是否登录,监听网络变化,嵌入友盟推送等等。本基类封装库又是一个完整的个体,所以就会出现尴尬的情况,基类是A->B->C->D->XXXXX这样的继承关系,但是我又需要在B和C之间插入一个继承B1,所以基于此需求,BaseFunctionsActivity通过实现类似ActivityCallback的方式,提供给外部嵌入内部的机会,通过继承BaseActivityFunction来实现不同功能模块。
比如我想让整个应用的所有Activity能监听网络变化,并Toast提示到界面上,我就只需要创建一个NetChangeActivityFunction,实现监听网络变化的广播,把NetChangeActivityFunction.class添加到BaseFunctionsActivity的静态functionsClasses中,这样每个Activity就具有了NetChangeActivityFunction的功能。如果某一个具体Activity想调用NetChangeActivityFunction中的方法时,用getFunction方法可以获取到当前Activity里已经实现的BaseActivityFunction具体对象,就可以调用到方法,变量同理。
同时也提供了对onBackPressed的拦截,因为有很多场景,需要在onBackPressed时判断一些逻辑,再决定是否继续执行。这块的重点设计的逻辑是,如果一个Activity有多个Function,只要其中有一个Function判定为false就不会执行super.onBackPressed();但是每一个Function的onBackPressed都会执行一次。暂时考虑对每个Function都公平一点,避免出现到某一个Function的onBackPressed就终止了后面的执行。
这样的在一个Activity里注入多个Function的方式有一个缺点就是每一个Activity都承载了多个的Function对象,如果BaseFunctions很多的话,相对直接继承多层Activity的方式,会占用更多内存。

3.BaseViewModelActivity(ViewModel层)

主要代码:

public abstract class BaseViewModelActivity<VB extends ViewBinding> extends BaseFunctionsActivity<VB>
{
    protected final void setViewModelHolder(@NonNull IViewModelHolder iViewModelHolder)
    {
        iViewModelHolder.setIViewModelOwners(new IViewModelOwners()
        {
            @Override
            public ViewModelStoreOwner getActivityViewModelStoreOwner()
            {
                return BaseViewModelActivity.this;
            }

            @Override
            public ViewModelStoreOwner getFragmentViewModelStoreOwner()
            {
                return null;
            }

            @Override
            public LifecycleOwner getActivityLifecycleOwner()
            {
                return BaseViewModelActivity.this;
            }

            @Override
            public LifecycleOwner getFragmentLifecycleOwner()
            {
                return null;
            }
        });
    }
} 
public interface IViewModelHolder
{
    void setIViewModelOwners(IViewModelOwners iViewModel);

    void onViewModelLoaded();
} 
public interface IViewModelOwners
{
    ViewModelStoreOwner getActivityViewModelStoreOwner();

    ViewModelStoreOwner getFragmentViewModelStoreOwner();

    LifecycleOwner getActivityLifecycleOwner();

    LifecycleOwner getFragmentLifecycleOwner();
} 
public class BaseViewModelHolder implements IViewModelHolder
{
    private IViewModelOwners iViewModelOwners;

    private ViewModelProvider activityViewModelProvider;
    private ViewModelProvider fragmentViewModelProvider;

    @Override
    public final void setIViewModelOwners(IViewModelOwners iViewModel)
    {
        this.iViewModelOwners = iViewModel;
        onViewModelLoaded();
    }

    @Override
    public void onViewModelLoaded()
    {

    }

    protected final <VM extends ViewModel> VM getActivityViewModel(Class<VM> vmClass)
    {
        if (activityViewModelProvider == null)
        {
            activityViewModelProvider = new ViewModelProvider(
                    iViewModelOwners.getActivityViewModelStoreOwner(),
                    new ViewModelProvider.NewInstanceFactory());
        }
        return activityViewModelProvider.get(vmClass);
    }

    protected final <VM extends ViewModel> VM getFragmentViewModel(Class<VM> vmClass)
    {
        if (fragmentViewModelProvider == null)
        {
            fragmentViewModelProvider = new ViewModelProvider(
                    iViewModelOwners.getFragmentViewModelStoreOwner(),
                    new ViewModelProvider.NewInstanceFactory());
        }
        return fragmentViewModelProvider.get(vmClass);
    }

    protected final <T> void observeActivityLiveData(LiveData<T> liveData, Observer<T> observer)
    {
        liveData.observe(iViewModelOwners.getActivityLifecycleOwner(), observer);
    }

    protected final <T> void observeFragmentLiveData(LiveData<T> liveData, Observer<T> observer)
    {
        liveData.observe(iViewModelOwners.getFragmentLifecycleOwner(), observer);
    }
}

ViewModel这块也是尝试了很多种方式,目前保持一个还算合适的状态,但是总感觉也不是最好的状态。因为对ViewModel和LiveData的学习也是近期才开始的,没有太多的应用场景可能理解的不是完全到位。
ViewModel层整体的思路是,因为ViewModel需要ViewModelStore才能创建,LiveData又需要LifeCycle,这两个需要都集中在ComponentActivity和Fragment这两个宿主中,但是ViewModel的创建又不能直接持有宿主的引用,ViewModel的使用场景绝大部分又应该都是在Presenter的内部。在一个宿主中可能存在多个ViewModel,ViewModel就要发源于宿主,在Presenter中被引用和使用。还有个限制是不希望ViewModel层高于MVP层,因为希望MVP层是具体使用场景中比较主要的,整体的架构还是围绕MVP的,ViewModel相关的只是附加提供的功能。
因为Activity和Fragment都具有ViewModelStoreOwner和LifeCycleOwner的属性,所以把他们两个类里总计四个属性:ActivityViewModelStoreOwner、ActivityLifeCycleOwner、FragmentViewModelStoreOwner和FragmentLifeCycleOwner抽象为一个新的对象IViewModelOwners(这个名字不太妥,之后得改)。这样的新对象就是脱离了Activity和Fragment的限制的,是超越了他们的。为什么一定要拆分成四个方法而不是两个,是因为有的时候需要在Fragment里用到Fragment所附着的Activity的ViewModel,所以只能拆分开。
在BaseViewModelHolder中,通过IViewModelOwners接口在Activity和Fragment中获取到ViewModelStoreOwner和LifeCycleOwner,可以创建出ViewModelProvider,再通过对应的activityViewModelProvider或者fragmentViewModelProvider创建出需要的ViewModel。LiveData的观测也是发在BaseViewModelHolder中。之后让BasePresenter继承自BaseViewModelHolder就可以获得到对应的创建ViewModel和观测LiveData功能了。

BaseMVPActivity(MVP层)

主要代码:

public abstract class BaseMVPActivity<Presenter extends BaseContract.Presenter, VB extends ViewBinding> extends BaseViewModelActivity<VB> implements BaseContract.View

{
    protected Presenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        presenter = createPresenter();
        if (presenter != null)
        {
            setViewModelHolder(presenter);
        }
    }

    @Override
    public void showLoading()
    {
        showLoadingDialog();
    }

    @Override
    public void hideLoading()
    {
        hideLoadingDialog();
    }

    @Override
    public void message(String message)
    {
        ToastUtil.toast(message);
    }

    protected Presenter createPresenter()
    {
        return null;
    }

    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        if (presenter != null)
        {
            presenter.detachView();
        }
    }
}
public interface BaseContract
{
    interface View
    {
        void showLoading();

        void hideLoading();

        void message(String message);
    }

    interface Presenter extends IViewModelHolder
    {
        boolean viewNotNull();

        void detachView();
    }
}
public abstract class BasePresenter<View extends BaseContract.View> extends BaseViewModelHolder implements BaseContract.Presenter
{
    protected View view;

    protected BasePresenter(View view)
    {
        this.view = view;
    }

    @Override
    public boolean viewNotNull()
    {
        return this.view != null;
    }

    @Override
    public void detachView()
    {
        this.view = null;
    }

    protected <VM extends ViewModel> VM getViewModel(Class<VM> vmClass)
    {
        if (view instanceof Fragment)
        {
            return getFragmentViewModel(vmClass);
        }
        return getActivityViewModel(vmClass);
    }

    protected <T> void observeLiveData(LiveData<T> liveData, Observer<T> observer)
    {
        if (view instanceof Fragment)
        {
            observeFragmentLiveData(liveData, getMVPObserver(observer));
        }
        else
        {
            observeActivityLiveData(liveData, getMVPObserver(observer));
        }
    }

    private <T> Observer<T> getMVPObserver(Observer<T> observer)
    {
        return t -> {
            if (viewNotNull())
            {
                observer.onChanged(t);
            }
        };
    }
}

MVP的封装还是比较基础的,BaseContract内含View和Presenter的接口,Activity持有Presenter的引用,Presenter持有View的引用,同时BasePresenter继承了BaseViewModelHolder,也就有了M层的功能,但是又相对BaseContract的这一套是独立的,ViewModel相关并不与MVP强相关。同时对获取ViewModel做了进一步封装,减少BasePresenter子类使用ViewModel时的判断逻辑。BaseMVPActivity还是和BaseViewActivity一样,在继承没有强制需要实现的方法,增加了Presenter的泛型,自主去实现createPresenter方法,实现了之后自然就需要让Activity实现XXXContract.View的接口。
举例创建一个Activity:通过在文件夹右键单击 New->Activity->Empty Activity创建一个TestActivity,自动创建好后只需要把继承的AppCompatActivity修改为BaseMVPActivity(也可以根据需要继承自BaseToolbarActivity)。这时都不需要实现任何抽象方法,只需要在BaseMVPActivity上配置泛型就可以,但是Presenter的创建还是要主动实现一个父类的createPresenter()方法。
这样写起代码来都是顺畅的,比如要新建一个页面,就是先新建一个XXXContract类,想好这个页面可能会有那些功能和回调,去定义View和Presenter的方法,然后新建一个XXXPresenter类实现XXXContract.Presenter接口,可以暂时不写具体功能,然后再创建一个XXXActivity,把XXXViewBinding和XXXPresenter泛型配置好。这时候可以先去写XXXViewBinding的布局页面,也可以先写XXXPresenter中的业务逻辑。脑袋里想好要改页面的东西,那就去关注xml布局,想改业务逻辑就去XXXModel或者XXXPresenter,相互之间都不干扰。git提交时也可以一眼看出来这次提交是修改了业务还是布局,减少出错。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容