您的位置:首页 > 移动开发 > Android开发

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

2016-10-03 19:06 357 查看

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();
}
})
b173
;

}

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下载地址
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息