Android的幾種多線程方式(AsyncTask,HandlerThread,IntentService,ThreadPool)涌韩,使用場景以及注意事項

在程序開發(fā)的實踐當中熏版,為了讓程序表現(xiàn)得更加流暢纷责,我們肯定會需要使用到多線程來提升程序的并發(fā)執(zhí)行性能。但是編寫多線程并發(fā)的代碼一直以來都是一個相對棘手的問題撼短,所以想要獲得更佳的程序性能再膳,我們非常有必要掌握多線程并發(fā)編程的基礎技能。

引入多線程而可能伴隨而來的內(nèi)存問題

雖然使用多線程可以提高程序的并發(fā)量曲横,但是我們需要特別注意因為引入多線程而可能伴隨而來的內(nèi)存問題喂柒。例如:在 Activity 內(nèi)部定義的一個 AsyncTask,它屬于一個內(nèi)部類禾嫉,該類本身和外面的 Activity 是有引用關系的灾杰,如果 Activity 要銷毀的時候,AsyncTask 還仍然在運行夭织,這會導致 Activity 沒有辦法完全釋放吭露,從而引發(fā)內(nèi)存泄漏吠撮。所以說尊惰,多線程是提升程序性能的有效手段之一讲竿,但是使用多線程卻需要十分謹慎小心,如果不了解背后的執(zhí)行機制以及使用的注意事項弄屡,很可能引起嚴重的問題题禀。

下面我們來一個一個分析下系統(tǒng)為我們提供的幾種多線程方式,包括:AsyncTask,HandlerThread,IntentService,ThreadPool

1. AsyncTask

使用場景:

為 UI 線程與工作線程之間進行快速的切換提供一種簡單便捷的機制膀捷。適用于當下立即需要啟動迈嘹,但是異步執(zhí)行的生命周期短暫的使用場景。

基本使用

AsyncTask是一個抽象方法全庸,如果想使用它秀仲,需要先創(chuàng)建一個子類來繼承他,還需要為其指定三個泛型參數(shù):

  • Params
    在執(zhí)行AsyncTask時需要傳入的參數(shù)壶笼,可用于在后臺任務中使用神僵。
  • Progress
    后臺任務執(zhí)行時,如果需要在界面上顯示當前的進度覆劈,則使用這里指定的泛型作為進度單位保礼。
  • Result
    當任務執(zhí)行完畢后,如果需要對結果進行返回责语,則使用這里指定的泛型作為返回值類型炮障。

經(jīng)常需要去重寫的方法有以下四個:

  1. onPreExecute()
    這個方法會在后臺任務開始執(zhí)行之間調(diào)用,用于進行一些界面上的初始化操作坤候,比如顯示一個進度條對話框等胁赢。
  2. doInBackground(Params...)
    這個方法中的所有代碼都會在子線程中運行,我們應該在這里去處理所有的耗時任務白筹。任務一旦完成就可以通過return語句來將任務的執(zhí)行結果進行返回徘键,如果AsyncTask的第三個泛型參數(shù)指定的是Void,就可以不返回任務執(zhí)行結果遍蟋。注意吹害,在這個方法中是不可以進行UI操作的,如果需要更新UI元素虚青,比如說反饋當前任務的執(zhí)行進度它呀,可以調(diào)用publishProgress(Progress...)方法來完成。
  3. onProgressUpdate(Progress...)
    當在后臺任務中調(diào)用了publishProgress(Progress...)方法后棒厘,這個方法就很快會被調(diào)用纵穿,方法中攜帶的參數(shù)就是在后臺任務中傳遞過來的。在這個方法中可以對UI進行操作奢人,利用參數(shù)中的數(shù)值就可以對界面元素進行相應的更新谓媒。
  4. onPostExecute(Result)
    當后臺任務執(zhí)行完畢并通過return語句進行返回時,這個方法就很快會被調(diào)用何乎。返回的數(shù)據(jù)會作為參數(shù)傳遞到此方法中句惯,可以利用返回的數(shù)據(jù)來進行一些UI操作土辩,比如說提醒任務執(zhí)行的結果,以及關閉掉進度條對話框等抢野。

最后在需要的地方調(diào)用方法 new MyAsyncTask().execute(); 就可以運行了拷淘。

