仿B站Android客户端系列(一)项目环境搭建

前言

挖了很久的坑来填。。(゚▽゚)/
上接👉👉👉仿B站Android客户端系列(启动篇)

项目架构

项目的基础框架是在学习的时候不断积累出来的,这个项目主要骨架也是MVP架构+Rxjava+Retrofit+Dagger,这也是技术论坛讨论最多的几个第三方库,下面就来具体说说。

RxJava2

RxJava这两年太火了,导致我没办法不用它。。。。。诶呀这么说太不好了,当然前提是它真的好用,能解决很多问题,优化很多代码,主要是用在处理异步操作。它的操作符十分强大,举个栗子:一个页面由多个请求的数据拼凑而成,如果不同请求相互依赖就需要写额外的逻辑来控制,而如果有RxJava的话一个zip操作符就解决了。随着代码逻辑越来越复杂,它体现的作用就越来越大,而且学习Rx的异步、链式调用思想也是很有帮助的。

Retrofit2+Gson+Okhttp3

这套网络请求组合拳可以说是十分实用了,相信大部分开发者都会选择它们,而且还完美契合上面的RxJava。

Dagger2+Butterknife

依赖注入的两把“利刃”,这俩也是优化代码的神器。Dagger2在刚入门的时候感觉非常难以理解,大多数文章都在说用法和注解的意义,然后一个demo甩脸,让人不明所以。但其实把dagger当做一个帮助我们去管理对象初始化的工具,再去学习用法会好懂一些。本项目里主要用Dagger2结合Retrofit来管理Api和结合泛型对Presenter的注入。

黄油刀没什么好说的,主要用到的功能就是绑定控件id,处理点击事件。用起来很舒服。

Fresco

当下强大的图片加载库无非就是Glide、Picasso、Fresco,这里三个库可以说是各有千秋,但是看过对比分析的文章后显而易见Fresco的B格最高,功能最为强大。再加上工作中Glide用的比较多,这里抱着多接触新技术的目的就选择了Fresco。

Fresco相对其他图片框来说没那么无脑,会复杂一点,从使用包括settings都是这样。如果使用默认设置会出现意料之外的问题。例如如果mBitmapMemoryCacheParamsSupplier使用默认不做设置的话,那么浏览图片列表时内存根本刹不住车。

其他

1.Fragmentation

非常实用的Fragment管理库,这个库把Fragment的大多数常见坑都填平了,用这个可以轻松实现并管理单Activity+多Fragment或是多模块Activity+多Fragment。作者还提供了页面组合的栗子,十分良心。

之前再用的时候觉得唯一的缺点就是Activity和Fragment必须继承库中提供的基类,这样大大降低了可扩展性(当有优先级更高的第三方基类需继承时,就会出现问题,目前良好的设计是通过接口+代理模式的方法实现),但是最近1.0版本中作者已经解决了这个问题。

推荐一下Fragmentation这个库,作者维护很勤,良心。

2.Multitype

Multitype这个也安利下,这是一个封装RecyclerView.Adapter用于实现多类型复杂列表的库,通过作者巧妙地设计可以轻松实现多类型列表。这里是使用的一个栗子:

        mAdapter = new MultiTypeAdapter();
        //注册Class Type和ViewBinder
        mAdapter.register(ImageBean.class, new ImageBinder());
        mAdapter.register(TextBean.class, new TextBinder());
        mAdapter.register(VideoBean.class, new VideoBinder());
        mRecyclerView.setAdapter(mAdapter);
        //设置数据
        Items<Object> items = new Items<>();
        items.add(new ImageBean(R.drawable.image1));
        items.add(new ImageBean(R.drawable.image2));
        items.add(new TextBean("text1"));
        items.add(new TextBean("text2"));
        items.add(new TextBean("text3"));
        items.add(new VideoBean(url));
        mAdapter.setItems(items);
        mAdapter.notifyDataSetChanged();

我们所需要做的工作是注册这个type的数据类型和其对应的binder,将具体的布局实现交由viewBinder,结合布局管理器LayoutManager和ItemDecoration可以很酷炫的实现多类型布局。其中binder里方法的名字与Recycler.Adapter别无二致,功能也大相径庭,如下所示:

