Android进阶——多线程和异步任务小结

引言

众所周知,无论是在任何的程序语言和操作系统中。多线程、多进程和异步同步始终都是经久不衰的话题。当然在我们实际的Android项目需求中也是如此,很多的业务需求都通过多线程及异步任务以便用户能够在使用App中得到优秀的体验。而很多App在使用过程中出现各种莫名其妙的问题,多是由于开发人员使用多线程不当造成的,因此掌握在多线程及异步任务的原理和使用方法非常有必要。

一Android UI主线程设计原则

在开始总结多线程前,先讲下UI 线程(主线程)的一个基本原则——不要Block UI Thread;不要在UI线程外直接操作UI,每一个App运行之时,Android系统会自动为每一个App创建一个线程即主线程,只能在主线程(UI线程)中操作UI,这是因为在Android源码在线阅读中并没有对UI操作部分做线程同步处理,如果在非UI(非主线程)中操作UI就会导致线程安全问题,所以在非UI线程中操作UI运行时直接报错了。

二使用多线程的意义

我们在开发的过程中,很多业务需求都是非常耗时的,比如说IO操作、网络访问、数据库操作、上传下载文件等等,如果我们全部都放到主线程中去执行就会可能导致主线程阻塞,用户在使用APP的过程中就会产生卡顿的不良体验,自然对于APP满意度下降。为了给用户以最优秀的体验,前辈建议对于超过50ms(因为1000ms/50ms=20fps刚好是人眼的能感受到的最大值)的操作,都应该使用多线程去处理,才不至于给用户以卡顿的感受。

三使用多线程的方式

1、和Java的一样扩展java.lang.Thread类,即new 一个线程对象把run()方法写到线程里面

new Thread(new Runnable(){
    @Override
    public void run() {
        //在这里做耗时操作
        });
    }
}).start();

2、实现Runnable接口,让Activity类实现Runnable接口,然后把run方法单独提出来:

public class MutilThreadActivity extends Activity implements Runnable  {

    @Override  
    public void run() {  
       //在这里做耗时操作
    }  
}

3、利用线程池ExecutorService接口创建多线程

3.1 使用步骤