注意事項:
  • 首先,默認情況下指孤,所有的 AsyncTask 任務都是被線性調(diào)度執(zhí)行的启涯,他們處在同一個任務隊列當中,按順序逐個執(zhí)行恃轩。假設你按照順序啟動20個 AsyncTask结洼,一旦其中的某個 AsyncTask 執(zhí)行時間過長,隊列中的其他剩余 AsyncTask 都處于阻塞狀態(tài)叉跛,必須等到該任務執(zhí)行完畢之后才能夠有機會執(zhí)行下一個任務补君。為了解決上面提到的線性隊列等待的問題,我們可以使用 AsyncTask.executeOnExecutor()強制指定 AsyncTask 使用線程池并發(fā)調(diào)度任務昧互。

  • 其次挽铁,如何才能夠真正的取消一個 AsyncTask 的執(zhí)行呢?我們知道 AsyncTaks 有提供 cancel()的方法敞掘,但是這個方法實際上做了什么事情呢叽掘?線程本身并不具備中止正在執(zhí)行的代碼的能力,為了能夠讓一個線程更早的被銷毀玖雁,我們需要在 doInBackground()的代碼中不斷的添加程序是否被中止的判斷邏輯更扁。一旦任務被成功中止,AsyncTask 就不會繼續(xù)調(diào)用 onPostExecute()赫冬,而是通過調(diào)用 onCancelled()的回調(diào)方法反饋任務執(zhí)行取消的結果浓镜。我們可以根據(jù)任務回調(diào)到哪個方法(是 onPostExecute 還是 onCancelled)來決定是對 UI 進行正常的更新還是把對應的任務所占用的內(nèi)存進行銷毀等。

  • 最后劲厌,使用 AsyncTask 很容易導致內(nèi)存泄漏膛薛,一旦把 AsyncTask 寫成 Activity 的內(nèi)部類的形式就很容易因為 AsyncTask 生命周期的不確定而導致 Activity 發(fā)生泄漏。

綜上所述补鼻,AsyncTask 雖然提供了一種簡單便捷的異步機制哄啄,但是我們還是很有必要特別關注到他的缺點,避免出現(xiàn)因為使用錯誤而導致的嚴重系統(tǒng)性能問題风范。

2. HandlerThread

使用場景:

為某些回調(diào)方法或者等待某些任務的執(zhí)行設置一個專屬的線程咨跌,并提供線程任務的調(diào)度機制。

大多數(shù)情況下硼婿,AsyncTask 都能夠滿足多線程并發(fā)的場景需要(在工作線程執(zhí)行任務并返回結果到主線程)锌半,但是它并不是萬能的。例如打開相機之后的預覽幀數(shù)據(jù)是通過 onPreviewFrame()的方法進行回調(diào)的寇漫,onPreviewFrame()和 open()相機的方法是執(zhí)行在同一個線程的刊殉。如果使用 AsyncTask殉摔,會因為 AsyncTask 默認的線性執(zhí)行的特性(即使換成并發(fā)執(zhí)行)會導致因為無法把任務及時傳遞給工作線程而導致任務在主線程中被延遲,直到工作線程空閑冗澈,才可以把任務切換到工作線程中進行執(zhí)行钦勘。所以我們需要的是一個執(zhí)行在工作線程陋葡,同時又能夠處理隊列中的復雜任務的功能亚亲,而 HandlerThread 的出現(xiàn)就是為了實現(xiàn)這個功能的,它組合了 Handler腐缤,MessageQueue捌归,Looper 實現(xiàn)了一個長時間運行的線程,不斷的從隊列中獲取任務進行執(zhí)行的功能岭粤。

基本用法:

HandlerThread 繼承于 Thread,它本質(zhì)上是一個線程惜索,只不過是 Android 為我們封裝好了 Looper 和 MessageQueue的線程,簡化了操作剃浇。使用方法很簡單:

// 創(chuàng)建一個線程巾兆,線程名字 : handlerThreadTest
        mHandlerThread = new HandlerThread("handlerThreadTest");
        mHandlerThread.start();
        
        // Handler 接收消息
        final Handler mHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                Log.e("Test", "收到 " + msg.obj.toString() + " 在 "
                        + Thread.currentThread().getName());
            }
        };
        mTextView = (TextView) findViewById(R.id.text_view);
        // 主線程發(fā)出消息
        mTextView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message msg = new Message();
                msg.obj = "第一條信息";
                mHandler.sendMessage(msg);
                Log.e("Test", "發(fā)出 " + msg.obj.toString() + " 在 "
                        + Thread.currentThread().getName());
            }
        });
        // 子線程發(fā)出消息
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.obj = "第二條信息";
                mHandler.sendMessage(msg);
                Log.e("Test", "發(fā)出 " + msg.obj.toString() + " 在 "
                        + Thread.currentThread().getName());
            }
        }).start();  

