Android 从本地和服务器拉取数据显示

需求分析

在做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使用更加方便,涉及了以下内容:

  • 兼容AsyncTask各版本差异性,采用并行执行线程池
  • 内存泄露问题,下层DataFetcher对上层Callback采用WeakReference封装,避免类似Activity 旋转recreate泄露
  • 线程安全问题,一些变量尽量在UI线程串行执行

用框架能解决的事情最好能自己写一遍,多写写才能加深软件设计中模块与层次的思想

你可能感兴趣的:(Android,Java)