3.1.1利用Executors的静态方法newCachedThreadPool()、newFixedThreadPool()、newSingleThreadExecutor()及重载形式实例化ExecutorService接口即得到线程池对象
  • 动态线程池newCachedThreadPool() 是根据需求创建新线程的,需求多时,创建的就多,需求少时,JVM自己会慢慢的释放掉多余的线程
  • 固定数量的线程池newFixedThreadPool()内部有个任务阻塞队列,假设线程池里有3个线程,提交了5个任务,那么后两个任务就放在任务阻塞队列了,即使前3个任务sleep或者堵塞了,也不会执行后两个任务,除非前三个任务有执行完的
  • 单线程newSingleThreadExecutor()返回一个线程池(不过这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去
ExecutorService service =Executors.newFixedThreadPool(); 
ExecutorService service =Executors.newFixedThreadPool();
ExecutorService service =Executors.newFixedThreadPool(); 
3.1.2 创建ExecutorService对象之后,然后执行提交submit,可以发起一个Runnable对象。
 service.submit(new Runnable(){  

            @Override  
            public void run() {  
                 // 
            }  
        });  

3.2完整使用线程池的代码片段

private ExecutorService service =Executors.newFixedThreadPool(6);  
private void testByExecutors(){  
      service.submit(new Runnable(){  

          @Override  
          public void run() {  
            //在这做耗时操作
          }  
      });  
}

3.3 线程池的优势

比如说现在我们要展示800张图片如果创建800个线程去加载,保证系统会死掉。用线程池就可以避免这个问题,我们可以差创建用6个线程轮流执行,6个一组,执行完的线程不直接回收而是等待下次执行,这样对系统的开销就可以减小不少。所以用线程池来管理的好处是,可以保证系统稳定运行,适用与有大量线程,高工作量的情景下使用。

4 异步任务AsyncTask

四异步任务AsyncTask

在Android中实现异步任务机制有两种方式,Handler和AsyncTask。在这里先总结下AsyncTask

1 AsyncTask概述

AsyncTask主要用于后台与界面持续交互的,AsyncTask是个抽象类,使用时需要继承这个类,(把耗时的后台操作放到doInBackgound() 方法里,在onPostExecute()中完成UI操作),然后调用execute()方法。注意继承时需要设定三个泛型Params,Progress和Result的类型,其中:

  1. Params是指调用execute()方法时传入的参数类型和doInBackgound()的参数类型
  2. Progress是指更新进度时传递的参数类型,即publishProgress()和onProgressUpdate()的参数类型
  3. Result是指doInBackground()的返回值类型

2几个常用方法

  1. doInBackgound() 这个方法是继承AsyncTask必须要实现的,运行于后台,耗时的操作可以在这里做
  2. onPostExecute 在主线程中运行,可以用来写一些开始提示代码。
  3. publishProgress() 更新进度,给onProgressUpdate()传递进度参数
  4. onProgressUpdate() 在publishProgress()调用完被调用,更新进度

3 AsyncTask运行机制

Android进阶——多线程和异步任务小结_第1张图片
1. 主线程调用AsynTask子类实例的execute()方法后,首先会调用onPreExecute()方法。onPreExecute()在主线程中运行,可以用来写一些开始提示代码。
2. 之后启动新线程,调用doInBackground()方法,进行异步数据处理。
3. 处理完毕之后异步线程结束,在主线程中调用onPostExecute()方法。onPostExecute()可以进行一些结束提示处理。
补充:在doInBackground()方法异步处理的时候,如果希望通知主线程一些数据(如:处理进度)。这时,可以调用publishProgress()方法。这时,主线程会调用AsynTask子类的onProgressUpdate()方法进行处理。
4. 各个函数间数据的传递通过上面的调用关系,我们就可以大概看出一些数据传递关系。如下:
  execute()向doInBackground()传递。
  doInBackground()的返回值会传递给onPostExecute()。
  publishProgress()向progressUpdate()传递。
5. Android为了调用关系明确及安全,AsynTask类在继承时要传入3个泛型。第一个泛型对应execute()向doInBackground()的传递类型。第二个泛型对应doInBackground()的返回类型和传递给onPostExecute()的类型。第三个泛型对应publishProgress()向progressUpdate()传递的类型。传递的数据都是对应类型的数组,数组都是可变长的。可以根据具体情况使用。

4 异步的简单应用

结合WebView通过异步加载指定网页


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <EditText 
        android:id="@+id/title"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"

        />
    <ProgressBar 
        android:id="@+id/progressbar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#00f"
        android:layout_gravity="center"
        />
    <TextView android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <Button 
        android:id="@+id/load_web"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="加载指定网页"/>
LinearLayout>
package cmo.learn.activity;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PublicKey;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;

public class HandlerActivity extends Activity implements OnClickListener {
    private Button mUpdProgressBtn;
    private EditText mTitleEdt;
    private ProgressBar mProgressBar;
    private TextView mContentTxt;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_asynctask);
        init();
    }
    private void init(){
        getView();
        mTitleEdt.setText("http://www.hao123.com");
        mUpdProgressBtn.setOnClickListener(this);
    }
    private void getView(){
        mTitleEdt=(EditText) findViewById(R.id.title);
        mProgressBar=(ProgressBar) findViewById(R.id.progressbar);
        mUpdProgressBtn=(Button) findViewById(R.id.load_web);
        mContentTxt=(TextView) findViewById(R.id.content);
    }
    //异步加载网页内容
    class WebGetAsyncTask extends AsyncTask{

        @Override
        protected String doInBackground(String... params) {
            try {
                HttpClient client=new DefaultHttpClient();
                HttpGet get=new HttpGet(params[0]);
                HttpResponse response=client.execute(get);
                HttpEntity entity=response.getEntity();
                long length=entity.getContentLength();
                InputStream inStream=entity.getContent();
                String s=null;
                int toCase=0;
                if(inStream !=null){
                    ByteArrayOutputStream boas=new ByteArrayOutputStream();
                    byte[] buf=new byte[128];
                    int ch=-1;
                    int count=0;
                    while((ch=inStream.read(buf))!=-1){
                        boas.write(buf, 0, ch);
                        count +=ch;
                        if(length>0){
                            toCase=(int)((count/(float)length)*100);
                            publishProgress(toCase);
                        }
                        Thread.sleep(100);
                    }
                    s=new String(boas.toByteArray());
                    return s;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } 
            return null;
        }
        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            mContentTxt.setText(result);
        }
        @Override
        protected void onProgressUpdate(Integer... values) {
            mProgressBar.setProgress(values[0]);
        }

    }
    @Override
    public void onClick(View v) {
        new WebGetAsyncTask().execute(mTitleEdt.getText().toString());
    }
}

5 使用异步必须遵守的准则

  • AsyncTask的实例必须在UI Thread中创建
  • execute方法必须在主线程(UI Thread)中调用
  • 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…),
    onProgressUpdate(Progress…)这几个方法
  • 该AsyncTask实例只能被执行一次,否则多次调用时将会出现异常

