android開發筆記之異步ThreadPoolExecutor

android異步開發

android異步開發,主要有
1.Thread+Handler進行異步處理
2.繼承Thread類和實現Runnable接口
3.AsyncTask類
4.RxJava
5.AsyncQueryHandler

但是事實上AsyncTask是有缺點的。

syncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.

AsyncTask是一個幫助類,它非常適合一個後臺短耗時操作(最多幾秒)。如果你要使後臺進程保持運行非常長的時間,強烈推薦你使用 Executor, ThreadPoolExecutor 和FutureTask。

所以下面我們就看看ThreadPoolExecutorFutureTask的使用方法.

ThreadPoolExecutor

我們直接查看android源碼中ThreadPoolExecutor的使用情況:

grep -rni --include=*.java "ThreadPoolExecutor "  ./packages/ 
./packages/inputmethods/LatinIME/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java:113:    
private ThreadPoolExecutor mExecutor;
./packages/inputmethods/LatinIME/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java:124:        
// Executors#newSingleThreadExecutor creates a ThreadPoolExecutor but it returns the

具體我們查看關鍵的源碼:
packages/inputmethods/LatinIME/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java

public final class DictionaryService extends Service {

    /**
     * An executor that serializes tasks given to it.
     */
    private ThreadPoolExecutor mExecutor;
    private static final int WORKER_THREAD_TIMEOUT_SECONDS = 15;

    @Override
    public void onCreate() {
        mExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
                WORKER_THREAD_TIMEOUT_SECONDS /* keepAliveTime */,
                TimeUnit.SECONDS /* unit for keepAliveTime */,
                new LinkedBlockingQueue<Runnable>() /* workQueue */);
        mExecutor.allowCoreThreadTimeOut(true);
    }

    @Override
    public synchronized int onStartCommand(final Intent intent, final int flags,
            final int startId) {
            ........................
            mExecutor.submit(new Runnable() {
                @Override
                public void run() {
                ..........................
            });

其實上面也就給出了ThreadPoolExecutor 的一個使用樣例了。

我們仿照上面的例子,寫一個Demo:

我們使用ThreadPoolExecutor 來實現一個後臺的60秒的耗時操作,然後再通過handler更新UI界面。

public class MainActivity extends AppCompatActivity {

    private final static String TAG = "MainActivity";

    private final static int UPDEAT_UI =1;

    private TextView textView;
    private Button button_ThreadPoolExecutor;

    private ThreadPoolExecutor mExecutor;
    private static final int WORKER_THREAD_TIMEOUT_SECONDS = 15;

    private  Handler handler =  new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch(msg.what){
                case UPDEAT_UI:
                    //Toast.makeText(getApplication(),"on do long funtion",Toast.LENGTH_LONG).show();
                    textView.setText("UPDEAT_UI--ThreadPoolExecutor");
                    Log.i(TAG,"handler.handleMessage--UPDEAT_UI");
                    break;
                default :
                    break;
            }
        }
    };

    private  Runnable runnable = new Runnable() {
        @Override
        public void run() {
            //on do long time function
            try {
                Thread.sleep(1000*60);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Log.i(TAG,"handler.sendEmptyMessage--UPDEAT_UI");
            handler.sendEmptyMessage(UPDEAT_UI);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init() {
        mExecutor = new ThreadPoolExecutor(
                1 /* corePoolSize */,
                1 /* maximumPoolSize */,
                WORKER_THREAD_TIMEOUT_SECONDS /* keepAliveTime */,
                TimeUnit.SECONDS /* unit for keepAliveTime */,
                new LinkedBlockingQueue<Runnable>() /* workQueue */);
        mExecutor.allowCoreThreadTimeOut(true);

        textView = (TextView) findViewById(R.id.textView);

        button_ThreadPoolExecutor = (Button) findViewById(R.id.button_ThreadPoolExecutor);
        button_ThreadPoolExecutor.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.i(TAG,"button_ThreadPoolExecutor---onClick");
                //mExecutor.submit(runnable);
                mExecutor.execute(runnable);
            }
        });
    }
}

