Android中线程与多线程学习(一)

在Android系统中,每当我们新启动一个Application(应用程序),就会启动一个主线程。主线程的入口是基于main函数的。

Android程序中的主线程,又称为UI线程,这是因为所有UI界面的绘制事件都只能在主线程中操作。主线程负责UI事件的分发。系统不会为每个组件单独创建线程,在同一个进程里的UI组件都会在UI线程里实例化,系统对每一个组件的调用都从UI线程分发出去。结果就是,响应系统回调的方法(比如响应用户动作的onKeyDown()和各种生命周期回调)永远都是在UI线程里运行。

一个小小的总结:主线程(Main Thread)=UI线程(UI Thread)

单线程模型常常导致我们的程序效率低下。因为单线程工作方式将会使得用户在使用应用过程中,必须在界面中等待一个耗时的操作。这些耗时的操作包括图形绘图,下载网络资源,加载数据库等等。(我们将这个耗时的过程称之为“阻塞“的。)当长时间的任务正在执行的时候,没有事件(Event)会被派发(dispatch),包括drawing event(绘图事件)。从用户的角度来看,该程序出现了终止或者说,是卡住了。甚至更糟糕的是,如果UI程序被阻塞(blocked)几秒后(大约5s)就会出现ANR 对话框(应用程序没有响应)。

我们来举一个例子,在主线程中通过设置一个Button点击事件来完成一个线程睡眠的操作。通过这段简单的代码来感受单线程模型的“不尽人意”。

      Button.setOnClickListener(new OnClickListener() {
 
      @Override
      public void onClick(View v) {
        // TODO Auto-generated method stub
        Thread.sleep(2000); 
        }
      });
补充完整的代码后,运行该安卓程序并点击该按钮触发事件,发现按钮将会保持2秒的按下状态然后恢复正常的状态。因为这个任务“阻塞”了主线程的UI绘制工作。这样很容易让用户感到程序很慢。

显然,解决单线程“阻塞”问题并提高程序效率的最有效途径就是多线程开发——让不同的线程并发完成独立的任务,以避免在主线程中执行长时间的后台任务。

接下来看下这段代码,我们尝试新建线程来完成后台下载网络图片资源的任务:

      imageView = (ImageView)findViewById(R.id.imageView1);
      Button.setOnClickListener(new OnClickListener(){
 
      @Override
      public void onClick(View v) {
        // TODO Auto-generated method stub
        new Thread(new Runnable(){
          public void run(){
           Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
           imageView.setImageBitmap(bitmap);   
          }
        }).start();
       }    
      });

 看起来感觉还可以。但运行时就会发现,在这里存在着一个错误:我们无法在子线程(主线程以外的线程)中访问UI组件。UI组件的属性必须在UI线程中访问和操作。 

解释原因在Google安卓官方的开发文档中提到:Andoid UI Toolkit并不是线程安全的,所以我们不能从非UI线程来操纵UI组件。UI操作必须在UI线程中执行。

由此我们能够得到安卓基于多线程开发概念中的两条重要原则:

  1.不要阻塞UI线程。  2.不要在UI线程之外访问Android UI toolkit(主要是这两个包中的组件:android.widget and android.view)。

那么,我们该怎样解决多线程开发的技术难题呢?安卓为我们提供了以下众多方式。

  1. Activity.runOnUiThread(Runnable)
  2. View.post(Runnable);
  3. View.postDelayed(Runnable,long);
  4. Handler
  5. AsyncTask

在这里我们简要地介绍下第5种技术——AsyncTask类。 AsyncTask是Android所设置出来的一个公共类,目的就是为子线程和主线程之间的通信提供一种简易,可行的方案。

根据AsyncTask的技术文档,我们将上述“在子线程下载网络图片”的代码改写成如下:

      imageView = (ImageView)findViewById(R.id.imageView1);
      Button.setOnClickListener(new OnClickListener(){
 
      @Override
      public void onClick(View v) {
        // TODO Auto-generated method stub
        new DownloadImageTask().execute("http://example.com/image.png");
       }    
      });
            
      private class DownloadImageTask extends AsyncTask {
        //doInBackground方法在AsyncTask实例化的时候自动执行
        protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
       }
        //图片加载完之后,让UI线程访问imageView组件
        protected void onPostExecute(Bitmap result) {
        imageView.setImageBitmap(result);
       }
      }

 

AsyncTask是一个抽象类,通过它的子类(初始化一个继承了AsyncTask公共类的子类实例时)才能使用。

要记住,一个AsyncTask实例必须在主线程创建并且只能被执行一次。要完全理解和使用这个类,读者可以阅读AsyncTask文档。这里快速的说一下AsyncTask是怎么工作的:

1. 可以通过泛型指定它的类型:参数,进度值,任务的结果值。
2. doInBackGround()方法自动在工作线程中自动执行。//在子线程完成工作的函数
3. onPreExecute(),onPostExecute(),onProgressUpdate()方法都在UI线程中执行。故名思议,"pre"代表在之前。"post"代表在之后。
4. doInBackground()方法返回的值被当作参数传递给onPostExecute()方法。
5. 你能够在doInBackground()方法里任何时候调用publishProgress()方法在UI线程中去执行onProgressUpdate()方法。


而前四种方式,依据我的个人理解,可以认为是一种“Handler类”式的处理方式。我们会在下一篇文章中讨论子线程通过Handler向主线程发布消息或runnable从而让主线程来执行UI组件属性访问、操作的方法。

参考资料及优秀文章:

  1. Andorid中轻松使用线程
  2. Android UI线程及非UI线程



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值