在做GankIO客户端App时,有这样一种需求:类似微信消息列表界面,每次进入该界面时,都会先显示本地的数据,同时向服务器拉取最新的数据,减少用户等待数据的时间,以增强用户体验。
我已经使用RxJava中的merge操作符实现该需求。现在如果不用RxJava框架,这个怎么实现?如何封装一个好用的模块给上层使用?
这里需要同时在本地、服务器拉取数据,而且两件事情都做完后,整个事情才做完,故我们设计两个任务并行执行。
class DataFetcher{
LocalDataFetcher;
RemoteDataFetcher;
}
线程上下文切换的工作交给AsyncTask内部的Handler处理,这样基本的数据结构就完成了。
底层DataFetcher模块:
package com.liguang.datafetcher;
import android.os.AsyncTask;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 并行加载本地和远程数据
*/
public class DataFetcher {
private static final String TAG = "DataFetcher";
/**
* 上层,只在主线程读写
*/
private WeakReference mRef;
private Worker mLocalFetcher;
private Worker mRemoteFetcher;
private String mUrl;
public DataFetcher(String url, Callback callback) {
mRef = new WeakReference<>(callback);
mUrl = url;
//假数据
mLocalFetcher = new Worker(500, new String[]{"D", "E", "F"});
mRemoteFetcher = new Worker(2500, new String[]{"A", "B", "C", "D", "E", "F"});
}
/**
* 页面退出时需要调用该方法
*/
public void cancel() {
Log.d(TAG, "cancel: ");
mRef.clear();
mLocalFetcher.cancel(false);
mRemoteFetcher.cancel(false);
}
public void execute() {
Log.d(TAG, "execute: ");
//To avoid AsyncTask's version problems, we schedule our job to parallel executor
mLocalFetcher.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl);
mRemoteFetcher.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl);
}
private class Worker extends AsyncTask> {
private static final String TAG = "Worker";
private boolean mCompleted;
private long mDelay;
private String[] mMockData;
public Worker(long delay, String[] mockData) {
mDelay = delay;
mMockData = mockData;
}
@Override
protected void onPreExecute() {
mCompleted = false;
}
@Override
protected List doInBackground(String... strings) {
Log.d(TAG, "doInBackground: enter worker " + System.identityHashCode(this));
try {
//模拟耗时
Thread.sleep(mDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "doInBackground: exit worker " + System.identityHashCode(this));
return new ArrayList<>(Arrays.asList(mMockData));
}
@Override
protected void onPostExecute(List data) {
mCompleted = true;
Callback callback = mRef.get();
if (callback == null) {
Log.d(TAG, "onPostExecute: worker complete but high level callback is null");
return;
}
callback.onNext(data);
checkState();
}
@Override
protected void onCancelled() {
Log.d(TAG, "onCancelled: " + System.identityHashCode(this));
}
public boolean isCompleted() {
return mCompleted;
}
}
private void checkState() {
if (mLocalFetcher.isCompleted() && mRemoteFetcher.isCompleted()) {
//这里不需要检查Callback是否为空
// GC线程不回收Callback对象, 其在前一个栈帧(onPostExecute)上引用着
mRef.get().onComplete();
}
}
interface Callback {
void onNext(List data);
void onComplete();
}
}
上层测试Activity:
package com.liguang.datafetcher;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
public class MainActivity extends AppCompatActivity implements DataFetcher.Callback {
private DataFetcher mDataFetcher;
private List mData;
private String mUrl = "https://github.com/passionli";
@BindView(R.id.progressBar)
ProgressBar mProgressBar;
@BindView(R.id.recyclerView)
RecyclerView mRecyclerView;
private MyAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mAdapter = new MyAdapter();
mRecyclerView.setAdapter(mAdapter);
mProgressBar.setVisibility(View.VISIBLE);
mDataFetcher = new DataFetcher(mUrl, this);
mDataFetcher.execute();
}
@Override
public void onNext(List data) {
if (mData == null) {
mData = new ArrayList<>();
}
//merge
for (String element : data) {
if (!mData.contains(element)) {
mData.add(element);
}
}
//sort
Collections.sort(mData);
mAdapter.notifyDataSetChanged();
}
@Override
public void onComplete() {
mProgressBar.setVisibility(View.GONE);
}
@Override
protected void onDestroy() {
super.onDestroy();
//unregister to low level
mDataFetcher.cancel();
}
class MyAdapter extends RecyclerView.Adapter {
LayoutInflater mInflater;
public MyAdapter() {
mInflater = LayoutInflater.from(MainActivity.this);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MyViewHolder(mInflater.inflate(R.layout.list_item, parent, false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((MyViewHolder) holder).tv.setText(mData.get(position));
}
@Override
public int getItemCount() {
if (mData == null) {
return 0;
} else {
return mData.size();
}
}
}
class MyViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.tv)
TextView tv;
public MyViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}
这里是通过增加了一层DataFetcher让UI使用更加方便,涉及了以下内容:
用框架能解决的事情最好能自己写一遍,多写写才能加深软件设计中模块与层次的思想