6AsyncTask小结

初识这个异步调用关系可能觉得很复杂,但其实熟悉了之后会发现这种结构很好用。这种结构将所有的线程通信都封装成回调函数,调用逻辑容易书写。尤其是在异步处理结束之后,有回调函数进行收尾处理。如果是使用Thread的run()方法,run()结束之后没有返回值。所以必须要自己建立通信机制。但是,其实使用Handler+Thread机制其实完全可以替代AsynTask的这种调用机制。只要将Handler对象传给Thread,就可以进行方便的异步处理。个人经验,Handler+Thread适合进行大框架的异步处理,而AsynTask适用于小型简单的异步处理。仅仅代表个人观点和见解。

五一个综合的例子

布局文件很简单


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <ImageView 
        android:id="@+id/img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        />
    <Button
        android:id="@+id/thread"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load and set in Main Thread"/>
    <Button
        android:id="@+id/thread2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load and set in Thread"/>
    <Button
        android:id="@+id/thread3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load in Thread,set by View.post(Runnable)"/>
    <Button
        android:id="@+id/thread4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Load in Thread,set by AsyncTask"/>
LinearLayout>
package cmo.learn.activity;

import java.net.URL;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class MutilThreadActivity extends Activity {
    /**
     * 
     * Android UI主线程简单原则:不要Block UI Thread;不要在UI线程外直接操作UI
     * 
     */
    private Button mMainThreadBtn;
    private Button mThread2Btn;
    private Button mThread3Btn;
    private Button mThread4Btn;
    private ImageView mImg;
    private final static String IMAGE_URL="http://www.lhzhang.org/image.axd?pictrue=/201102/46613566.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mutilthread);
        init();
        //1 在主线程中加载图片到Image中
        mMainThreadBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Drawable drawable=loadImageFromNet(IMAGE_URL,"Main Thread");
                mImg.setImageDrawable(drawable);
            }
        });
        //2 在Thread子线程中加载到ImageView,但是会有线程安全问题,直接报错
        mThread2Btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                new Thread(new Runnable(){
                    @Override
                    public void run() {
                        Drawable drawable=loadImageFromNet(IMAGE_URL,"Runnale Thread");
                        mImg.setImageDrawable(drawable);
                    }
                }).start();
            }
        });
        //3 加载图片在子线程,把是通过View.post(Runnable)设置图片到ImageView
        mThread3Btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                //在子线程中从网络中加载Image
                new Thread(new Runnable(){
                    @Override
                    public void run() {
                        final Drawable drawable=loadImageFromNet(IMAGE_URL,"Runnale Thread By Post");
                        //通过post把set操作放到了UI线程
                        mImg.post(new Runnable(){

                            @Override
                            public void run() {
                                mImg.setImageDrawable(drawable);
                            }
                        });
                    }
                }).start();
            }
        });
        //4加载图片 在子线程中,通过异步AsyncTask设置到ImageView
        mThread4Btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                new LoadImgAsyncTask().execute(IMAGE_URL);
            }
        });
    }
    private void init(){
        getView();
    }

    private void getView(){
        mImg=(ImageView) findViewById(R.id.img);
        mMainThreadBtn=(Button) findViewById(R.id.thread);
        mThread2Btn=(Button) findViewById(R.id.thread2);
        mThread3Btn=(Button) findViewById(R.id.thread3);
        mThread4Btn=(Button) findViewById(R.id.thread4);
    }
    private Drawable loadImageFromNet(String imageUrl,String tag){
        Drawable drawable=null;
        try{
            drawable=Drawable.createFromStream(new URL(imageUrl).openStream(), "img_1.png");
        }catch(Exception e){

        }
        if(drawable==null){
            Log.d(tag, "null drawable");
        }else{
            Log.d(tag, "not null drawable");
        }
        return drawable;
    }
    //在后台操作获取数据,再把操作数据集返回到前台进行一些UI更新操作
    private class LoadImgAsyncTask extends AsyncTask{

        //在工作线程中完成获得Image,并把返回结果传递到AsyncTask.execute()
        @Override
        protected Drawable doInBackground(String... params) {
            return loadImageFromNet(IMAGE_URL, "Thread Runnable AsyncTask");
        }
        //把doInBackGround的结果接收并作为参数传递到UI线程中实现设置到ImageView
        @Override
        protected void onPostExecute(Drawable result) {
            mImg.setImageDrawable(result);
        }
    }
}

你可能感兴趣的:(Android,进阶,Android系统组件使用)