一个令人无比蛋疼的线程里更新ListView的问题

treachery 2011-03-10 11:39:53
弄了好几天了,想实现一个功能:显示一个ListView,在Activity的Create方法中开启一个新的线程,在线程里通过循环为一个自定义的ArrayAdapter类的add(使用父类的方法)方法增加数据,ListView已经使用setAdapter方法与自定义的ArrayAdapter绑定,希望在线程里为ArrayAdapter增加数据的同时,显示的ListView能同步显示出增加的数据,可是这么做后,会报Only the original thread that created a view hierarchy can touch its views的错误,并出现Force Close

最终希望达到的效果就是,ListView随着线程里ArrayAdapter数据项的逐步增加(故意增加延迟放慢循环),ListView一条一条的显示数据,并不影响主界面的其他操作。

我想可能是我的思路有问题,希望能熟悉Android开发的朋友们能帮帮我,谢谢了。

另外这个代码的ListView只能显示出ArrayAdapter的前10条数据的重复组合,虽然数据的总数貌似是对的,但是ListView的显示的是ArrayAdapter的前10条数据的重复随机组合,为什么会出现这种情况呢?

再次感谢各位了!谢谢!

贴出全部代码:

主Activity代码:

ListTest.java:



public class ListTest extends Activity {

private ListView lv;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
lv = (ListView)findViewById(R.id.list);


//创建新线程用来显示列表
HandlerThread handlerThread = new HandlerThread("hthread_readcontact");
handlerThread.start(); //异步启动线程
//创建Handler
MyHandler myHandler = new MyHandler(handlerThread.getLooper());
//创建消息对象
Message msg = myHandler.obtainMessage();

//取出消息并执行
msg.sendToTarget();

}

//循环增加100个列表项
protected void AddListItem(){
List<PeopleInfo> myList = new ArrayList<PeopleInfo>();
MyAdapter ca = new MyAdapter(ListTest.this,myList);
//ca.setNotifyOnChange(true);
lv.setAdapter(ca);

for(int i=1;i<=100;i++){

//增加记录
PeopleInfo pe = new PeopleInfo();
pe.setPeoleName("Fucker"+Integer.toString(i));
pe.setPeopleNum(Integer.toString(i));
System.out.println("i:"+Integer.toString(i));
System.out.println("PeopleInfo-->Name:"+pe.peopleName);
System.out.println("PeopleInfo-->Num :"+pe.peopleNum);
ca.add(pe);

SystemClock.sleep(50L); //为了看到效果,故意增加一个延迟
}
ca.notifyDataSetChanged();
}

class MyHandler extends Handler{

public MyHandler(){

}

public MyHandler(Looper looper){
super(looper);
}

@Override
public void handleMessage(Message msg) {

AddListItem();
System.out.println("Thread is runed!");
}
}


}



其他类代码:

MyAdapter.java:



public class MyAdapter extends ArrayAdapter<PeopleInfo> {

private LayoutInflater inflater;
List<PeopleInfo> itemList;
private static final int mResource = R.layout.contact_list; //xml布局文件

public MyAdapter(Context context,List<PeopleInfo> objects) {
super(context, mResource, objects);
// TODO Auto-generated constructor stub
inflater = LayoutInflater.from(context);
itemList = objects;
}

@Override
public void add(PeopleInfo object) {
// TODO Auto-generated method stub
super.add(object);
//itemList.add(object);
}

@Override
public void setNotifyOnChange(boolean notifyOnChange) {
// TODO Auto-generated method stub
super.setNotifyOnChange(notifyOnChange);
}

@Override
public Filter getFilter() {
// TODO Auto-generated method stub
return super.getFilter();
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
ViewHolder holder;
holder = new ViewHolder();

if(convertView == null){

convertView = inflater.inflate(R.layout.contact_list, null);
//convertView = inflater.inflate(R.layout.listitem, null);

holder.PhoneName = (TextView)convertView.findViewById(R.id.mname);
holder.PhoneNum = (TextView)convertView.findViewById(R.id.msisdn);

convertView.setTag(holder);
holder.PhoneName.setText(itemList.get(position).getPeopleName());
holder.PhoneNum.setText(itemList.get(position).getPeopleNum());

}

return convertView;
}

class ViewHolder{
TextView PhoneName;
TextView PhoneNum;
}
}




