比分更新功能记录
最近做的比分更新功能,算是到一个完善的稳定版本了,将从以下部分由整体到具体细节一步一步实现更新比分功能,将了解到,Android的轮询实现,在长列表中如何优雅的更新局部数据,以及更新动画
- 主体思想
- Android服务端
- Android客户端
- 客服端更新数据
- 重新进入应用
主体思想
Android端定时请求list_code.htm,获取当前服务器数据更新状态标志,如果本次请求得到的code标志与上一次请求的标志不一致,则请求list.htm,实时需要更新的数据列表。
如下图
Android中的服务端
服务端需要实现功能
- 获得需要的参数
- 启动定时请求
- 将更新的数据传递到Activity
- 停止定时请求
服务端需要两个参数
- 更新时间间隔
- 需要更新的赛事类别
将其封装出一个CategoryServiceNeed
客户端每次bindService都需要将所需要的CategoryServiceNeed封装好,put到Inent中
准备客户端需要的Binder
ICategoryUpdate.aidl负责获得客户端实现的IDataCallback.aidl接口,启动更新Timer
1 | // ICategoryUpdate.aidl |
onServiceConnected()的是时候,将以下Binder提供给客服端调用,类似RPC调用,而在客户端实现的IDataCallback接口提供给服务端调用,从而服务端到客户端数据能双向更新。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70private ICategoryUpdate.Stub mBinder = new ICategoryUpdate.Stub() {
@Override
public void init(CategoryServiceNeed need) throws RemoteException {
mNeed = need;
if (mNeed != null &&mNeed.getRefreshTime() <= 0){
mNeed.setRefreshTime(5000);
}
}
@Override
public void start() throws RemoteException {
if (mNeed == null){
return;
}
if (mTimer == null){
mTimer = new Timer();
} else {
mTimer.cancel();
mTimer = null;
mTimer = new Timer();
}
doGetData();
mTimer.schedule(new TimerTask() {
@Override
public void run() {
RetrofitHelper.getCategoryCodeService()
.getUpdateCodeCategory(mNeed.getCateId())
.enqueue(new Callback<CategoryCode>() {
@Override
public void onResponse(Call<CategoryCode> call,
Response<CategoryCode> response) {
if (!mCode.equals(response.body().getCode())){
doGetData();
}
mCode = response.body().getCode();
}
@Override
public void onFailure(Call<CategoryCode> call, Throwable t) {
}
});
}
}, 0, mNeed.getRefreshTime());
}
@Override
public void setCallback(IDataCallback callback) throws RemoteException {
mListenerList.clear();
mListenerList.add(callback);
}
@Override
public void stop() throws RemoteException {
if (mTimer!= null){
mCode = "";
mTimer.cancel();
mTimer = null;
mCallback = null;
}
}
@Override
public void clearCallback(IDataCallback callback) throws RemoteException {
if (mListenerList.contains(callback)){
mListenerList.remove(callback);
}
}
};
更新数据的列表方法,得到正确的数据后,调用iDataCallback.onSuccess()将数据传递Activity层,实现服务层到Activity层。
1 | private void doGetData() { |
Note: 为什么在Serive使用异步,不是使用同步,因为,Serivce也是和Activity都是属于Appliction下的,是Appliction的组件,所以也不能使用做耗时的请求。参见深入理解Android源码第二章
自此我们完成了获取参数,启动更新,数据更新到Acivity层,还差停止定时请求,这个放在Service的onUnbind生命方法中
1 | /** |
Android中的客户端
客户端实现的功能就比较简单了,需要
- 绑定服务
- 实现IDataCallback
- 取消绑定服务
在客户端中主要是Fragment绑定服务,和解除绑定,这两个动作需要与Fragment的生命周期一致并且得配对好,所以我就选择在onResume()绑定服务和onPause()中解除绑定。进入下一个Acitvity还是退出Activity必调用onPasue(),而从Activity回来也必调onResume()
绑定服务
1 |
|
实现IDataCallback接口,在onSuccess()方法中只需要数据包裹在Message中,然后交给Handler把数据添加到MessageQuene中
1 | private static IDataCallback.Stub mCallback = new IDataCallback.Stub() { |
将mConnection传到bindService方法中,一旦建立连接后,设置回调接口,启动更新(之前,不晓得RPC这个概念,这就相当于在客户端调用了服务端的方法,很给力)
1 | /** |
解除绑定
1 | @Override |
Note: 为啥要加try catch捕获crash异常,及时是配对的bind和unbind,但是极少数情况下,就给你来个给个服务之前已经取消绑定了,不要再给我取消绑定,再给取消绑定,我崩给你看。
数据更新UI部分
终于到高潮了,马上就能看到UI更新的效果。好激动,还是先冷静一下,看看有哪几类客户端
- ViewPager Container类Fragment.
- 头部比分类Fragment
ViewPager Container类Fragment
我们这类app都是采用TabLayout+ViewPager来实现,所以我就选在TabLayout+ViewPager的Fragment作为客户端,而不是选择ViewPager中具体的Fragment来作为客户端
这样做的好处
- 避免了频繁的绑定,解除绑定
- 统一管理更新
这里里面涉及到,怎么调用ViewPager下的Fragment的更新方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28private static class UiUpdateHandler extends Handler {
SoftReference<HomeFragment> mFragmentReference;
UiUpdateHandler(SoftReference<HomeFragment> fragmentReference) {
mFragmentReference = fragmentReference;
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x1000) {
HomeFragment fragment = mFragmentReference.get();
CategoryUpdateList list = msg.getData().getParcelable("Update");
if (fragment != null) {
if (list != null && list.getList() != null &&
list.getList().size() > 0) {
Fragment item = fragment.adapter.getCurrentFragment();
if (item instanceof CateHomeHotFragment) {
((CateHomeHotFragment) item).update(list);
}
if (item instanceof CateHomeFragment) {
((CateHomeFragment) item).update(list);
}
}
}
}
}
}
我值ViewPager的Adapter中添加了getCurrentFragment()获取当前页的Fragment, 这个方法会被具体的Fragment向上转型成通用的Fragment,之后调用updata()方法更新,当然也可把CateHomeHotFragment和CateHomeHotFragment重构成一个抽象类。
1 | public Fragment getCurrentFragment() { |
Fragment调用的update方法,mGameRVAdapter.updateVisibleItem(mLiveIndex, first, last, list);
mLiveIndex记录第一个在直播中的位置,之后如果下拉加载之前的数据,需要mLiveIndex += 封装后的List的长度。
first列表中当前第一个可见的item的位置,last列表中当前最后一个可见的位置。
1 | /** |
而在Adapter中更新的需要从mLiveIndex向下找的第一个是日期类型的item,通过id匹配,最后更新数据,但是notifyItemChanged的只有[first,last]中匹配的id
Note:直接调notifyItemChanged(postion)会使图片闪烁,所以需要用notifyItemChanged(postion, Object)和以下方法配合使用。1
2
3
4
5
6
7
8
9@Override
public void onBindViewHolder(final BaseViewHolder holder,
final int position, List playList) {
if (playList.isEmpty()){
onBindViewHolder(holder, position);
} else {
holder.bindingData(mList.get(position), context, isNoScore);
}
}
至此ViewPager Container类Fragment完成数据更新
头部比分类Fragment
这类的客户端就简单多了,分成两类
- 带小小比分的Fragment
- 不带小小比分的Fragment
- 不带局数的Fragment;
抽取公共方法
开始编写这个公能的时候,我将这个三个类都单独的,绑定服务,解除绑定,更新数据
但是绑定服务,解除绑定,不同就是更新数据的部分不同,这时候抽象类的功效就来了,绑定服务,解除绑定,初始化的一些数据全部放到,抽象类中
添加更新头部比分和小比分的抽象方法,在子类中初始化UI控件,将更新的数据类上ViewPager Container类Fragment完成数据更新,只不过这次
只需要遍历列表,找到比赛id,实现对应的部比分和小比分的抽象方法即可
RecyclerView更新动画
由于每次跟新数据,RecycelerView渐变动画,时间太短,我们需要设置Recycler的渐变动画
在RecyclerView如果没有设置动画,Recyceler设置一个默认250ms的渐变动画,这个动画对于用户来说,TMD什么时候变的,我都没看清楚就变了,
这时候,重新设置一下recyclerView的渐变动画时间即可,当然也可以重写SimpleItemAnimator实现自己的数据更新动画,在Adapter的 onAttachedToRecyclerView中设置动画时间为3秒
1 | @Override |
这两个客户端都完成数据跟新了,看看最终结果吧
重新进入应用处理
我们这服务并不需要包活,app没处于活动状态,没有比要跟新比分,费流量
冲新进入应用,需要走SplashActivity–>到MainActivity.onRestart()方法
所以在重写的onRestart()方法中findFragmentByTag(“Tab1”),在调用fragment方法即可
MainAcitvity1
2
3
4
5
6
7
8
9@Override
protected void onRestart() {
super.onRestart();
Fragment tab1 = getSupportFragmentManager().findFragmentByTag("Tab1");
if (tab1!=null && tab1 instanceof HomeFragment){
HomeFragment home = (HomeFragment) tab1;
home.onRestart();
}
}
HomFragment,绑定以一下服务或者启动一下更新,就Over了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public void onRestart() {
if (homeSoftReference == null){
homeSoftReference = new SoftReference<>(this);
}
if (mUpdateUI == null){
mUpdateUI = new UiUpdateHandler(homeSoftReference);
}
try {
if (!mCategoryUpdate.asBinder().isBinderAlive()) {
HomeCate homeCateByIndex =
adapter.getHomeCateByIndex(viewPager.getCurrentItem());
bindToService(homeCateByIndex);
} else {
mCategoryUpdate.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}