• 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.

  • 相关阅读:
    c#結合正則表達式驗證輸入的字符串
    [SQL]数据库设计说明书模板
    如何在网页中插入Flv视频文件
    JAVASCRIPT常用检验代码
    Javascript知识精华
    提高Remoting的DataSet传输的性能
    如何快速的在SharePoint里构建一个Blog站点
    可爱的 Python,可爱的.NET
    郁闷的Xml Serialization BUG
    初试Zope(1)
  • 原文地址:https://www.cnblogs.com/byghui/p/3029681.html
Copyright © 2020-2023  润新知