構造函數

ThreadPoolExecutor(
 int corePoolSize,
 int maximumPoolSize, 
 long keepAliveTime, 
 TimeUnit unit, 
 BlockingQueue<Runnable> workQueue,
  ThreadFactory threadFactory, 
  RejectedExecutionHandler handler) 

這個是ThreadPoolExecutor完整的構造器,其他的構造器其實也是在內部調用這個.

corePoolSize 核心線程數,線程池保留線程的數量,即使這些線程是空閒.除非設置了allowCoreThreadTimeOut
maximumPoolSize 線程池最大允許的線程數.
keepAliveTime 噹噹前的線程數大於核心線程數,那麼這些多餘的空閒的線程在被終止之前能等待新任務的時間.
unit keepAliveTime時間的單位
workQueue 這個是用來保留將要執行的工作隊列.
threadFactory 用於創建新線程的工廠
handler 如果工作隊列(workQueue)滿了,那麼這個handler是將會被執行.

Android中的線程池的分類

通過配置不同參數的ThreadPoolExecutor, 有4類常用的線程池, Executors類提供了4個工廠方法用於創建4種不同特性的線程池給開發者用.

FixedThreadPool
用法:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(num);
fixedThreadPool.execute(runnable對象);
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>());
}

特點:只有核心線程數,並且沒有超時機制,因此核心線程即使閒置時,也不會被回收,因此能更快的響應外界的請求.

CachedThreadPool
用法:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(runnable對象);
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                              60L, TimeUnit.SECONDS,
                              new SynchronousQueue<Runnable>());
}

特點:沒有核心線程,非核心線程數量沒有限制, 超時爲60秒.
適用於執行大量耗時較少的任務,當線程閒置超過60秒時就會被系統回收掉,當所有線程都被系統回收後,它幾乎不佔用任何系統資源.

ScheduledThreadPool
用法:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
scheduledThreadPool.schedule(runnable對象, 2000, TimeUnit.MILLISECONDS);
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

特點:核心線程數是固定的,非核心線程數量沒有限制, 沒有超時機制.
主要用於執行定時任務和具有固定週期的重複任務.

SingleThreadExecutor

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(runnable對象);
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                            0L, TimeUnit.MILLISECONDS,
                            new LinkedBlockingQueue<Runnable>()));
}

特點:只有一個核心線程,並沒有超時機制.
意義在於統一所有的外界任務到一個線程中, 這使得在這些任務之間不需要處理線程同步的問題.

Android中的四種線程池的使用源碼

我們在源碼中搜索ExecutorService,會發現大把的使用樣例。下面我們就簡單的列舉幾個吧。

Executors.newSingleThreadExecutor

packages\apps\SnapdragonGallery\src\com\android\photos\drawables\AutoThumbnailDrawable.java

private static ExecutorService sThreadPool = Executors.newSingleThreadExecutor();
sThreadPool.execute(mLoadBitmap);
.........................................
    private final Runnable mLoadBitmap = new Runnable() {
        @Override
        public void run() {
      
        }
    };

packages\apps\Contacts\src\com\android\contacts\vcard\VCardService.java

    // Should be single thread, as we don't want to simultaneously handle import and export
    // requests.
    private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
   ........................
   Log.d(LOG_TAG, "Executor service status: shutdown: " + mExecutorService.isShutdown()
                        + ", terminated: " + mExecutorService.isTerminated());
........................
    /**
     * Tries to call {@link ExecutorService#execute(Runnable)} toward a given processor.
     * @return true when successful.
     */
   private synchronized boolean tryExecute(ProcessorBase processor) {
        try {
            mExecutorService.execute(processor);
            return true;
        } catch (RejectedExecutionException e) {
            Log.w(LOG_TAG, "Failed to excetute a job.", e);
            return false;
        }
    }