最后在不需要的時候記得調(diào)用quit();

@Override
    protected void onDestroy() {
        super.onDestroy();
        //停止消息循環(huán)
        mHandlerThread.quit();
    }
注意事項:

HandlerThread 比較合適處理那些在工作線程執(zhí)行虎囚,需要花費時間偏長的任務角塑。我們只需要把任務發(fā)送給 HandlerThread,然后就只需要等待任務執(zhí)行結束的時候通知返回到主線程就好了淘讥。
另外很重要的一點是圃伶,一旦我們使用了 HandlerThread,需要特別注意給 HandlerThread 設置不同的線程優(yōu)先級蒲列,CPU 會根據(jù)設置的不同線程優(yōu)先級對所有的線程進行調(diào)度優(yōu)化窒朋。

3. IntentSerice

默認的 Service 是執(zhí)行在主線程的,可是通常情況下蝗岖,這很容易影響到程序的繪制性能(搶占了主線程的資源)侥猩。除了前面介紹過的 AsyncTask 與 HandlerThread,我們還可以選擇使用 IntentService 來實現(xiàn)異步操作抵赢。IntentService 繼承自普通 Service 同時又在內(nèi)部創(chuàng)建了一個 HandlerThread拭宁,在 onHandlerIntent()的回調(diào)里面處理扔到 IntentService 的任務,在執(zhí)行完任務后會自動停止瓣俯。所以 IntentService 就不僅僅具備了異步線程的特性杰标,還同時保留了 Service 不受主頁面生命周期影響,優(yōu)先級比較高彩匕,適合執(zhí)行高優(yōu)先級的后臺任務,不容易被殺死的特點腔剂。

使用場景:

適合于執(zhí)行由 UI 觸發(fā)的后臺 Service 任務,并可以把后臺任務執(zhí)行的情況通過一定的機制反饋給 UI驼仪。

基本用法:
public class MyIntentService extends IntentService {

    public static UpdateUI updateUI;
    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public MyIntentService(String name) {
        super(name);
    }

    public interface UpdateUI{
        void updateUI(Message message);
    }

    public static void setUpdateUI(UpdateUI updateUIInterface){
        updateUI = updateUIInterface;
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        // 執(zhí)行耗時操作
        Message msg1 = new Message();
        msg1.obj ="我是耗時操作";
        // 調(diào)用回調(diào) (也可以通過廣播機制完成)
        if(updateUI != null){
            updateUI.updateUI(msg1);
        }
    }
}

最后 Activity 通過 Handler 獲取數(shù)據(jù)并刷新UI掸犬。

注意事項:

使用 IntentService 需要特別留意以下幾點:
首先袜漩,因為 IntentService 內(nèi)置的是 HandlerThread 作為異步線程,所以每一個交給 IntentService 的任務都將以隊列的方式逐個被執(zhí)行到湾碎,一旦隊列中有某個任務執(zhí)行時間過長宙攻,那么就會導致后續(xù)的任務都會被延遲處理。
其次介褥,通常使用到 IntentService 的時候座掘,我們會結合使用 BroadcastReceiver 把工作線程的任務執(zhí)行結果返回給主 UI 線程。使用廣播容易引起性能問題柔滔,我們可以使用 LocalBroadcastManager 來發(fā)送只在程序內(nèi)部傳遞的廣播溢陪,從而提升廣播的性能。我們也可以使用 runOnUiThread() 快速回調(diào)到主 UI 線程睛廊。
最后形真,包含正在運行的 IntentService 的程序相比起純粹的后臺程序更不容易被系統(tǒng)殺死,該程序的優(yōu)先級是介于前臺程序與純后臺程序之間的超全。

4. ThreadPool

系統(tǒng)為我們提供了 ThreadPoolExecutor 來實現(xiàn)多線程并發(fā)執(zhí)行任務咆霜。

使用場景:

把任務分解成不同的單元,分發(fā)到各個不同的線程上嘶朱,進行同時并發(fā)處理蛾坯。

基本用法:

線程池有四個構造方法,這四個構造方法咋一看见咒,前面三個都是調(diào)用第四個構造方法實現(xiàn)的偿衰,每一個構造方法都特別復雜,參數(shù)很多改览,使用起來比較麻煩下翎。

下面列出是四種構造方法,從簡單到復雜:

  • 第一種
ThreadPoolExecutor(
int corePoolSize, 
int maximumPoolSize, 
long keepAliveTime, 
TimeUnit unit, 
BlockingQueue<Runnable> workQueue
);
  • 第二種
