Android 多線程編程

ka7231zfvl 8年前發布 | 17K 次閱讀 多線程 Android開發 移動開發 Android

在Android中,我們繪制圖形界面的線程即是主線程,也叫UI線程。由于在主線程中進行過于耗時的操作(Activity超過5秒,BroadCast超過10秒)會導致ANR(Application Not Responding,應用程序無響應),而且Android4.0以后也規定,不允許在主線程中進行網絡操作(耗時操作),因此對于耗時操作我們并需在新開啟的線程中執行,并通過線程間的通信機制在主線程中更新UI。以下總結了幾種多線程編程的方法:

 

1.開啟新線程,使用handler通信

這是界面的xml代碼:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="horizontal"
    tools:context="com.example.test.MyActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="1+1 = "
        android:textSize="24sp" />

    <TextView
        android:id="@+id/ans"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="\?"
        android:textSize="24sp" />

</LinearLayout>


布局只有一行文字,非常簡單。

再來是Activity的代碼:

package com.example.test;

import java.util.concurrent.TimeUnit;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;


public class MyActivity extends Activity{

    // 使用handler實現線程間通信
    private Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            tv.setText(msg.arg1+"");
        };
    };
    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.ans);
        // 使用匿名類開啟新線程
        new Thread(new Runnable() {

            @Override
            public void run() {
                // 模擬耗時操作,等待3秒
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (Exception e) {};
                // 使用handler實現線程間通信
                // 發送消息
                Message message = Message.obtain();
                message.arg1 = 2;
                handler.sendMessage(message);

                // post Runnable也可以
//              handler.post(new Runnable() {
//                  
//                  @Override
//                  public void run() {
//                      tv.setText("2");
//                  }
//              });

                // runOnUiThread也行
//              MyActivity.this.runOnUiThread(new Runnable() {
//                  
//                  @Override
//                  public void run() {
//                      tv.setText("2");
//                  }
//              });
            }
        }).start();
    }

}


在上面我們開啟了一個子線程來處理延時事件(讓線程休眠模擬延時),并使用了handler發送消息來與主線程通信,在主線程中更新UI界面。我們還可以使用handler.post和Activity的runOnUiThread來傳入Runnable參數更新UI界面,但其實兩者都是使用handler發送消息并添加回調函數來實現的。

效果圖如下:

thread效果圖

2.Executor

除了上面手動新建線程來進行多線程編程,我們還可以使用JavaSE5為我們提供的Executor(執行器),他將替我們管理Thread對象,簡化了我們的多線程編程。代碼如下:

package com.example.test;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;

public class MyActivity extends Activity {

    // 使用handler實現線程間通信
    private Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            tv.setText(msg.arg1 + "");
        };
    };
    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.ans);

        // 使用線程池來開啟新線程
        ExecutorService executor = Executors.newCachedThreadPool();
        executor.execute(new Runnable() {

            @Override
            public void run() {
                // 模擬耗時操作,等待3秒
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (Exception e) {
                }
                ;
                // 使用handler實現線程間通信
                // 發送消息
                Message message = Message.obtain();
                message.arg1 = 2;
                handler.sendMessage(message);
                // post Runnable也可以
//              handler.post(new Runnable() {
//
//                  @Override
//                  public void run() {
//                      tv.setText("2");
//                  }
//              });
                // runOnUiThread也行
//              MyActivity.this.runOnUiThread(new Runnable() {
//
//                  @Override
//                  public void run() {
//                      tv.setText("2");
//                  }
//              });
            }
        });
    }

}

上面使用了CachedThreadPool線程池,他會在執行過程中創建與所需數目相同的線程,然后會回收不使用的舊線程而非創建新線程。除此以外Java還提供了別的多種線程池,在此不一一描述。使用Executor會更便于我們管理線程,而且在線程數較多的時候也能使代碼看起來更優雅。

由于效果圖一樣,在此就不再貼圖。

3.AsyncTask

AsyncTask是Android提供的異步任務類,本質是用Java線程池改造的。以下是Java代碼:

package com.example.test;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;

public class MyActivity extends Activity {

