Android中Adapter的源码分析以及其中的陷阱

来源:互联网 发布:网络录音电话 编辑:程序博客网 时间:2024/06/05 20:34

Android中Adapter的源码分析以及其中的陷阱

前言

前几天在群里看到有位同学,在自己写的项目中遇到了一个问题,在调用Adapter.notifyDataSetChanged()方法的时候,listview的界面没刷新,来向大家求助,大家热心帮忙找了好久,都是口述可能的问题点,仍然还是没有解决,他把的部分代码发了出来,我自己也找了一下,最终发现了这个小小的陷阱,具体是什么呢,容我先卖个关子。我们先来看看本文的内容。

理解观察者模式

在当前市场上的各类App中,ListView以及RecyclerView是非常常见以及重要的控件,我们可以通过适配器来动态的绑定数据,那么大家除了自己会用之外,有没有了解过Adapter的设计模式呢?其实Adapter的设计模式,就是Java中的观察者模式
Java设计者模式参考文章
观察者模式所涉及的角色有:

  • 抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。

  • 具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。

  • 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。

  • 具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

BaseAdapter源码分析

我们在使用BaseAdapter的时候,一般都会创建一个类来继承它,然后重写相关的方法,我们首先来看一下BaseAdapter的继承关系。
BaseAdapter是一个抽象类,实现了ListAdapter以及SpinnerAdapter接口,而这2个接口最终又继承自Adapter接口,在实际的项目中,自定义Adapter还是最常用的,其他的ArrayAdapter、SimpleAdapter,CursorAdapter也都是继承自它。
我们先看一下如果继承BaseAdapter,有哪些我们必须重写的方法,这几个方法实际上都是Adapter接口中定义的方法。