PeopleInfo.java:


public class PeopleInfo implements Serializable {
public String peopleName;
public String peopleNum;

public String getPeopleName(){
return peopleName;
}

public void setPeoleName(String peopleName){
this.peopleName=peopleName;
}

public String getPeopleNum(){
return peopleNum;
}

public void setPeopleNum(String peopleNum){
this.peopleNum = peopleNum;
}

}



...全文
3032 28 打赏 收藏 转发到动态 举报
写回复
用AI写文章
28 条回复
切换为时间正序
请发表友善的回复…
发表回复
zouhao_1 2012-07-11
  • 打赏
  • 举报
回复
楼主,能不能把你改好的代码分享一下,我也遇到了同样一个问题。谢谢!
ziyouzhifeng007 2011-05-28
  • 打赏
  • 举报
回复
楼主把改好的源代码分享一下呗!学习一下!
treachery 2011-03-13
  • 打赏
  • 举报
回复
以上分值较大的代码基本OK,谢谢大家提供帮助!谢谢
JKelfin 2011-03-11
  • 打赏
  • 举报
回复
public class AsyncImageLoader {
private static AsyncImageLoader asyncImageLoader = new AsyncImageLoader();

private AsyncImageLoader() {
}

public static void setAsyncImage(ImageView view, final String url) {
asyncImageLoader.setItem(view, url);
}

private HashMap<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();

public void setItem(final ImageView view, final String url) {
if (imageCache.containsKey(url)) {
SoftReference<Bitmap> softReference = imageCache.get(url);
Bitmap drawableStore = softReference.get();
if (drawableStore != null) {
view.setImageBitmap(drawableStore);
return;
}
}

imageQuery.clean(view);
ImageLoadItem item = new ImageLoadItem(view, url);
synchronized (imageQuery.waitList) {
imageQuery.waitList.push(item);
imageQuery.waitList.notifyAll();
}

// start thread if it's not started yet
if (loadThread.getState() == Thread.State.NEW)
loadThread.start();
}

public Bitmap loadImageFromUrl(String url) {
URL m;
InputStream i = null;
try {
m = new URL(url);
i = (InputStream) m.getContent();
return BitmapFactory.decodeStream(i);
} catch (MalformedURLException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

private class ImageLoadItem {
private String url;
private ImageView view;

private ImageLoadItem(ImageView v, String u) {
url = u;
view = v;
}
}

private ImageQuery imageQuery = new ImageQuery();

private class ImageQuery {
private Stack<ImageLoadItem> waitList = new Stack<ImageLoadItem>();

private void clean(ImageView v) {
for (int j = 0; j < waitList.size();) {
if (waitList.get(j).view == v)
waitList.remove(j);
else
++j;
}
}
}

private ImageLoadThread loadThread = new ImageLoadThread();

private class ImageLoadThread extends Thread {
public void run() {
try {
while (true) {
if (imageQuery.waitList.size() == 0) {
synchronized (imageQuery.waitList) {
imageQuery.waitList.wait();
}
} else {
ImageLoadItem item;
final ImageView view;
String url;
synchronized (imageQuery.waitList) {
item = imageQuery.waitList.pop();
}
view = item.view;
url = item.url;
final Bitmap bmp = loadImageFromUrl(url);
imageCache.put(url, new SoftReference<Bitmap>(bmp));
Activity a = (Activity) view.getContext();
a.runOnUiThread(new Runnable() {
public void run() {
view.setImageBitmap(bmp);
}
});
}
if (Thread.interrupted())
break;
}
} catch (InterruptedException e) {
// allow thread to exit
}
}
}
}

这是我写的一个开辟单线程处理图片异步设置的一个类,不会阻塞程序,由于是单线程处理请求队列,程序也不会卡。另外加入SoftReference缓冲图片,因为listview在滚动时会不停的调用绘制imageview的方法。
qbwjly 2011-03-11
  • 打赏
  • 举报
回复
ListTest:
public class ListTest extends Activity {
//-------------------------------------------------------------------
private Thread myThread = new Thread(){
@Override
public void run () {
AddListItem();
}
};
//------------------------------------------------------------------

private MyHandler myHandler = new MyHandler();
private ListView lv;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
lv = (ListView)findViewById(R.id.list);


// //创建新线程用来显示列表
// HandlerThread handlerThread = new HandlerThread("hthread_readcontact");
// handlerThread.start(); //异步启动线程
// //创建Handler
// MyHandler myHandler = new MyHandler(handlerThread.getLooper());
// //创建消息对象
// Message msg = myHandler.obtainMessage();
//
// //取出消息并执行
// msg.sendToTarget();

//----------------------------------------------------------------------

List<PeopleInfo> myList = new ArrayList<PeopleInfo>();
MyAdapter ca = new MyAdapter(this, myList);
lv.setAdapter(ca);

myThread.start();
//------------------------------------------------------------------------
}

//循环增加100个列表项
protected void AddListItem(){
// List<PeopleInfo> myList = new ArrayList<PeopleInfo>();
// MyAdapter ca = new MyAdapter(ListTest.this,myList);
// //ca.setNotifyOnChange(true);
// lv.setAdapter(ca);


for(int i=1;i<=100;i++){

//增加记录
// PeopleInfo pe = new PeopleInfo();
// pe.setPeopleName("Fucker"+Integer.toString(i));
// pe.setPeopleNum(Integer.toString(i));
// System.out.println("i:"+Integer.toString(i));
// System.out.println("PeopleInfo-->Name:"+pe.peopleName);
// System.out.println("PeopleInfo-->Num :"+pe.peopleNum);

//-----------------------------------------------------------------------
Message m = new Message();
Bundle data = new Bundle();
data.putString("name", "Fucker" + i);
data.putString("number", "" + i);
m.setData(data);
myHandler.sendMessage(m);
//------------------------------------------------------------------------

SystemClock.sleep(200L); //为了看到效果,故意增加一个延迟
}
//ca.notifyDataSetChanged();

}
class MyHandler extends Handler{

public MyHandler(){

}

public MyHandler(Looper looper){
super(looper);
}

@Override
public void handleMessage(Message msg) {
// AddListItem();
// System.out.println("Thread is runed!");

//----------------------------------------------------------------------
if (null == msg)
return;
PeopleInfo pe = new PeopleInfo();
Bundle data = msg.getData();
pe.setPeopleName(data.getString("name"));
pe.setPeopleNum(data.getString("number"));
Log.v("================", data.getString("name")+" "+data.getString("number"));
MyAdapter ad = (MyAdapter) lv.getAdapter();
ad.add(pe);
ad.notifyDataSetChanged();
//-------------------------------------------------------------------------
}
}
}

MyAdapter:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub

if(convertView == null){
ViewHolder holder;
holder = new ViewHolder();

convertView = inflater.inflate(R.layout.contact_list, null);
//convertView = inflater.inflate(R.layout.listitem, null);

holder.PhoneName = (TextView)convertView.findViewById(R.id.mname);
holder.PhoneNum = (TextView)convertView.findViewById(R.id.msisdn);

convertView.setTag(holder);
holder.PhoneName.setText(itemList.get(position).getPeopleName());
holder.PhoneNum.setText(itemList.get(position).getPeopleNum());

}
//-------------------------------------------------------------------------
else {
ViewHolder h = (ViewHolder) convertView.getTag();
h.PhoneName.setText(itemList.get(position).getPeopleName());
h.PhoneNum.setText(itemList.get(position).getPeopleNum());
}
//-------------------------------------------------------------------------
return convertView;
}
youngwolf 2011-03-11
  • 打赏
  • 举报
