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