一次独立 App 的尝试

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

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

weibo

weibo

weibo

weibo

weibo

weibo

weibo

OAuth2授权

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

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

HTTP

超文本传输协议,微博接口是常规 GET 和 POST,十分清晰。

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

图片异步加载

图片异步加载是两个问题,工作线程更新 UI 和RecyclerView的子View复用。

前者好解决,HandlerAsyncTask都可以实现,后者问题表现为图片错位,即设置的图片和实际显示的图片不一致。根源是RecyclerView复用子View,因为图片在异步任务中下载需要时间,完成后原View可能已经滚出屏幕被拿来显示下一条数据。同一个View位置和数据都变了,自然导致图片错位。解决方法是在绑定数据和启动下载任务的onBindViewHolder()中为View设置标记,图片下载完成取出标记验证是否和数据匹配,不匹配即说明View已被复用,不必再为它加载这个图片。

一开始我尝试创建自己的ImageLoader,使用MemoryLruCacheDiskLruCache为图片建立两级缓存,封装异步加载功能。后来遇到的图片问题越来越多,比如 GIF 动图,全部自己“造轮子”并不现实,转而选择更方便、安全的 Glide。

Activity滑动退出

本质是控制当前Activity的整个View滑动,并在结束滑动时退出Activity

思路不复杂,DecorViewView树的根节点,由竖直方向的LinearLayout填充,包含TitleViewContentView。要做的是将一个ViewGroup插入到DecorViewLinearLayout之间,用它监听触控事件,调用父级DecorViewscrollBy()移动Content,当移动距离或速度达到一定阈值时开启动画退出Activity。注意Window背景要设为透明色,否则看不到下层Activity

TextView显示链接和图片

之前不知道TextView可以嵌入链接和图片,好奇表情包是如何显示在正文里的,其实就是将普通String转换为SpannableString,为字符设定显示格式。

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

后台服务定时轮询

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

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

主题切换

Android 提供的切换主题方案是通过setTheme()方法,加载本地定义的Style作为整个ActivityThemeView会从Theme中读取自己需要的样式随主题切换变化,所以setTheme()必须在Activity加载View之前调用,即在setContentView()之前。

setTheme()会触发Activity重新创建,注意各个组件在重建时的数据恢复,做到对用户而言的无感切换。

适配不同屏幕尺寸

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

重构

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

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

最近阅读 Android 最佳实践接触很多优质类库,尤其 Gson + RxJava + Retrofit 组合,可以使用链式结构轻松写出行云流水的异步操作,不会再出现到处Handler的壮观景象。还有 MVP,在 MVC 基础上进一步把ModelView隔离,用定义View事件处理方法的Presenter连接,Activity持有Presenter即可完成 UI 驱动的交互闭环。

建一个新分支重构,应用 MVP 将杂乱代码分类、剔除,Activity只处理生命周期和操作 UI,具体任务全部丢给Presenter完成。新架构清晰简洁,互相独立的模块各司其职,第一次感觉写代码可以像写诗文一样洒脱顺畅😯。

后记

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

刚开始打算做一个全功能第三方微博客户端,越做越发现只能通过“投机取巧”获取 API 之外的基本数据令人失落,不过写此软件的目的已经达到,作为第一个练习项目收获颇丰。

arrow_upward