回复
[Quote=引用 21 楼 treachery 的回复:]

引用 19 楼 yang79tao 的回复:

还是给个例子吧,你这用法感觉非常奇怪(我说楼主):
Java code

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

ArrayAdapter<String> ad = new ArrayAdapter<……
[/Quote]

不好意思,First_Android是我的activity的名字,mh是我new出来的一个Handler,在onCreate里面如下:
mh = new Handler();
注意,它是个成员变量,否则在线程里面访问不了。
tfront 2011-03-11
  • 打赏
  • 举报
回复
按照你的要求修改了一下,你代码最大的问题就是在Handler的处理过程中加入了Sleep,等于把主线程阻塞了,导致界面绘制不畅。


public class ListTest extends Activity {

private ListView mListView;

Handler mHandler = new Handler();

List<PeopleInfo> mList = null;
MyAdapter mAdapter = null;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mListView = (ListView)findViewById(R.id.list);

mList = new ArrayList<PeopleInfo>();

mAdapter = new MyAdapter(ListTest.this, mList);

mListView.setAdapter(mAdapter);

new Thread(new Runnable() {
public void run() {
while (true) {
mHandler.post(new Runnable() {
public void run() {
AddListItem();
}
});
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();
}

// 循环增加100个列表项
protected void AddListItem() {
// 增加记录
PeopleInfo pe = new PeopleInfo();
int number = (int)(Math.random() * 1000);
pe.setPeoleName("Haha " + Integer.toString(number));
pe.setPeopleNum(Integer.toString(number));
System.out.println("i:" + Integer.toString(number));
System.out.println("PeopleInfo-->Name:" + pe.peopleName);
System.out.println("PeopleInfo-->Num :" + pe.peopleNum);
mAdapter.add(pe);

// SystemClock.sleep(1000); // 为了看到效果,故意增加一个延迟
mAdapter.notifyDataSetChanged();
}
}


raoyongchao 2011-03-10
  • 打赏
  • 举报
回复
你线程处理的有点诡异...用Java原生态的Thread,然后发消息给handler,在handler里面更新你的listView
附:非UI线程是不能“直接”对UI线程里面的组件进行操作的
儿大不由爷 2011-03-10
  • 打赏
  • 举报
回复
发消息,在message handler里边更新
treachery 2011-03-10
  • 打赏
  • 举报
回复
[Quote=引用 19 楼 yang79tao 的回复:]

还是给个例子吧,你这用法感觉非常奇怪(我说楼主):
Java code

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

ArrayAdapter<String> ad = new ArrayAdapter<String>(th……
[/Quote]

谢谢你,朋友,可是你的例子里的First_Android.this.mh.pos和First_Android.this.runOnUiThread到底是什么意思啊?我在编译环境里无法识别First_Android
youngwolf 2011-03-10
  • 打赏
  • 举报
回复
更简单的,把mh = new Handler();去掉
把First_Android.this.mh.post改为irst_Android.this.runOnUiThread
youngwolf 2011-03-10
  • 打赏
  • 举报
回复
还是给个例子吧,你这用法感觉非常奇怪(我说楼主):

public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

ArrayAdapter<String> ad = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1, new Vector<String>());

mlv = new ListView(this);
mlv.setAdapter(ad);

setContentView(mlv);

mh = new Handler();
new Thread() {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 5; ++i) {
First_Android.this.mh.post(new Runnable() {
public void run() {
// TODO Auto-generated method stub
int n = new Random().nextInt();

@SuppressWarnings("unchecked")
ArrayAdapter<String> ad = (ArrayAdapter<String>) First_Android.this.mlv.getAdapter();
ad.add(Integer.toString(n));
ad.notifyDataSetChanged();
}
});

try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();


我刚写的。
JKelfin 2011-03-10
  • 打赏
  • 举报
回复
给你2例子,google上的
1.用View.post的方法.
new Thread(new Runnable() {
public void run() {
final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();

public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
2.用AsyncTask队列处理
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
新手多看看文档
file:///D:/android-sdk-windows/docs/guide/topics/fundamentals/processes-and-threads.html
sdk里面带了的。这里面有很多注意事项
treachery 2011-03-10
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 tfront 的回复:]

如果是我,我想我会这么写。不知道是不是符合你的要求。
Java code

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
……
[/Quote]

我就不明白了,Android的这个所谓的Runnable类有什么用啊?这样写是不报错了,但是效果还是把ListView的100条数据全部添加完最后才一次性显示出来的,添加完之前程序会黑着半天,而如果是这样的话,那我直接调用我自己写的AddListItem()函数不就完了,反正都是会被阻塞住,我何必要用一个线程,再来一个Runable来做呢?

完全没有达到效果,我希望的效果是,for循环里增加一条数据,这个数据就立马在ListView里显示出来,运行的效果应该是ListView显示一条数据,停顿一下,然后再显示下一条,然后再停顿一下,直到显示完所有100条数据为止,而且在这过程中不影响用户其他的操作。

而现在按照文档的这个要求,用所谓的这个在主线程中调用Runable,和直接调用一个函数的效果是一样的。
s054349 2011-03-10
  • 打赏
  • 举报
回复
[Quote=引用楼主 treachery 的回复:]
弄了好几天了,想实现一个功能:显示一个ListView,在Activity的Create方法中开启一个新的线程,在线程里通过循环为一个自定义的ArrayAdapter类的add(使用父类的方法)方法增加数据,ListView已经使用setAdapter方法与自定义的ArrayAdapter绑定,希望在线程里为ArrayAdapter增加数据的同时,显示的ListView能同步显示出增加的数据,可是……
//创建新线程用来显示列表
HandlerThread handlerThread = new HandlerThread("hthread_readcontact");
handlerThread.start(); //异步启动线程
[/Quote]
在非UI线程里面是不能更新UI控件的,你在这里通过HandlerThread创建了一个新的线程肯定是不行的。而Handler handler = new Handler() {
public void handleMessage(Message msg) {
}}
是跑在UI线程里面的,可以用来异步更新UI
tfront 2011-03-10
  • 打赏
  • 举报
回复
文档上说,Handler运行在创建它的线程中,也就是主线程。
[Quote=引用 14 楼 sjm19880409 的回复:]

你确定用的handler是UI线程的Handler?
[/Quote]
sjm19880409 2011-03-10
  • 打赏
  • 举报
回复
你确定用的handler是UI线程的Handler?
小裴同学 2011-03-10
  • 打赏
  • 举报
回复
我的写法和上面的一样。。

我的这这样的。。
ublic Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
mDateBase.delete(modelname);
mCursor = mDateBase.select();
listView.invalidateViews();
break;
}
super.handleMessage(msg);
}
};


public class MyAdapter extends BaseAdapter {

private LayoutInflater inflater;
private View myView;
public MyAdapter(Context c) {
this.inflater = LayoutInflater.from(c);
}


public int getCount() {
return mCursor.getCount() - 8;
}


public Object getItem(int position) {
return null;
}


public long getItemId(int position) {
return 0;
}

public View getView(int position, View convertView, ViewGroup parent) {
if (strOpt.equals("480*800")) {
myView = inflater.inflate(R.layout.modeldelectlist, null);
}
if (strOpt.equals("320*480")) {
myView = inflater.inflate(R.layout.modeldelectlistone, null);
}
final TextView textView = (TextView) myView
.findViewById(R.id.name_deletelist);
final int c = position;
mCursor.moveToPosition(8 + position);
textView.setText(mCursor.getString(1));
ImageButton button = (ImageButton) myView
.findViewById(R.id.ImageButton_deletelist);
button.setFocusable(false);
button.setOnClickListener(new OnClickListener() {

public void onClick(View v) {
modelname = textView.getText().toString();
mCursor.moveToPosition(8 + c);
AlertDialog.Builder dialogBuilder = new Builder(
ModelDeleteActivity.this);
dialogBuilder.setTitle("你真的要删除吗");
String[] strarrStrings = new String[] { "YES", "NO" };
dialogBuilder.setItems(strarrStrings,
new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog,
int which) {
switch (which) {
case 0:
if (mCursor.getInt(8) == 2) {
mDateBase.update("经典", 2);
}
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
Toast.makeText(
ModelDeleteActivity.this,
"模式已删除!", Toast.LENGTH_SHORT)
.show();
break;
case 1:
break;
default:
break;
}
}
});
dialogBuilder.show();
}
});
return myView;
}
}

希望能够帮助到你。。。
tfront 2011-03-10
  • 打赏
  • 举报
回复
补充两个定义:

private ListView mListView;

Handler mHandler = new Handler();

[Quote=引用 11 楼 tfront 的回复:]

如果是我,我想我会这么写。不知道是不是符合你的要求。
Java code

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
……
[/Quote]
tfront 2011-03-10
  • 打赏
  • 举报
回复
如果是我,我想我会这么写。不知道是不是符合你的要求。

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mListView = (ListView)findViewById(R.id.list);

new Thread(new Runnable() {
public void run() {
mHandler.postDelayed((new Runnable() {
public void run() {
AddListItem();
}
}), 1000);
}
}).start();
}
加载更多回复(8)

80,352

社区成员

发帖
与我相关
我的任务
社区描述
移动平台 Android
androidandroid-studioandroidx 技术论坛(原bbs)
社区管理员
  • Android
  • yechaoa
  • 失落夏天
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