ThreadPoolExecutor(
int corePoolSize, 
int maximumPoolSize, 
long keepAliveTime, 
TimeUnit unit, 
BlockingQueue<Runnable> workQueue, 
RejectedExecutionHandler handler
);
  • 第三種
ThreadPoolExecutor(
int corePoolSize, 
int maximumPoolSize, 
long keepAliveTime, 
TimeUnit unit, 
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory
);
  • 第四種
ThreadPoolExecutor(
int corePoolSize, 
int maximumPoolSize, 
long keepAliveTime,
TimeUnit unit, 
BlockingQueue<Runnable> workQueue, 
ThreadFactory threadFactory, 
RejectedExecutionHandler handler
);
參數(shù)作用:
  • corePoolSize:
    池中所保存的線程數(shù)宝当,包括空閑線程视事。

  • maximumPoolSize:
    池中允許的最大線程數(shù)。

  • keepAliveTime:
    當線程數(shù)大于核心時庆揩,此為終止前多余的空閑線程等待新任務的最長時間俐东。

  • unit:
    keepAliveTime參數(shù)的時間單位

  • workQueue:
    執(zhí)行前用于保持任務的隊列。此隊列僅保持由 execute 方法提交的 Runnable 任務订晌。

  • threadFactory
    執(zhí)行程序創(chuàng)建新線程時使用的工廠虏辫。

  • **handler **
    由于超出線程范圍和隊列容量而使執(zhí)行被阻塞時所使用的處理程序。

這里用復雜的一個構造方法說明如何手動創(chuàng)建一個線程池锈拨。

package com.example.thread.threadpool;

import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @auther MaxLiu
 * @time 2017/2/23
 */

public class ThreadPoolTest {
    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 10;
    private static final int BLOCK_POOL_SIZE = 2;
    private static final int ALIVE_POOL_SIZE = 2;
    private static ThreadPoolExecutor executor;

    public static void main(String args[]) {
        executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,// 核心線程數(shù) 最小
                MAX_POOL_SIZE,// 最大執(zhí)行線程數(shù)
                ALIVE_POOL_SIZE,// 空閑線程超時
                TimeUnit.SECONDS,// 超時時間單位
                // 當線程池達到corePoolSize時砌庄,新提交任務將被放入workQueue中,
                // 等待線程池中任務調(diào)度執(zhí)行
                new ArrayBlockingQueue<Runnable>(BLOCK_POOL_SIZE),// 阻塞隊列大小
                // 線程工廠,為線程池提供創(chuàng)建新線程的功能娄昆,它是一個接口佩微,
                // 只有一個方法:Thread newThread(Runnable r)
                Executors.defaultThreadFactory(),
                // 線程池對拒絕任務的處理策略。一般是隊列已滿或者無法成功執(zhí)行任務萌焰,
                // 這時ThreadPoolExecutor會調(diào)用handler的rejectedExecution
                // 方法來通知調(diào)用者
                new ThreadPoolExecutor.AbortPolicy()
        );
        executor.allowCoreThreadTimeOut(true);
        /*
         * ThreadPoolExecutor默認有四個拒絕策略:
         *
         * 1哺眯、ThreadPoolExecutor.AbortPolicy()   直接拋出異常RejectedExecutionException
         * 2、ThreadPoolExecutor.CallerRunsPolicy()    直接調(diào)用run方法并且阻塞執(zhí)行
         * 3扒俯、ThreadPoolExecutor.DiscardPolicy()   直接丟棄后來的任務
         * 4奶卓、ThreadPoolExecutor.DiscardOldestPolicy()  丟棄在隊列中隊首的任務
         */

        for (int i = 0; i < 10; i++) {
            try {
                executor.execute(new WorkerThread("線程 --> " + i));
                LOG();
            } catch (Exception e) {
                System.out.println("AbortPolicy...");
            }
        }
        executor.shutdown();

        // 所有任務執(zhí)行完畢后再次打印日志
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println("\n\n---------執(zhí)行完畢---------\n");
                    LOG();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    /**
     * 打印 Log 信息
     */
    private static void LOG() {
        System.out.println(" ==============線程池===============\n"
                + "   線程池中線程數(shù) : " + executor.getPoolSize()
                + "   等待執(zhí)行線程數(shù) : " + executor.getQueue().size()
                + "   所有的任務數(shù) : " + executor.getTaskCount()
                + "   執(zhí)行任務的線程數(shù) : " + executor.getActiveCount()
                + "   執(zhí)行完畢的任務數(shù) : " + executor.getCompletedTaskCount()

        );
    }

