一次独立App的尝试

本文时间久远,部分内容可能过时,仅供参考。

这是我尝试做的第一款比较完整的软件,厌倦新浪微博官方App的广告骚扰和后台自启,不如为自己写一个轻量微博客户端,实现刷微博的功能而做到“召之即来,挥之即去”的简洁体验。涉及OAuth认证、加载网络媒体资源、数据缓存和界面控制,我第一次遇到一些之前没有想过的问题,也第一次经历重构,是很有意义的实践。

weibo

weibo

weibo

weibo

weibo

weibo

weibo

weibo

OAuth2授权

安全原因,用户不会轻易把密码交给第三方客户端,需要一种向客户端授权的可信机制,正是OAuth2要解决的问题。

OAuth2协议中,客户端不持有用户名和密码,只保存一个代表用户的accessToken,调用接口时以此验证身份。具体流程是,客户端WebView访问微博授权网址并提交微博给第三方客户端的授权信息,用户在此网页中输入账号、密码验证身份,然后微博服务器会返回一个授权码,客户端再用此授权码换取accessToken。一旦换取成功,客户端就可以在没有用户账号和密码的情况下用token访问微博信息。

HTTP

超文本传输协议,微博提接口都是常规GETPOST,十分清晰。

我在初期使用HttpUrlConnection线程池构建自己的网络访问框架,实现的很简单,对外提供接口,将任务放到线程池中执行并在完成后回调,也会结合Handler实现在UI线程中回调。解析JSON数据则使用原始JSONObjectJSONArray,将获取的微博数据填充到对应Object后加载给RecyclerView显示,同时在本地保存以离线使用。

图片异步加载

图片异步加载是两个问题,UI线程中更新UIRecyclerView的子View复用。

前者很好解决,HandlerAsyncTask都可以实现,后者问题表现为图片错位,即设置的图片和实际显示的图片不一致。根源是RecyclerView复用子View,因为图片在异步线程中下载需要时间,当下载完成后原View可能已经滚出屏幕,并被当作下一个ItemView进行显示。同一个View,位置却变了,自然会出现图片错位。解决方法是在绑定数据和启动下载任务的onBindViewHolder中为ItemView设置Tag标记,图片下载完成后取出Tag判断和之前是否一致,不一致即说明ItemView已被复用,不必再为它加载这个图片。

开发初期,我尝试创建自己的ImageLoader,使用MemoryLruCacheDiskLruCache为图片建立两级缓存,封装异步加载功能。只是后来遇到的图片问题越来越多,比如GIF动图,全部自己造轮子并不现实,最终选择Glide,既方便也安全。

Activity滑动退出

这个问题的本质是如何控制当前Activity的整个View进行滑动,并在结束滑动时退出Activity

思路并不复杂,DecorView是整个View树的根节点,它之下有一个竖直方向的LinearLayout,包含TitleViewContentView。要做的是将一个ViewGroup插入到DecorViewLinearLayout之间,用它监听触控事件,调用父ViewDecorViewscrollBy()方法移动整个DecorViewContent,包括状态栏背景,当移动到一定的距离或者速度达到一定值的时候开启动画并退出此Activity。注意Window背景要设为透明色,否则看不到此Activity之下的Activity

TextView显示链接和图片

之前一直不知道TextView可以嵌入链接和图片,还一直好奇表情图是怎么显示在微博里的,其实就是将普通String转换为SpannableString,在指定位置显示指定格式。

比如格式为ClickableSpan链接,ImageSpan图片,也可以为其定义点击事件。使用时只需配合正则表达式正确替换字符串中的指定字符。

后台服务定时轮询

我对是否加入后台服务有点犹豫,很多人只是空闲时刷微博看看别人都说了些什么,除私信外应该不会喜欢微博主动在通知栏弹出消息。但考虑到功能完整性,我还是做了一个基本的后台推送放在设置里选择是否开启。

实现十分直白,在ServiceonStartCommand()方法中定义一个AlarmManager定时服务,让它每隔一段时间发送一条广播。定义BroadcastReceiver接收广播并启动Service,其onStartCommand()方法会被执行,在这里开启子线程执行网络查询任务,有新消息就弹出Notification,完成任务后关闭服务等待下一次唤醒。

主题切换

Android官方的切换主题方案是通过setTheme()方法,它可以加载本地定义的Style作为整个ActivityThemeView样式随主题的切换而不同。只是这些样式值必须已经在Theme中存在,且setTheme()方法必须在Activity加载View之前调用,即在setContentView()之前。

这种方式会触发Activity重新创建,必须注意各个组件在重建时的数据恢复,做到对用户而言的无感切换。

适配不同屏幕尺寸

我适配了手机和平板两种类型的设备,软件运行时根据不同屏幕尺寸加载对应布局文件,配合RecyclerView线性布局瀑布流布局,在手机和平板的横屏、竖屏情况下都可以呈现美观的用户界面。

重构

这是我做的第一个综合性软件,集网络、异步、图片、服务、本地存储于一体,刚开始真的一头雾水,没有“低耦合、高内聚”的概念,只要能实现功能就写到哪算哪。在这个过程中逐渐意识到架构的重要性,因为我不知道应该如何安排这些代码的结构,看起来把它们放到哪里都可以运行。

经过几天尝试,在完成软件的同时也形成一种拙劣“架构”,所有数据操作和网络操作都被封装成Tools中的静态方法,所有异步任务都被封装成独立的Runnable导出类在线程池中执行,然后通过Handler线程之间传递Message。的确,它可以正常运行,用户在UI上看不出任何瑕疵,但内部其实千疮百孔。这种结构也确实难堪大用,可以想象后期的维护和更迭会是什么样的灾难,因此重构是必然的过程。

最近接触很多好东西,尤其Gson+RxJava+Retrofit组合,可以使用链式结构写出行云流水的异步操作,不会再出现到处都是Handler的壮观景象。还有MVP,在MVC基础上更进一步,把ModelView完全隔离,用Presenter连接,在其中定义由View驱动的所有操作,Activity只需持有不同Presenter即可控制整个界面。这使得软件结构清晰简洁,模块各司其职,互相独立。

建一个新分支用于重构,花一天半时间将原来杂乱的代码分类、剔除,以MVP作为软件结构,使用RxJava+Retrofit+Gson处理异步和网络操作。重构后的代码相比之下非常直观,Activity只处理生命周期和操作UI,其余异步和数据处理全部丢给Presenter完成。

后记

这个小客户端让我经历了一个软件从设计到开发乃至重构的大致流程,只是遗憾,微博对外提供的API非常吝啬,接口还有苛刻的频率限制。一些在我看来很基础的权限要额外申请,而申请的条件又相当“费解”,我尝试很多次都无法通过这一步,只能使用有限的测试账号。

其实,刚开始有打算做一个全功能的第三方微博客户端,越做越发现只能通过“投机取巧”来获取一些API之外的基本数据十分令人失落,不过写此软件的目的已经达到,作为第一个练习项目,从中获得这些,已经足够。

arrow_upward