在安卓中,网络加载数据或准备数据后显示在界面是一种很频繁的需求,大体来说就是在线程内下载了数据之后需要在ui线程内去更新ui界面,我们需要避免以下两点
- 绝对不能在UI Thread当中进行耗时的操作,不能阻塞我们的UI Thread
- 不能在UI Thread之外的线程当中操纵我们的UI元素
那么,在我们实际代码中,采用什么方式来达到很好的线程内耗时操作+ui线程中更新界面呢,方式其实是有很多的
按照我之前在工作中的方式来说的话,是new Thread()来在线程内进行耗时操作,然后在操作完成之后,通过
new Handler(context.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// 在这里执行你要想的操作 比如直接在这里更新ui或者调用回调在 在回调中更新ui
}
});
这种方式来防止线程内操作界面刷新报错,但是,可以说我在代码中这样写的很痛苦(所以说看着我以前写的代码真的想砍了自己),所以我需要一种新的很好的方式来进行规避,我深入了解了Handler,这里有一个最简单的例子
public class MainActivity extends Activity{
private Handler handler;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler(){
public void handleMessage(Message msg) {
Log.i("test", "收到消息:" + msg.what);
A a = (A)msg.obj;
Log.i("test", "收到对象了吗:" + a.getId() + ",名称:" + a.getName());
mTextView.setText(a.getName());
}
};
mTextView = (TextView)findViewById(R.id.activity_main_text_title);
initClick();
}
private void initClick(){
Log.i("test", "这是:" + mTextView);
mTextView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//采用这种方式新建Message更好,因为这种方法有内存池管理新建的Message对象
Message msg = Message.obtain();
msg.what = 1;
msg.obj = new A("3","lilei");
handler.sendMessage(msg);
}
}).start();
}
});
}
}
然后下面是输出
08-16 16:31:31.196: I/test(31963): 收到消息:1
08-16 16:31:31.196: I/test(31963): 收到对象了吗:3,名称:lilei
二、Handler的线程间通信方法
这里Handler调用时的方法有post(Runnable), postAtTime(Runnable, long),postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message),sendMessageAtTime(Message, long)和sendMessageDelayed(Message,long)等方法,方法我就不一一介绍了,看方法名称就能猜测出来一点
如果根据Handler的构造器来看的话,是可以发现这一点的
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Looper.myLooper()获取了当前线程保存的Looper实例,然后又获取了这个Looper实例中保存的MessageQueue(消息队列),这样就保证了handler的实例与我们Looper实例中MessageQueue关联上了
然后往下看找到最后调用handleMessage的地方的话就可以确认这一点
Handler在哪个线程新建,最后的handleMessage就在哪个线程执行
在这里我们也会发现一点,
if (mLooper == null) {
这一句,一般来说是不会无的放矢的,那说明获取到的当前的mLooper是有可能为空的,那么在什么情况下会为空呢?最后发现这种情况就是我们自己新建的工作线程中
除开android的ui线程,创建的工作线程默认是没有消息循环和消息队列的,如果想让该线程具有消息队列和消息循环,需要在线程中首先 调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
那么在收集到这些,我们是否就可以利用这一点进行线程之间的通信?关于这一点,我用代码试验了一下,结论是可以的
public class MainActivity extends Activity{
private Handler handler;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
final A outTag = new A("15","lilei");
Looper.prepare();
handler = new Handler(){
public void handleMessage(Message msg) {
Log.i("test","callback1:" + msg.what);
A a = (A)msg.obj;
outTag.setId(a.getId());
outTag.setName(a.getName());
Log.i("test","callback2:" + a.getId() + ",aname:" + a.getName());
}
};
Looper.loop();
}
}).start();
mTextView = (TextView)findViewById(R.id.activity_main_text_title);
initClick();
}
private void initClick(){
mTextView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Message msg = Message.obtain();
msg.what = 1;
msg.obj = new A("50","hanmeimei");
handler.sendMessage(msg);
}
}).start();
}
});
}
}
在我点击了回调之后,在第一个新建的线程内输出了点击时新建的线程传递过去的值,如果将Looper.prepare(); 和Looper.loop();去除的话就会报错
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
由此可以发现一点规律,其实在子线程里面进行数据操作之后在ui更新界面也是一样的
Handler会绑定在指定线程上,然后可以在任意其他线程将处理过的数据传递过来,或者通知,已经处理结束了
三、线程的队列执行
在这里留个尾巴,是Handler的可以队列执行的方式
HandlerThread handlerThread = new HandlerThread("myHandlerThread");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
<pre name="code" class="java"> handler<span style="font-family: Arial; font-size: 14px; line-height: 26px;">.sendEmptyMessage(1);</span>
这样就可以在线程内队列执行任务,一个很方便的方法
找时间的时候再对Handler深入了解