    // 模擬線程任務
    public static class WorkerThread implements Runnable {
        private String threadName;

        public WorkerThread(String threadName) {
            this.threadName = threadName;
        }

        @Override
        public synchronized void run() {

            int i = 0;
            boolean flag = true;
            try {
                while (flag) {
                    i++;
                    if (i > 2) flag = false;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public String getThreadName() {
            return threadName;
        }
    }

}

結果:

線程池執(zhí)行過程
線程池執(zhí)行完畢

注意事項:

  • 使用線程池需要特別注意同時并發(fā)線程數(shù)量的控制,理論上來說陵珍,我們可以設置任意你想要的并發(fā)數(shù)量寝杖,但是這樣做非常的不好违施。因為 CPU 只能同時執(zhí)行固定數(shù)量的線程數(shù)互纯,一旦同時并發(fā)的線程數(shù)量超過 CPU 能夠同時執(zhí)行的閾值,CPU 就需要花費精力來判斷到底哪些線程的優(yōu)先級比較高磕蒲,需要在不同的線程之間進行調(diào)度切換留潦。一旦同時并發(fā)的線程數(shù)量達到一定的量級,這個時候 CPU 在不同線程之間進行調(diào)度的時間就可能過長辣往,反而導致性能嚴重下降兔院。
  • 另外需要關注的一點是,每開一個新的線程站削,都會耗費至少 64K+ 的內(nèi)存坊萝。為了能夠方便的對線程數(shù)量進行控制,ThreadPoolExecutor 為我們提供了初始化的并發(fā)線程數(shù)量许起,以及最大的并發(fā)數(shù)量進行設置十偶。
  • 另外需要關注的一個問題是:Runtime.getRuntime().availableProcesser()方法并不可靠,他返回的值并不是真實的 CPU 核心數(shù)园细,因為 CPU 會在某些情況下選擇對部分核心進行睡眠處理惦积,在這種情況下,返回的數(shù)量就只能是激活的 CPU 核心數(shù)猛频。
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狮崩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鹿寻,更是在濱河造成了極大的恐慌睦柴,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毡熏,死亡現(xiàn)場離奇詭異坦敌,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門恬试,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窝趣,“玉大人,你說我怎么就攤上這事训柴⊙剖妫” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵幻馁,是天一觀的道長洗鸵。 經(jīng)常有香客問我,道長仗嗦,這世上最難降的妖魔是什么膘滨? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮稀拐,結果婚禮上火邓,老公的妹妹穿的比我還像新娘。我一直安慰自己德撬,他們只是感情好铲咨,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蜓洪,像睡著了一般纤勒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隆檀,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天摇天,我揣著相機與錄音,去河邊找鬼恐仑。 笑死泉坐,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的菊霜。 我是一名探鬼主播坚冀,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鉴逞!你這毒婦竟也來了记某?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤构捡,失蹤者是張志新(化名)和其女友劉穎液南,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勾徽,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡嘱根,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了捍歪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡咒钟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出若未,到底是詐尸還是另有隱情朱嘴,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布粗合,位于F島的核電站萍嬉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏隙疚。R本人自食惡果不足惜壤追,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望供屉。 院中可真熱鬧行冰,春花似錦、人聲如沸贯卦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撵割。三九已至,卻和暖如春辙芍,著一層夾襖步出監(jiān)牢的瞬間啡彬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工故硅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留庶灿,地道東北人。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓吃衅,卻偏偏與公主長得像往踢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子徘层,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android中的線程 線程峻呕,在Android中是非常重要的,主線程處理UI界面趣效,子線程處理耗時操作瘦癌。如果在主線程...
    shenhuniurou閱讀 757評論 0 3
  • 線程是程序員進階的一道重要門檻。對于移動開發(fā)者來說跷敬,“將耗時的任務放到子線程去執(zhí)行讯私,以保證UI線程的流暢性”是線程...
    vb12閱讀 1,441評論 0 2
  • 簡介 1. 線程分類 主線程(UI線程) : 處理和界面相關的事情. 子線程 : 處理耗時操作. Android中...
    王世軍Steven閱讀 917評論 0 2
  • 參考Android線程的正確使用姿勢Android性能優(yōu)化典范之多線程篇 Android多線程編程的總結Andro...
    合肥黑閱讀 571評論 1 2
  • 吃晚飯時,我喂快兩歲的二寶吃飯娘锁,小家伙正吃著就站起來跑鬼廓,我端著飯,揪他肩上衣服致盟,讓他坐下碎税,沒想到小家伙倚著我手打了...
    冬陽一縷閱讀 373評論 9 9