在用多线程获取数据的时候总是用Thread,要去启动一个子线程就去new 一个thread,久而久之,要做的事情多了,代码看起来就零散而臃肿,效率估计也不好,而且维护和扩展起来也不一定容易,估计大多数人也会这么写。
比如我之前一篇文章讲的sqlite分页查询。
其中有如下代码,。
这是在刚进入该画面的时候,启动线程,去数据库那数据,然后发送消息给mHandler刷新UI。
if (thread == null) {
thread = new MyThread();
thread.start();
}
当用户不断的滑动ListView以获取更多数据时,会重新开启一个线程,再去拿数据,然后发送消息刷新UI。
有如下代码:
cityListView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (view.getLastVisiblePosition() == view.getCount() - 1
&& scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
if (thread != null && !thread.isInterrupted()) {
thread.interrupt();
thread = null;
}
currentPage++;
cityListView.setSelection(view.getLastVisiblePosition());// 设置显示位置,这句只是让Listview停留在最后末尾的显示而已,加不加影响不大
thread = new MyThread();
thread.start();
}
}
......这只是个DEMO,所以不是很在意这些小问题。但是实际项目中,这样写并不是一个好的方式。
android提供了,HandlerThread,IntentService,AsyncTask等这些使用起来在某些情况下或许比较好一点。
接下来,我使用HandlerThread来修改之前的demo;
地址:http://blog.csdn.net/xxm282828/article/details/21437727
贴个代码先、
package com.example.sqlitepagetest; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Handler.Callback; import android.os.HandlerThread; import android.os.Message; import android.view.KeyEvent; import android.view.Menu; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ListView; /** * <p> * 使用HandlerThread 刷新数据 * </p> * 下午12:30:18 * * @auther dalvikCoder */ public class Activity4 extends Activity { private ListView cityListView; private List<cls_city> cityList; private CityAdapter cityAdapter; private HandlerThread handlerThread = null; /** 为线程设立标志位 **/ private boolean isThreadRunning = false; /** * 每页有数据条数 这个数量可以根据需要更改,而不需在程序中更改具体数值 * **/ private int perPageItemNum = 200; /** 当前是第几页 0表示第一页 **/ private int currentPage = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity4); setUpView(); } private void setUpView() { cityList = new ArrayList<cls_city>(); try { Common.loadCityDatabase(this); } catch (Exception e) { e.printStackTrace(); } Common.dbh = new DatabaseHelper(this, "city"); cityListView = (ListView) findViewById(R.id.citylistview); cityAdapter = new CityAdapter(this, cityList); cityListView.setAdapter(cityAdapter); cityListView.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (view.getLastVisiblePosition() == view.getCount() - 1 && scrollState == OnScrollListener.SCROLL_STATE_IDLE) { currentPage++; cityListView.setSelection(view.getLastVisiblePosition());// 设置显示位置,这句只是让Listview停留在最后末尾的显示而已,加不加影响不大 isThreadRunning = true; mHandler.post(runable); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }); handlerThread = new HandlerThread("myThread"); handlerThread.start(); mHandler = new Handler(handlerThread.getLooper()) { @Override public void handleMessage(Message msg) { @SuppressWarnings("unchecked") List<cls_city> dataList = (List<cls_city>) msg.obj; if (!dataList.isEmpty()) { cityAdapter.refresh(dataList); } isThreadRunning = false; super.handleMessage(msg); } }; isThreadRunning = true; mHandler.post(runable); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } /** 分页获取数据 **/ Runnable runable = new Runnable() { @Override public void run() { while (isThreadRunning) { int num[] = new int[2]; num[0] = currentPage * perPageItemNum;// 0*50 1*50 2*50 num[1] = perPageItemNum; List<cls_city> dataList = cls_city.getCityList(Common.dbh, num); Message msg = new Message(); msg.what = 1; msg.obj = dataList; mHandler.sendMessage(msg); } } }; /** to refresh UI **/ private Handler mHandler = null; /** * <p> * 这里我需要的效果是类似于QQ当我点击返回键后他不会退出,再次进来后他还是原来的状态 查阅资料google,sdk,这里涉及一个方法。 * </p> */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // 过滤按键动作 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { moveTaskToBack(true); } return super.onKeyDown(keyCode, event); } @Override protected void onResume() { isThreadRunning = true; super.onResume(); } @Override protected void onPause() { isThreadRunning = false; super.onPause(); } @Override protected void onStop() { isThreadRunning = false; super.onStop(); } @Override protected void onDestroy() { isThreadRunning = false; mHandler.removeCallbacks(runable); super.onDestroy(); } // @Override // public void onBackPressed() { // // moveTaskToBack(true); // super.onBackPressed(); // } }
问题:使用上面代码测试的时候发现。线程会不断的跑,而且消息发送出去后并没有执行到mHandler的handleMessage(Message msg)方法中区。
这里是Log.
一段时间后导致OOM.
因此,我换了种方式写。
代码如下:
package com.example.sqlitepagetest; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Handler.Callback; import android.os.HandlerThread; import android.os.Message; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ListView; /** * <p> * 使用HandlerThread 刷新数据 * </p> * 下午12:30:18 * * @auther dalvikCoder */ public class Activity5 extends Activity { private ListView cityListView; private List<cls_city> cityList; private CityAdapter cityAdapter; private mHandlerThread handlerThread = null; /** * 每页有数据条数 这个数量可以根据需要更改,而不需在程序中更改具体数值 * **/ private int perPageItemNum = 50; /** 当前是第几页 0表示第一页 **/ private int currentPage = 0; /** to refresh UI **/ private Handler mHandler = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity4); setUpView(); } private void setUpView() { cityList = new ArrayList<cls_city>(); try { Common.loadCityDatabase(this); } catch (Exception e) { e.printStackTrace(); } Common.dbh = new DatabaseHelper(this, "city"); cityListView = (ListView) findViewById(R.id.citylistview); cityAdapter = new CityAdapter(this, cityList); cityListView.setAdapter(cityAdapter); cityListView.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (view.getLastVisiblePosition() == view.getCount() - 1 && scrollState == OnScrollListener.SCROLL_STATE_IDLE) { Log.e("------------>", "翻页"); currentPage++; cityListView.setSelection(view.getLastVisiblePosition());// 设置显示位置,这句只是让Listview停留在最后末尾的显示而已,加不加影响不大 mHandler.sendEmptyMessage(2); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }); handlerThread = new mHandlerThread("mytest"); handlerThread.start(); mHandler = new Handler(handlerThread.getLooper(), handlerThread); mHandler.sendEmptyMessage(2); Log.e("--------oncreate------>", Thread.currentThread().getId() + ""); } class mHandlerThread extends HandlerThread implements Callback { public mHandlerThread(String name) { super(name); Log.e("--------mHandlerThread------>", Thread.currentThread() .getId() + ""); } List<cls_city> dataList = null; @Override public boolean handleMessage(Message msg) { Log.e("----------------->", "执行到handleMessage方法"); int num[] = new int[2]; num[0] = currentPage * perPageItemNum;// 0*50 1*50 2*50 num[1] = perPageItemNum; dataList = cls_city.getCityList(Common.dbh, num); Log.e("------handleMessage threadid----->", Thread.currentThread() .getId() + ""); // 更新UI必须在主线程中。 runOnUiThread(new Runnable() { public void run() { Log.e("--------runOnUiThread------>", Thread .currentThread().getId() + ""); if (!dataList.isEmpty()) { cityAdapter.refresh(dataList); } } }); return false; } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } /** * <p> * 这里我需要的效果是类似于QQ当我点击返回键后他不会退出,再次进来后他还是原来的状态 查阅资料google,sdk,这里涉及一个方法。 * </p> */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // 过滤按键动作 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { moveTaskToBack(true); } return super.onKeyDown(keyCode, event); } }
打印出来的Log截图如下,程序一切正常:
程序中我新建了一个类继承自HandlerThread类,实现Handler类中的回调接口
Open Declaration android.os.Handler.Callback
实现其handlerMessage方法。然后设计到更新UI部分调用Activity的runOnUiThread方法,即代码部分“”:
// 更新UI必须在主线程中。
runOnUiThread(new Runnable() {
public void run() {
Log.e("--------runOnUiThread------>", Thread
.currentThread().getId() + "");
if (!dataList.isEmpty()) {
cityAdapter.refresh(dataList);
}
}
});
从打印出来的Log看的出,handlerThread新开了一个线程、
问题描述:
另外一个思路,猜测可以这样实现:
public class HandlerThread extends Thread {
HandlerThread是一个线程类,因此他有run方法。我可以同时在我的类mHandlerThread中同时实现run和handleMessage方法。将从数据库拿数据的代码放到run中然后在handleMessage中处理消息更新UI。
但是结果会抛出异常。
代码如下:
package com.example.sqlitepagetest; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Handler.Callback; import android.os.HandlerThread; import android.os.Message; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.ListView; /** * <p> * 使用HandlerThread 刷新数据 * </p> * 下午12:30:18 * * @auther dalvikCoder */ public class Activity6 extends Activity { private ListView cityListView; private List<cls_city> cityList; private CityAdapter cityAdapter; private mHandlerThread handlerThread = null; /** * 每页有数据条数 这个数量可以根据需要更改,而不需在程序中更改具体数值 * **/ private int perPageItemNum = 50; /** 当前是第几页 0表示第一页 **/ private int currentPage = 0; /** to refresh UI **/ private Handler mHandler = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity4); setUpView(); } private void setUpView() { cityList = new ArrayList<cls_city>(); try { Common.loadCityDatabase(this); } catch (Exception e) { e.printStackTrace(); } Common.dbh = new DatabaseHelper(this, "city"); cityListView = (ListView) findViewById(R.id.citylistview); cityAdapter = new CityAdapter(this, cityList); cityListView.setAdapter(cityAdapter); cityListView.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (view.getLastVisiblePosition() == view.getCount() - 1 && scrollState == OnScrollListener.SCROLL_STATE_IDLE) { Log.e("------------>", "翻页"); currentPage++; cityListView.setSelection(view.getLastVisiblePosition());// 设置显示位置,这句只是让Listview停留在最后末尾的显示而已,加不加影响不大 mHandler.post(handlerThread); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }); handlerThread = new mHandlerThread("mytest"); handlerThread.start(); mHandler = new Handler(handlerThread.getLooper(), handlerThread); mHandler.post(handlerThread); Log.e("--------oncreate------>", Thread.currentThread().getId() + ""); } class mHandlerThread extends HandlerThread implements Callback { public mHandlerThread(String name) { super(name); Log.e("--------mHandlerThread------>", Thread.currentThread() .getId() + ""); } @Override public void run() { super.run(); int num[] = new int[2]; num[0] = currentPage * perPageItemNum;// 0*50 1*50 2*50 num[1] = perPageItemNum; List<cls_city> dataList = cls_city.getCityList(Common.dbh, num); Message msg = new Message(); msg.what = 2; msg.obj = dataList; mHandler.obtainMessage(); mHandler.sendMessage(msg); } @Override public boolean handleMessage(Message msg) { Log.e("----------------->", "执行到handleMessage方法"); Log.e("------handleMessage threadid----->", Thread.currentThread() .getId() + ""); final List<cls_city> obj = (List<cls_city>) msg.obj; // 更新UI必须在主线程中。 runOnUiThread(new Runnable() { public void run() { Log.e("--------runOnUiThread------>", Thread .currentThread().getId() + ""); if (!obj.isEmpty()) { cityAdapter.refresh(obj); } } }); return false; } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } /** * <p> * 这里我需要的效果是类似于QQ当我点击返回键后他不会退出,再次进来后他还是原来的状态 查阅资料google,sdk,这里涉及一个方法。 * </p> */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // 过滤按键动作 if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { moveTaskToBack(true); } return super.onKeyDown(keyCode, event); } }
异常截图截图如下:
原因:因为我在创建的时候启动了线程(他会执行run方法),而之前的代码是这样的,看上面
@Override
public void run() {
Log.e("--------run ------>", Thread.currentThread().getId() + "");
super.run();
int num[] = new int[2];
num[0] = currentPage * perPageItemNum;// 0*50 1*50 2*50
num[1] = perPageItemNum;
List<cls_city> dataList = cls_city.getCityList(Common.dbh, num);
会调用super.run() ;
查看源码 :
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
可以看出,HandlerThread有自己的Looper,super.run()被重复执行了,因此异常说每个线程的Looper只能有一个。具体为什么会被重复执行,以及如何解决,下次来把。
至于这个思路如何做好,有时间再继续研究。代码就在上面,有兴趣可以拿下来看下。欢迎各位批评指正。