public abstract class ItemViewBinder<T, VH extends ViewHolder> {

    ......

    @NonNull
    protected abstract VH onCreateViewHolder(
        @NonNull LayoutInflater inflater, @NonNull ViewGroup parent);

    protected abstract void onBindViewHolder(@NonNull VH holder, @NonNull T item);

    protected final int getPosition(@NonNull final ViewHolder holder) {
        return holder.getAdapterPosition();
    }

    @NonNull
    protected final MultiTypeAdapter getAdapter() {
        return adapter;
    }
}

可以说这样设计代码易读性非常高,而且用起来很灵活,可扩展性高。

3.ActivityLifecycleCallbacks

这不是一个类库,是一个偏结构型的设计。上面再讲Fragmentation时说道Activity和Fragment继承的缺点,优秀的类库设计一般会避免这种问题出现,比如通过接口的方式侧面解决问题。

我在看了这篇讲了ActivityLifecycleCallbacks的文章后也开始使用这种方式构建项目里面的基类。

public final class ActivityLifecycleManager implements Application.ActivityLifecycleCallbacks {
......
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        boolean isIBaseActivity = activity instanceof IBaseActivity;
        boolean isIBaseMvpActivity = activity instanceof IBaseMvpActivity;

        //IBaseActivity 项目中的 Activity 都应实现 IBaseActivity
        if (isIBaseActivity) {
            IBaseActivity iActivity = (IBaseActivity) activity;
            //加载布局
            activity.setContentView(LayoutInflater.from(activity).inflate(iActivity.getLayoutId(), null));
            //创建变量保存实体
            ActivityBean bean = new ActivityBean();
            //绑定布局
            Unbinder unbinder = ButterKnife.bind(activity);
            bean.setUnbinder(unbinder);
            //保存变量
            activity.getIntent().putExtra(EXTRA_ACTIVITY_BEAN, bean);
            //Dagger注入
            iActivity.initInject();
            //IBaseMvpActivity 如果是 MVP Activity 则实现 IBaseMvpActivity
            if (isIBaseMvpActivity) {
                IBaseMvpActivity mvpActivity = (IBaseMvpActivity) activity;
                BaseView view = (BaseView) mvpActivity;
                if (mvpActivity.getPresenter() != null) {
                    mvpActivity.getPresenter().attachView(view);
                }
            }
            //TODO 如有其它特殊 Activity,定义新接口继承IBaseActivity,并在此处理

            //初始化
            iActivity.initViewAndEvent();
        }
        //TODO 第三方的 Activity 如需做公共处理,可在此处添加

    }

    @Override
    public void onActivityStarted(Activity activity) {
        if (activity instanceof IBaseMvpActivity) {
            IBaseMvpActivity mvpActivity = (IBaseMvpActivity) activity;
            mvpActivity.getPresenter().loadData();
        }
    }

    @Override
    public void onActivityResumed(Activity activity) {

    }

    @Override
    public void onActivityPaused(Activity activity) {

    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (activity instanceof IBaseMvpActivity) {
            IBaseMvpActivity mvpActivity = (IBaseMvpActivity) activity;
            mvpActivity.getPresenter().releaseData();
        }
    }

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

    @Override
    public void onActivityDestroyed(Activity activity) {
        if (activity instanceof IBaseActivity) {
            ActivityBean bean = activity.getIntent().getParcelableExtra(EXTRA_ACTIVITY_BEAN);
            bean.getUnbinder().unbind();
            if (activity instanceof IBaseMvpActivity) {
                IBaseMvpActivity iActivity = (IBaseMvpActivity) activity;
                if (iActivity.getPresenter() != null) {
                    iActivity.getPresenter().detachView();
                }
            }
        }
    }

}

在Application中设置就好

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleManager());
    }
    
}

通过ActivityLifecycleCallbacks我们可以完成基类中的功能并且适当解耦,很多公共的逻辑都可以写进这里面,与其他基类并不冲突。并且通过Intent还可以对对象进行存储,比如完成ButterKnife的绑定与解绑。

可以发现完全实现了基类的效果,当然用实现接口+代理的模式也可以解决基类冲突的问题,但这不失为一种优雅地解决方式。

项目地址:FakeBiliBili

还可以的话就赏个star吧!(≧▽≦)/

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