在安卓中,网络加载数据或准备数据后显示在界面是一种很频繁的需求,大体来说就是在线程内下载了数据之后需要在ui线程内去更新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; }
然后往下看找到最后调用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深入了解