android多线程与UI操作

  在android开发中经常听到这样一句话——“android的UI操作不是线程安全的,同时也只有主线程才能够操作UI”。对于这句话,一直感觉不是太理解。当初心里想既然android的UI操作只能在UI线程即主线程中操作,别的线程不允许操作。所以是不会出现线程的同步问题的,这不应该是很安全的吗?为什么不是线程安全的呢?最近才想明白这句话什么意思:正是因为android的UI操作不是线程安全的,所以才不允许在非UI线程中进行UI操作。试想假如允许在其它工作线程中允许直接进行UI操作,会带来一个问题:多个线程同时操作一个控件可能会有冲突发生。所以android就限定了只能在UI线程中操作各种view。那么如果其它线程需要操作view应该怎么办呢?答案是通过Handler,Handler是子线程与UI主线程通信的桥梁,子线程通过Handler去通知主线程操作UI.

  主线程的主要任务就是负责处理与UI相关的事件,比如按钮的按下,并且负责分发相应的事件到相应的组件上。鉴于此,其他一些比较耗时的操作比如:从网络上下载东西,数据库操作等最好不要放到主线程中进行。否则可能导致主线程的阻塞,给用户的感觉就是程序的卡顿。我们要做的就是新开线程处理这些比较耗时的操作,然后在适当的时机通知主线程进行UI的更新操作。

看下面的例子:

主界面上有一个按钮,点击此按钮从网络上下载一个图片,然后显示到一个ImageView上。

解决方案1:

新开一个线程完成下载操作,然后再利用handler.post(runnable())方法将进行UI的操作post到主线程中执行。

代码如下:

View Code
 1 package learn.again.android;
 2 
 3 import java.io.IOException;
 4 
 5 import android.os.Bundle;
 6 import android.os.Handler;
 7 import android.view.View;
 8 import android.view.View.OnClickListener;
 9 import android.widget.ImageView;
10 import android.app.Activity;
11 import android.graphics.Bitmap;
12 
13 public class MainActivity extends Activity {
14     protected static final int SHOW_IMAGE = 0;
15     private ImageView image;
16 
17     
18     private Handler myHandler = new Handler();
19 
20     @Override
21     protected void onCreate(Bundle savedInstanceState) {
22         super.onCreate(savedInstanceState);
23         setContentView(R.layout.activity_main);
24         image = (ImageView) findViewById(R.id.image);
25         findViewById(R.id.download_image).setOnClickListener(
26                 new OnClickListener() {
27 
28                     @Override
29                     public void onClick(View v) {
30                         // download image to simulate LongOperation
31                         downloadImage("http://www.android.com/images/logo.png");
32                     }
33 
34                 });
35     }
36 
37 // cut-off rule................................................................................
38 
39     /**
40      * start a new thread to download image
41      * 
42      * @param url
43      *            image address
44      * @throws IOException
45      */
46     // 方法1
47     private void downloadImage(final String url) {
48 
49         new Thread(new Runnable() {
50 
51             @Override
52             public void run() {
53                 try {
54                     final Bitmap bitmap = ImageUtil.getImageByUrl(url);
55                     myHandler.post(new Runnable() {
56                         @Override
57                         public void run() {
58                             image.setImageBitmap(bitmap);
59                         }
60                     });
61                 } catch (IOException e) {
62                     e.printStackTrace();
63                 }
64             }
65         }).start();
66 
67     }
68 }

 解决方案2:

我们可以重写Handler类复写handleMessage方法,在子线程中通过handler.sendMessage()方法把下载好的bitmap发回主线程,再在主线程中通过image.setImageBitmap(bitmap)完成。

代码如下:

View Code
 1 package learn.again.android;
 2 
 3 import java.io.IOException;
 4 
 5 import android.os.Bundle;
 6 import android.os.Handler;
 7 import android.os.Message;
 8 import android.view.View;
 9 import android.view.View.OnClickListener;