........................
        Log.i(LOG_TAG, "No unfinished job. Stop this service.");
        mExecutorService.shutdown();
........................
        if (mExecutorService.isShutdown()) {
            Log.w(LOG_TAG, "MediaScanner update is requested after executor's being shut down. " +
                    "Ignoring the update request");
            return;
        }        
........................
mExecutorService.shutdown();

Executors.newCachedThreadPool

packages\apps\Dialer\java\com\android\voicemail\impl\fetch\FetchVoicemailReceiver.java

  private void fetchVoicemail(final Network network, final VoicemailStatus.Editor status) {
    Executor executor = Executors.newCachedThreadPool();
    executor.execute(
        new Runnable() {
          @Override
          public void run() {
         ................................................
          }
        });
  }

Executors.newFixedThreadPool

packages\apps\CMFileManager\src\com\cyanogenmod\filemanager\providers\SecureResourceProvider.java

private final ExecutorService mExecutorService = Executors.newFixedThreadPool(1);
...............................
mExecutorService.execute(new Runnable() {
                @Override
                public void run() {
                    ....................
                }
            });

Executors.newScheduledThreadPool

packages\apps\Dialer\java\com\android\dialer\app\voicemail\VoicemailPlaybackPresenter.java

private static ScheduledExecutorService mScheduledExecutorService;
......................
  private static synchronized ScheduledExecutorService getScheduledExecutorServiceInstance() {
    if (mScheduledExecutorService == null) {
      mScheduledExecutorService = Executors.newScheduledThreadPool(NUMBER_OF_THREADS_IN_POOL);
    }
    return mScheduledExecutorService;
  }
...........................
  /** Must be invoked when the parent activity is destroyed. */
  public void onDestroy() {
    // Clear references to avoid leaks from the singleton instance.
    mActivity = null;
    mContext = null;
    if (mScheduledExecutorService != null) {
      mScheduledExecutorService.shutdown();
      mScheduledExecutorService = null;
    }
 }

代碼最後調用跳轉到這個runnable:
\packages\apps\Dialer\java\com\android\dialer\app\voicemail\VoicemailPlaybackLayout.java

 /** Controls the animation of the playback slider. */
  @ThreadSafe
  private final class PositionUpdater implements Runnable {

    /** Update rate for the slider, 30fps. */
    private static final int SLIDER_UPDATE_PERIOD_MILLIS = 1000 / 30;

    private final ScheduledExecutorService mExecutorService;
    @GuardedBy("mLock")
    private ScheduledFuture<?> mScheduledFuture;

.........................
        mScheduledFuture = mExecutorService.scheduleAtFixedRate(
                this, 0, SLIDER_UPDATE_PERIOD_MILLIS, TimeUnit.MILLISECONDS);
...........................
      if (mScheduledFuture != null) {
        mScheduledFuture.cancel(true);
        mScheduledFuture = null;
      }

參考資料

1.Class ThreadPoolExecutor
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html
2.(十七)java多線程之ThreadPoolExecutor
https://segmentfault.com/a/1190000009111732
3.android中的線程池 ThreadPoolExecutor
https://www.jianshu.com/p/86eb8ea62141
4.ThreadPoolExecutor使用示例
https://cwind.iteye.com/blog/2286567

寫在後面的話

爲什麼好久沒有寫博客,今天又開始寫呢.

主要是最近看了一下一個Android面試一天一題,感覺這個作者android技術非常的棒,但是對技術還是這麼熱情.而對比自己,android技術的深度和廣度都有欠缺,雖然平時開發沒有什麼問題,但是面試還是被別人diss.不一定別人就比我厲害,只是自己還是對一些平時有疑問的技術沒有更深入的進一步瞭解.

就比如這個ThreadPoolExecutor 和FutureTask,其實早在幾年前就想了解一下,但是就是沒有進一步的去實踐,那後面就再進一步把以前自己想做的事再做一次吧,去完成自己以前留下的小尾巴吧.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章