    // 使用AsyncTask實現多線程編程
    private AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            // UI線程預處理
        }
        @Override
        protected Void doInBackground(Void... params) {
            // 線程休眠3秒模擬耗時操作
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {};
            return null;
        }
        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
            // doInBackground調用publishProgress(values)時調用該方法;
            // 該方法處于UI線程,可以更新UI!
        }
        @Override
        protected void onPostExecute(Void result) {
            super.onPostExecute(result);
            // 處理結果
            tv.setText("2");
        }
    };
    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.ans);
        // 必須在UI線程使用
        task.execute();
    }

}


AsyncTask的三個泛型參數分別代表:處理時傳給Task的參數類型,打印過程時所需數據的參數類型,返回結果的類型,如果不需要該參數只要填寫Void即可。AsyncTask需要重寫幾個關鍵的方法,在代碼中已有注釋在此不再贅述。值的注意的是,同一個AsyncTask不可execute多次,否則會發生java.lang.IllegalStateException: Cannot execute task: the task is already running.的錯誤。如果你想開啟多個新線程,應該考慮繼承AsyncTask類并創建多個實例。同時AsyncTask還存在著些許奇奇怪怪的問題,本文在此也不做深入探究。

 

4.使用Loader類

Android在3.0以后,SDK提供了Loader技術,使用Loader技術可以很容易進行數據的異步加載。Loader技術為我們提供的核心類有:

  1. LoaderManager:可以通過Activity或者的Fragment的getLoaderManager()方法得到LoaderManager,用來對Loader進行管理,一個Activity或者Fragment只能有一個LoaderManager。
  2. LoaderManager.LoaderCallbacks:用于同LoaderManager進行交互,可以在其中創建Loader對象。
  3. AsyncTaskLoader:抽象類,可以進行異步加載數據的Loader,貌似內部也是通過AsynTask實現的,可以通過繼承它構建自己的Loader,也可以使用現有的子類,例如異步查詢數據庫可以使用CursorLoader。

一般而言還是使用官方實現好的Loader,自己重寫Loader比較麻煩。

以下是我自己重寫Loader的例子:

package com.example.test;

import java.util.concurrent.TimeUnit;

import android.app.Activity;
import android.app.LoaderManager;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class MyActivity extends Activity {

    private static String TAG = "MyActivity";
    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.ans);
        getLoaderManager().initLoader(0, null, new MyLoaderCallbacks());
    }

    private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks<Integer> {
        @Override
        public Loader<Integer> onCreateLoader(int id, Bundle args) {
            Log.v(TAG, "onCreateLoader");
            // UI預處理
            return new MyLoader(getApplicationContext());
        }
        @Override
        public void onLoaderReset(Loader<Integer> loader) {
            Log.v(TAG, "onLoaderReset");
            // 處理Loader被reset的情況,在此需要清除掉對上一個data的引用
        }
        @Override
        public void onLoadFinished(Loader<Integer> loader, Integer data) {
            Log.v(TAG, "onLoadFinished");
            // 處理UI結果
            tv.setText(data+"");
        }
    }

    // 必須是靜態類,否則會有RuntimeException
    private static class MyLoader extends AsyncTaskLoader<Integer> {
        public MyLoader(Context context) {
            super(context);
        }
        @Override
        protected void onStartLoading() {
            Log.v(TAG, "onStartLoading");
            // 調用forceLoad來開啟新線程執行任務
            forceLoad();
        }
        @Override
        public Integer loadInBackground() {
            Log.v(TAG, "loadInBackground");
            // 模擬耗時操作
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {};
            return 2;
        }
    }

}


值的注意的有幾個地方:

  1. LoaderCallbacks的泛型代表處理結果的返回類型
  2. 自己寫的AsyncTaskLoader的子類必須為靜態類
  3. 必須在自己寫的Loader中調用forceLoad方法,否則Loader不會開啟新線程執行操作

與AsyncTask相比,Loader有了更多處理上的優化,比如加載被中斷后自動保存現場等,本文在此不做深入探討。

 

綜上所述,在Android中實現多線程編程的方法有:

  1. 手動新建線程,使用Handler進行通信
  2. 使用線程池新建線程,使用Handler進行通信
  3. 使用Android提供的AsyncTask工具類
  4. 使用Android提供的Loader工具類

來自: http://blog.csdn.net/superxlcr/article/details/50890769

 本文由用戶 ka7231zfvl 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!