10 import android.widget.ImageView;
11 import android.app.Activity;
12 import android.graphics.Bitmap;
13 
14 public class MainActivity extends Activity {
15     protected static final int SHOW_IMAGE = 0;
16     private ImageView image;
17 
18     /*
19      * Handler类应该为static类型,否则有可能造成泄露。 在程序消息队列中排队的消息保持了对目标Handler类的应用。
20      * 如果Handler是个内部类,那么它也会保持它所在的外部类的引用。
21      * 为了避免泄露这个外部类,应该将Handler声明为static嵌套类,并且使用对外部类的弱应用。
22      */
23     private Handler myHandler = new Handler() {
24         @Override
25         public void handleMessage(Message msg) {
26             super.handleMessage(msg);
27             switch (msg.what) {
28             case SHOW_IMAGE:
29                 Bitmap bm = (Bitmap) msg.obj;
30                 image.setImageBitmap(bm);
31                 break;
32             }
33         }
34 
35     };
36 
37     @Override
38     protected void onCreate(Bundle savedInstanceState) {
39         super.onCreate(savedInstanceState);
40         setContentView(R.layout.activity_main);
41         image = (ImageView) findViewById(R.id.image);
42         findViewById(R.id.download_image).setOnClickListener(
43                 new OnClickListener() {
44 
45                     @Override
46                     public void onClick(View v) {
47                         // download image to simulate LongOperation
48                         downloadImage_2("http://www.android.com/images/logo.png");
49                     }
50 
51                 });
52     }
53 
54     // cut-off rule...................................................................
55     private void downloadImage_2(final String url) {
56 
57         new Thread(new Runnable() {
58 
59             @Override
60             public void run() {
61                 try {
62                     final Bitmap bitmap = ImageUtil.getImageByUrl(url);
63                     Message msg = Message.obtain();
64                     msg.what = SHOW_IMAGE;
65                     msg.obj = bitmap;
66                     myHandler.sendMessage(msg);
67                 } catch (IOException e) {
68                     e.printStackTrace();
69                 }
70             }
71         }).start();
72 
73     }
74 }

总结:

第一种方法是通过handler的post方法将一个Runnable对象置于UI线程中去执行,达到目的。第二种方法是能过handler的sendMessage方法,将主线程更新view所需要的东西发回到主线程中。两种方法的对比:第一种发送的是Runnable对象,第二种发送的是Message对象。这两种方法都能达到目的但使用第一种时要注意所发送的Runnable中的代码不能太复杂,否则还是会导致UI的卡顿。第二种方法要自己重写Handler的handleMessage方法。

最后附上ImageUtil类的代码:

ImageUtil
 1 package learn.again.android;
 2 
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 import java.net.HttpURLConnection;
 6 import java.net.URL;
 7 
 8 import android.graphics.Bitmap;
 9 import android.graphics.BitmapFactory;
10 
11 public class ImageUtil {
12     public static Bitmap getImageByUrl(String url) throws IOException {
13         URL url_image = new URL(url);
14         HttpURLConnection con = (HttpURLConnection) url_image.openConnection();
15         con.setRequestMethod("GET");
16         con.setReadTimeout(5000);
17         InputStream is = con.getInputStream();
18         Bitmap bitmap = BitmapFactory.decodeStream(is);
19         return bitmap;
20     }
21 }

 方案3:

还有一个比较轻量级的方法,Handler有一个以Callback接口为参数的构造函数

Handler(Callback callback),通过此构造函数我们可以避免实现Handler的子类而就可以达到处理消息的效果。
关键代码:
1 Handler myHandler = new Handler(new Callback() {
2         
3         @Override
4         public boolean handleMessage(Message msg) {
5             Bitmap bm = (Bitmap) msg.obj;
6             image.setImageBitmap(bm);
7             return true;
8         }
9     });

其实,和第一种方案类似。

方案4:

我们翻看View的方法会发现View也有一个Post方法:

public boolean post (Runnable action)

因为这个方法是View的所以调用这个方法,它会将action封装成一个Message然后投递到UI线程的消息队列中。

 1 private void downloadImage3(final String url) {
 2 
 3         new Thread(new Runnable() {
 4 
 5             @Override
 6             public void run() {
 7                 try {
 8                     final Bitmap bitmap = ImageUtil.getImageByUrl(url);
 9                     image.post(new Runnable() {
10                         @Override
11                         public void run() {
12                             image.setImageBitmap(bitmap);
13                         }
14                     });
15                 } catch (IOException e) {
16                     e.printStackTrace();
17                 }
18             }
19         }).start();
20 
21     }

这样的话我们就不需要自己new Handler对象了,通过翻看源码我们可以看到其实它也先获取view线程即主线程的handler对象然后再用此handler投递此action对象封装的Message.

原文地址:https://www.cnblogs.com/byghui/p/3029681.html