一次独立App的尝试
立泉本文时间久远,部分内容可能过时,仅供参考。
这是我尝试做的第一款比较完整的软件,厌倦新浪微博官方App的广告骚扰和后台自启,不如为自己写一个轻量微博客户端,实现刷微博的功能而做到“召之即来,挥之即去”的简洁体验。涉及OAuth
认证、加载网络媒体资源、数据缓存和界面控制,我第一次遇到一些之前没有想过的问题,也第一次经历重构
,是很有意义的实践。
OAuth2授权
安全原因,用户不会轻易把密码交给第三方客户端,需要一种向客户端授权的可信机制,正是OAuth2
要解决的问题。
在OAuth2
协议中,客户端不持有用户名和密码,只保存一个代表用户的accessToken
,调用接口时以此验证身份。具体流程是,客户端WebView
访问微博授权网址并提交微博给第三方客户端的授权信息,用户在此网页中输入账号、密码验证身份,然后微博服务器会返回一个授权码
,客户端再用此授权码
换取accessToken
。一旦换取成功,客户端就可以在没有用户账号和密码的情况下用token
访问微博信息。
HTTP
超文本传输协议,微博提接口都是常规GET
和POST
,十分清晰。
我在初期使用HttpUrlConnection
和线程池
构建自己的网络访问框架,实现的很简单,对外提供接口,将任务放到线程池
中执行并在完成后回调,也会结合Handler
实现在UI线程
中回调。解析JSON
数据则使用原始JSONObject
和JSONArray
,将获取的微博数据填充到对应Object
后加载给RecyclerView
显示,同时在本地保存以离线使用。
图片异步加载
图片异步加载是两个问题,UI线程
中更新UI
和RecyclerView
的子View
复用。
前者很好解决,Handler
和AsyncTask
都可以实现,后者问题表现为图片错位,即设置的图片和实际显示的图片不一致。根源是RecyclerView
复用子View
,因为图片在异步线程中下载需要时间,当下载完成后原View
可能已经滚出屏幕,并被当作下一个ItemView
进行显示。同一个View
,位置却变了,自然会出现图片错位。解决方法是在绑定数据和启动下载任务的onBindViewHolder
中为ItemView
设置Tag
标记,图片下载完成后取出Tag
判断和之前是否一致,不一致即说明ItemView
已被复用,不必再为它加载这个图片。
开发初期,我尝试创建自己的ImageLoader
,使用MemoryLruCache
和DiskLruCache
为图片建立两级缓存,封装异步加载功能。只是后来遇到的图片问题越来越多,比如GIF
动图,全部自己造轮子
并不现实,最终选择Glide
,既方便也安全。
Activity滑动退出
这个问题的本质是如何控制当前Activity
的整个View
进行滑动,并在结束滑动时退出Activity
。
思路并不复杂,DecorView
是整个View
树的根节点,它之下有一个竖直方向的LinearLayout
,包含TitleView
和ContentView
。要做的是将一个ViewGroup
插入到DecorView
和LinearLayout
之间,用它监听触控事件,调用父View
即DecorView
的scrollBy()
方法移动整个DecorView
的Content
,包括状态栏背景,当移动到一定的距离或者速度达到一定值的时候开启动画并退出此Activity
。注意Window
背景要设为透明色,否则看不到此Activity
之下的Activity
。
TextView显示链接和图片
之前一直不知道TextView
可以嵌入链接和图片,还一直好奇表情图是怎么显示在微博里的,其实就是将普通String
转换为SpannableString
,在指定位置显示指定格式。
比如格式为ClickableSpan
链接,ImageSpan
图片,也可以为其定义点击事件。使用时只需配合正则表达式
正确替换字符串中的指定字符。
后台服务定时轮询
我对是否加入后台服务有点犹豫,很多人只是空闲时刷微博看看别人都说了些什么,除私信外应该不会喜欢微博主动在通知栏弹出消息。但考虑到功能完整性,我还是做了一个基本的后台推送放在设置里选择是否开启。
实现十分直白,在Service
的onStartCommand()
方法中定义一个AlarmManager
定时服务,让它每隔一段时间发送一条广播。定义BroadcastReceiver
接收广播并启动Service
,其onStartCommand()
方法会被执行,在这里开启子线程执行网络查询任务,有新消息就弹出Notification
,完成任务后关闭服务等待下一次唤醒。
主题切换
Android
官方的切换主题方案是通过setTheme()
方法,它可以加载本地定义的Style
作为整个Activity
的Theme
,View
样式随主题的切换而不同。只是这些样式值必须已经在Theme
中存在,且setTheme()
方法必须在Activity
加载View
之前调用,即在setContentView()
之前。
这种方式会触发Activity
重新创建,必须注意各个组件在重建时的数据恢复,做到对用户而言的无感切换。
适配不同屏幕尺寸
我适配了手机和平板两种类型的设备,软件运行时根据不同屏幕尺寸加载对应布局文件,配合RecyclerView
的线性布局
和瀑布流布局
,在手机和平板的横屏、竖屏情况下都可以呈现美观的用户界面。
重构
这是我做的第一个综合性软件,集网络、异步、图片、服务、本地存储于一体,刚开始真的一头雾水,没有“低耦合、高内聚”的概念,只要能实现功能就写到哪算哪。在这个过程中逐渐意识到架构
的重要性,因为我不知道应该如何安排这些代码的结构,看起来把它们放到哪里都可以运行。
经过几天尝试,在完成软件的同时也形成一种拙劣“架构”,所有数据操作和网络操作都被封装成Tools
中的静态方法,所有异步任务
都被封装成独立的Runnable
导出类在线程池
中执行,然后通过Handler
在线程
之间传递Message
。的确,它可以正常运行,用户在UI
上看不出任何瑕疵,但内部其实千疮百孔。这种结构也确实难堪大用,可以想象后期的维护和更迭会是什么样的灾难,因此重构
是必然的过程。
最近接触很多好东西,尤其Gson
+RxJava
+Retrofit
组合,可以使用链式结构
写出行云流水的异步操作
,不会再出现到处都是Handler
的壮观景象。还有MVP
,在MVC
基础上更进一步,把Model
和View
完全隔离,用Presenter
连接,在其中定义由View
驱动的所有操作,Activity
只需持有不同Presenter
即可控制整个界面。这使得软件结构清晰简洁,模块各司其职,互相独立。
建一个新分支用于重构,花一天半时间将原来杂乱的代码分类、剔除,以MVP
作为软件结构,使用RxJava
+Retrofit
+Gson
处理异步和网络操作。重构后的代码相比之下非常直观,Activity
只处理生命周期和操作UI
,其余异步和数据处理全部丢给Presenter
完成。
后记
这个小客户端让我经历了一个软件从设计到开发乃至重构的大致流程,只是遗憾,微博对外提供的API
非常吝啬,接口还有苛刻的频率限制。一些在我看来很基础的权限要额外申请,而申请的条件又相当“费解”,我尝试很多次都无法通过这一步,只能使用有限的测试账号。
其实,刚开始有打算做一个全功能的第三方微博客户端,越做越发现只能通过“投机取巧”来获取一些API
之外的基本数据十分令人失落,不过写此软件的目的已经达到,作为第一个练习项目,从中获得这些,已经足够。