package com.csdn.lhy.listview_adapter;import java.util.List;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.TextView;public class MyAdapter extends BaseAdapter {    private LayoutInflater inflater;    private List<String> list;    private Context context;    public MyAdapter(List<String> list,Context context) {        super();        this.inflater = LayoutInflater.from(context);        this.list = list;    }    @Override    public int getCount() {        // TODO Auto-generated method stub        return list.size();    }    @Override    public Object getItem(int position) {        // TODO Auto-generated method stub        return list.get(position);    }    @Override    public long getItemId(int position) {        // TODO Auto-generated method stub        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder vh = null;        if (convertView == null) {            convertView = inflater.inflate(R.layout.list_item, parent,false);            vh = new ViewHolder(convertView);            convertView.setTag(vh);        }else{            vh = (ViewHolder) convertView.getTag();        }        vh.textView.setText(list.get(position));        return convertView;    }    class ViewHolder{        TextView textView;        public ViewHolder(View convertView){            textView = (TextView) convertView.findViewById(R.id.textView);        }    }}
方法 描述 public int getCount() 这个方法返回的是的当前数据源中数据的个数 public Object getItem(int position) 这个方法返回的是ListView中对应position的数据 public long getItemId(int position) 这个返回的是给当前position的item设置一个id,一般返回当前的position public View getView(int position, View convertView, ViewGroup parent) 返回指定positon位置的item的View,在此对其中的数据和listItem中的各个组件进行绑定。

mDataSetObservable:

接下来我们先看一下BaseAdapter的源码,BaseAdapter实现了ListAdapter以及SpinnerAdapter接口,在BaseAdapter中,声明了一个DataSetObservable对象mDataSetObservable,我们点进去看一下。

private final DataSetObservable mDataSetObservable = new DataSetObservable();

我们发现,DataSetObservable又继承自Observable,里面有2个方法,notifyChanged()以及notifyInvalidated(),看到这里是不是就觉得有些熟悉了。

public class DataSetObservable extends Observable<DataSetObserver> {    public void notifyChanged() {        synchronized(mObservers) {            //遍历mObservers集合,通知观察者状态改变:调用观察者的onChanged()方法            for (int i = mObservers.size() - 1; i >= 0; i--) {                mObservers.get(i).onChanged();            }        }    }    public void notifyInvalidated() {        synchronized (mObservers) {            for (int i = mObservers.size() - 1; i >= 0; i--) {                mObservers.get(i).onInvalidated();            }        }    }}

我们在看一下Observable类,即被观察者类,我们发现DataSetObservable类中的mObservers,实际上来自Observable,而它本身实际上就是一个ArrayList集合,存储的是观察者Observer的实现类的实例,我们继续看下面的这个方法。
- mObservers:

一个ArrayList集合 ,持有的是该被观察者Observable对象绑定的观察者对象Observers。

  • registerObserver(T observer):

    判断mObservers中是否存在observer观察者对象,如果已经存在了,说明被观察者中已经有改观察者对象了,否则将观察者对象Observer添加到集合中,即实现了被观察者与观察者的绑定

  • unregisterObserver(T observer):

    通过ArrayList的indexOf方法拿到改观察者对象的下标,然后通过remove()方法移出mObservers,即完成的是实现了被观察者与观察者的解绑

public abstract class Observable<T> {    /**     * The list of observers.  An observer can be in the list at most     * once and will never be null.     */    protected final ArrayList<T> mObservers = new ArrayList<T>();   //被观察者与观察者的绑定    public void registerObserver(T observer) {        if (observer == null) {            throw new IllegalArgumentException("The observer is null.");        }        synchronized(mObservers) {            if (mObservers.contains(observer)) {                throw new IllegalStateException("Observer " + observer + " is already registered.");            }            mObservers.add(observer);        }    }    //被观察者与观察者的解绑    public void unregisterObserver(T observer) {        if (observer == null) {            throw new IllegalArgumentException("The observer is null.");        }        synchronized(mObservers) {            int index = mObservers.indexOf(observer);            if (index == -1) {                throw new IllegalStateException("Observer " + observer + " was not registered.");            }            mObservers.remove(index);        }    }        //代码省略....}

当ListView绑定Adapter的时候发生了什么:

我们都知道,当我们使用Adapter的时候,通过listView的setAdapter()方法就可以了,那么这个方法中究竟都做了什么呢?
首先判断是否已经绑定过观察者对象,如果有,则先与之解绑,这种情况一般就是开发者多次setAdapter()发生的,然后初始化mAdapter,即listView的setAdapter()方法中传递进来的,然后调用了父类的setAdapter方法,重置了mCheckStates,它是一个SparseBooleanArray,里面存的是item的选中状态。
接下来重新创建了mDataSetObserver = new AdapterDataSetObserver();然后重新绑定了观察者对象,最后调用requestLayout()方法,重新绘图,这样就将ListView中绑定的数据显示出来了。

@Override    public void setAdapter(ListAdapter adapter) {        //首先判断是否已经绑定过观察者对象,如果有,则与之解绑        这种情况一般就是开发者多次setAdapter()发生的。        if (mAdapter != null && mDataSetObserver != null) {            mAdapter.unregisterDataSetObserver(mDataSetObserver);        }        resetList();        mRecycler.clear();        if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);        } else {            mAdapter = adapter;        }        mOldSelectedPosition = INVALID_POSITION;        mOldSelectedRowId = INVALID_ROW_ID;        // AbsListView#setAdapter will update choice mode states.        super.setAdapter(adapter);        if (mAdapter != null) {            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();            mOldItemCount = mItemCount;            mItemCount = mAdapter.getCount();            checkFocus();            mDataSetObserver = new AdapterDataSetObserver();            mAdapter.registerDataSetObserver(mDataSetObserver);            //代码省略....        requestLayout();//调用AbsListView的requestLayout()方法,继续调用View的requestLayout()方法开始绘图    }

adapter.notifyDataSetChanged()方法发生了什么:

这个方法实际上是调用了mDataSetObservable.notifyChanged();这个方法,而这个方法继续调用了被观察者的notifyChanged()方法,mObservers.get(i).onChanged();通知观察者更新,调用观察者的onChanged()方法。

 public void notifyDataSetChanged() {        mDataSetObservable.notifyChanged();    } public void notifyChanged() {      synchronized(mObservers) {          // since onChanged() is implemented by the app, it could do anything, including          // removing itself from {@link mObservers} - and that could cause problems if          // an iterator is used on the ArrayList {@link mObservers}.          // to avoid such problems, just march thru the list in the reverse order.          for (int i = mObservers.size() - 1; i >= 0; i--) {              mObservers.get(i).onChanged();          }      }  }

我们继续向下看onChanged()方法中执行了什么,在ListView中绑定的观察者mDataSetObserver,它是一个AdapterDataSetObserver对象,而AdapterDataSetObserver又继承自DataSetObserver,在它的onChanged()方法中,对当前的数据信息进行了保存。最后也是通过requestLayout()方法,重新绘制界面,从而完成ListView视图的刷新。

class AdapterDataSetObserver extends DataSetObserver {        private Parcelable mInstanceState = null;        @Override        public void onChanged() {            mDataChanged = true;            mOldItemCount = mItemCount;            mItemCount = getAdapter().getCount();            // Detect the case where a cursor that was previously invalidated has            // been repopulated with new data.            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null                    && mOldItemCount == 0 && mItemCount > 0) {                AdapterView.this.onRestoreInstanceState(mInstanceState);                mInstanceState = null;            } else {                rememberSyncState();            }            checkFocus();            requestLayout();        }

章总结

通过上面的分析,我们知道了实际上Adapter是通过观察者设计模式的,在整合流程中:
- 观察者是在ListView中的setAdapter()中创建的;
- 被观察者是在BaseAdapter中初始化的。

Adapter使用中的陷阱:

在ListView中数据进行刷新的时候,我们一般只要new一个Adapter就可以了,后续数据的变化可以通过adapter的notifyDataChange()方法来更新视图界面。
那么我们所说的陷阱在哪里呢?下面看一下这个Demo,通过这个Demo来还原一下这个陷阱。
下面是这个演示Demo的代码,每次下拉刷新只显示10条数据,适配器以及布局文件代码暂时就不贴了,有兴趣的同学可以下载Demo的源码来看。

package com.csdn.lhy.listview_adapter;import java.util.ArrayList;import java.util.List;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.os.SystemClock;import android.support.v4.widget.SwipeRefreshLayout;import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;import android.util.Log;import android.widget.ListView;import android.widget.Toast;public class MainActivity extends Activity {    public static final String TAG = "lhy";    private ListView listView;    private SwipeRefreshLayout refreshLayout;    private Handler mHandler = new Handler() {        @Override        public void handleMessage(Message msg) {            // 处理消息刷新视图            List<String> data = (List<String>) msg.obj;            Log.d(TAG, "data的size: " + data.size());            mDatas = data;   //错误示例//          mDatas.clear();//          mDatas.addAll(data);            refreshLayout.setRefreshing(false);            mAdapter.notifyDataSetChanged();            Toast.makeText(MainActivity.this, "数据更新成功", Toast.LENGTH_SHORT).show();        }    };    private List<String> mDatas;    private MyAdapter mAdapter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initViews();        initData();    }    private void initData() {        mDatas = new ArrayList<String>();        for (int i = 0; i < 10; i++) {            mDatas.add("初始消息:" + i);        }        mAdapter = new MyAdapter(mDatas, this);        listView.setAdapter(mAdapter);    }    private void initViews() {        listView = (ListView) findViewById(R.id.listView);        refreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeLayout);        refreshLayout.setOnRefreshListener(new OnRefreshListener() {            // 添加下拉刷新监听            @Override            public void onRefresh() {                loadNewData();            }        });    }    protected void loadNewData() {        new Thread(new Runnable() {            @Override            public void run() {                // 模拟耗时操作                SystemClock.sleep(1000);                Message message = mHandler.obtainMessage();                List<String> list = new ArrayList<String>();                for (int i = 0; i < 10; i++) {                    list.add("新的消息:" + i);                }                message.obj = list;                mHandler.sendMessage(message);            }        }).start();    }}

当我们下拉刷新的时候,神奇的事情出来了,为什么没有刷新成功呢?

我们分析一下,在下拉刷新的时候,在子线程中,new了一个新的list集合,然后List<String> list = new ArrayList<String>();然后在handleMessage方法中拿到集合data,然后直接将mDatas的引用指向的data,这时候虽然集合中有数据,但是mDatas此时的引用地址已经改变了,因此在调用mAdapter.notifyDataSetChanged();方法的时候,失败了。

    List<String> data = (List<String>) msg.obj;    Log.d(TAG, "data的size: " + data.size());    mDatas = data;   //错误示例

正确的方法:

//          mDatas = data;   //错误示例            mDatas.clear();            mDatas.addAll(data);

运行结果如图,可以看到数据更新成功了。

总结

其实谷歌推出了RecyclerView也很久了,而目前在github上,相关的框架也有很多了,相比于ListView,RecyclerView新增了一些强大的功能,所以还是推荐大家尽快切换到RecyclerView上面。

Demo下载地址

1 0
原创粉丝点击