Android的幾種多線程方式(AsyncTask,HandlerThread,IntentService,ThreadPool),使用場(chǎng)景以及注意事項(xiàng)

在程序開(kāi)發(fā)的實(shí)踐當(dāng)中,為了讓程序表現(xiàn)得更加流暢,我們肯定會(huì)需要使用到多線程來(lái)提升程序的并發(fā)執(zhí)行性能。但是編寫(xiě)多線程并發(fā)的代碼一直以來(lái)都是一個(gè)相對(duì)棘手的問(wèn)題,所以想要獲得更佳的程序性能,我們非常有必要掌握多線程并發(fā)編程的基礎(chǔ)技能。

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

雖然使用多線程可以提高程序的并發(fā)量,但是我們需要特別注意因?yàn)橐攵嗑€程而可能伴隨而來(lái)的內(nèi)存問(wèn)題。例如:在 Activity 內(nèi)部定義的一個(gè) AsyncTask,它屬于一個(gè)內(nèi)部類(lèi),該類(lèi)本身和外面的 Activity 是有引用關(guān)系的,如果 Activity 要銷(xiāo)毀的時(shí)候,AsyncTask 還仍然在運(yùn)行,這會(huì)導(dǎo)致 Activity 沒(méi)有辦法完全釋放,從而引發(fā)內(nèi)存泄漏。所以說(shuō),多線程是提升程序性能的有效手段之一,但是使用多線程卻需要十分謹(jǐn)慎小心,如果不了解背后的執(zhí)行機(jī)制以及使用的注意事項(xiàng),很可能引起嚴(yán)重的問(wèn)題。

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

1. AsyncTask

使用場(chǎng)景:

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

基本使用

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

  • Params
    在執(zhí)行AsyncTask時(shí)需要傳入的參數(shù),可用于在后臺(tái)任務(wù)中使用。
  • Progress
    后臺(tái)任務(wù)執(zhí)行時(shí),如果需要在界面上顯示當(dāng)前的進(jìn)度,則使用這里指定的泛型作為進(jìn)度單位。
  • Result
    當(dāng)任務(wù)執(zhí)行完畢后,如果需要對(duì)結(jié)果進(jìn)行返回,則使用這里指定的泛型作為返回值類(lèi)型。

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

  1. onPreExecute()
    這個(gè)方法會(huì)在后臺(tái)任務(wù)開(kāi)始執(zhí)行之間調(diào)用,用于進(jìn)行一些界面上的初始化操作,比如顯示一個(gè)進(jìn)度條對(duì)話框等。
  2. doInBackground(Params...)
    這個(gè)方法中的所有代碼都會(huì)在子線程中運(yùn)行,我們應(yīng)該在這里去處理所有的耗時(shí)任務(wù)。任務(wù)一旦完成就可以通過(guò)return語(yǔ)句來(lái)將任務(wù)的執(zhí)行結(jié)果進(jìn)行返回,如果AsyncTask的第三個(gè)泛型參數(shù)指定的是Void,就可以不返回任務(wù)執(zhí)行結(jié)果。注意,在這個(gè)方法中是不可以進(jìn)行UI操作的,如果需要更新UI元素,比如說(shuō)反饋當(dāng)前任務(wù)的執(zhí)行進(jìn)度,可以調(diào)用publishProgress(Progress...)方法來(lái)完成。
  3. onProgressUpdate(Progress...)
    當(dāng)在后臺(tái)任務(wù)中調(diào)用了publishProgress(Progress...)方法后,這個(gè)方法就很快會(huì)被調(diào)用,方法中攜帶的參數(shù)就是在后臺(tái)任務(wù)中傳遞過(guò)來(lái)的。在這個(gè)方法中可以對(duì)UI進(jìn)行操作,利用參數(shù)中的數(shù)值就可以對(duì)界面元素進(jìn)行相應(yīng)的更新。
  4. onPostExecute(Result)
    當(dāng)后臺(tái)任務(wù)執(zhí)行完畢并通過(guò)return語(yǔ)句進(jìn)行返回時(shí),這個(gè)方法就很快會(huì)被調(diào)用。返回的數(shù)據(jù)會(huì)作為參數(shù)傳遞到此方法中,可以利用返回的數(shù)據(jù)來(lái)進(jìn)行一些UI操作,比如說(shuō)提醒任務(wù)執(zhí)行的結(jié)果,以及關(guān)閉掉進(jìn)度條對(duì)話框等。

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

注意事項(xiàng):
  • 首先,默認(rèn)情況下,所有的 AsyncTask 任務(wù)都是被線性調(diào)度執(zhí)行的,他們處在同一個(gè)任務(wù)隊(duì)列當(dāng)中,按順序逐個(gè)執(zhí)行。假設(shè)你按照順序啟動(dòng)20個(gè) AsyncTask,一旦其中的某個(gè) AsyncTask 執(zhí)行時(shí)間過(guò)長(zhǎng),隊(duì)列中的其他剩余 AsyncTask 都處于阻塞狀態(tài),必須等到該任務(wù)執(zhí)行完畢之后才能夠有機(jī)會(huì)執(zhí)行下一個(gè)任務(wù)。為了解決上面提到的線性隊(duì)列等待的問(wèn)題,我們可以使用 AsyncTask.executeOnExecutor()強(qiáng)制指定 AsyncTask 使用線程池并發(fā)調(diào)度任務(wù)。

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

  • 最后,使用 AsyncTask 很容易導(dǎo)致內(nèi)存泄漏,一旦把 AsyncTask 寫(xiě)成 Activity 的內(nèi)部類(lèi)的形式就很容易因?yàn)?AsyncTask 生命周期的不確定而導(dǎo)致 Activity 發(fā)生泄漏。

綜上所述,AsyncTask 雖然提供了一種簡(jiǎn)單便捷的異步機(jī)制,但是我們還是很有必要特別關(guān)注到他的缺點(diǎn),避免出現(xiàn)因?yàn)槭褂缅e(cuò)誤而導(dǎo)致的嚴(yán)重系統(tǒng)性能問(wèn)題。

2. HandlerThread

使用場(chǎng)景:

為某些回調(diào)方法或者等待某些任務(wù)的執(zhí)行設(shè)置一個(gè)專(zhuān)屬的線程,并提供線程任務(wù)的調(diào)度機(jī)制。

大多數(shù)情況下,AsyncTask 都能夠滿足多線程并發(fā)的場(chǎng)景需要(在工作線程執(zhí)行任務(wù)并返回結(jié)果到主線程),但是它并不是萬(wàn)能的。例如打開(kāi)相機(jī)之后的預(yù)覽幀數(shù)據(jù)是通過(guò) onPreviewFrame()的方法進(jìn)行回調(diào)的,onPreviewFrame()和 open()相機(jī)的方法是執(zhí)行在同一個(gè)線程的。如果使用 AsyncTask,會(huì)因?yàn)?AsyncTask 默認(rèn)的線性執(zhí)行的特性(即使換成并發(fā)執(zhí)行)會(huì)導(dǎo)致因?yàn)闊o(wú)法把任務(wù)及時(shí)傳遞給工作線程而導(dǎo)致任務(wù)在主線程中被延遲,直到工作線程空閑,才可以把任務(wù)切換到工作線程中進(jìn)行執(zhí)行。所以我們需要的是一個(gè)執(zhí)行在工作線程,同時(shí)又能夠處理隊(duì)列中的復(fù)雜任務(wù)的功能,而 HandlerThread 的出現(xiàn)就是為了實(shí)現(xiàn)這個(gè)功能的,它組合了 Handler,MessageQueue,Looper 實(shí)現(xiàn)了一個(gè)長(zhǎng)時(shí)間運(yùn)行的線程,不斷的從隊(duì)列中獲取任務(wù)進(jìn)行執(zhí)行的功能。

基本用法:

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

// 創(chuàng)建一個(gè)線程,線程名字 : 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();  

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

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

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

3. IntentSerice

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

使用場(chǎng)景:

適合于執(zhí)行由 UI 觸發(fā)的后臺(tái) Service 任務(wù),并可以把后臺(tái)任務(wù)執(zhí)行的情況通過(guò)一定的機(jī)制反饋給 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í)行耗時(shí)操作
        Message msg1 = new Message();
        msg1.obj ="我是耗時(shí)操作";
        // 調(diào)用回調(diào) (也可以通過(guò)廣播機(jī)制完成)
        if(updateUI != null){
            updateUI.updateUI(msg1);
        }
    }
}

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

注意事項(xiàng):

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

4. ThreadPool

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

使用場(chǎng)景:

把任務(wù)分解成不同的單元,分發(fā)到各個(gè)不同的線程上,進(jìn)行同時(shí)并發(fā)處理。

基本用法:

線程池有四個(gè)構(gòu)造方法,這四個(gè)構(gòu)造方法咋一看,前面三個(gè)都是調(diào)用第四個(gè)構(gòu)造方法實(shí)現(xiàn)的,每一個(gè)構(gòu)造方法都特別復(fù)雜,參數(shù)很多,使用起來(lái)比較麻煩。

下面列出是四種構(gòu)造方法,從簡(jiǎn)單到復(fù)雜:

  • 第一種
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:
    當(dāng)線程數(shù)大于核心時(shí),此為終止前多余的空閑線程等待新任務(wù)的最長(zhǎng)時(shí)間。

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

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

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

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

這里用復(fù)雜的一個(gè)構(gòu)造方法說(shuō)明如何手動(dòng)創(chuàng)建一個(gè)線程池。

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,// 空閑線程超時(shí)
                TimeUnit.SECONDS,// 超時(shí)時(shí)間單位
                // 當(dāng)線程池達(dá)到corePoolSize時(shí),新提交任務(wù)將被放入workQueue中,
                // 等待線程池中任務(wù)調(diào)度執(zhí)行
                new ArrayBlockingQueue<Runnable>(BLOCK_POOL_SIZE),// 阻塞隊(duì)列大小
                // 線程工廠,為線程池提供創(chuàng)建新線程的功能,它是一個(gè)接口,
                // 只有一個(gè)方法:Thread newThread(Runnable r)
                Executors.defaultThreadFactory(),
                // 線程池對(duì)拒絕任務(wù)的處理策略。一般是隊(duì)列已滿或者無(wú)法成功執(zhí)行任務(wù),
                // 這時(shí)ThreadPoolExecutor會(huì)調(diào)用handler的rejectedExecution
                // 方法來(lái)通知調(diào)用者
                new ThreadPoolExecutor.AbortPolicy()
        );
        executor.allowCoreThreadTimeOut(true);
        /*
         * ThreadPoolExecutor默認(rèn)有四個(gè)拒絕策略:
         *
         * 1、ThreadPoolExecutor.AbortPolicy()   直接拋出異常RejectedExecutionException
         * 2、ThreadPoolExecutor.CallerRunsPolicy()    直接調(diào)用run方法并且阻塞執(zhí)行
         * 3、ThreadPoolExecutor.DiscardPolicy()   直接丟棄后來(lái)的任務(wù)
         * 4、ThreadPoolExecutor.DiscardOldestPolicy()  丟棄在隊(duì)列中隊(duì)首的任務(wù)
         */

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

        // 所有任務(wù)執(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()
                + "   所有的任務(wù)數(shù) : " + executor.getTaskCount()
                + "   執(zhí)行任務(wù)的線程數(shù) : " + executor.getActiveCount()
                + "   執(zhí)行完畢的任務(wù)數(shù) : " + executor.getCompletedTaskCount()

        );
    }

    // 模擬線程任務(wù)
    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;
        }
    }

}

結(jié)果:

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

注意事項(xiàng):

  • 使用線程池需要特別注意同時(shí)并發(fā)線程數(shù)量的控制,理論上來(lái)說(shuō),我們可以設(shè)置任意你想要的并發(fā)數(shù)量,但是這樣做非常的不好。因?yàn)?CPU 只能同時(shí)執(zhí)行固定數(shù)量的線程數(shù),一旦同時(shí)并發(fā)的線程數(shù)量超過(guò) CPU 能夠同時(shí)執(zhí)行的閾值,CPU 就需要花費(fèi)精力來(lái)判斷到底哪些線程的優(yōu)先級(jí)比較高,需要在不同的線程之間進(jìn)行調(diào)度切換。一旦同時(shí)并發(fā)的線程數(shù)量達(dá)到一定的量級(jí),這個(gè)時(shí)候 CPU 在不同線程之間進(jìn)行調(diào)度的時(shí)間就可能過(guò)長(zhǎng),反而導(dǎo)致性能?chē)?yán)重下降。
  • 另外需要關(guān)注的一點(diǎn)是,每開(kāi)一個(gè)新的線程,都會(huì)耗費(fèi)至少 64K+ 的內(nèi)存。為了能夠方便的對(duì)線程數(shù)量進(jìn)行控制,ThreadPoolExecutor 為我們提供了初始化的并發(fā)線程數(shù)量,以及最大的并發(fā)數(shù)量進(jìn)行設(shè)置。
  • 另外需要關(guān)注的一個(gè)問(wèn)題是:Runtime.getRuntime().availableProcesser()方法并不可靠,他返回的值并不是真實(shí)的 CPU 核心數(shù),因?yàn)?CPU 會(huì)在某些情況下選擇對(duì)部分核心進(jìn)行睡眠處理,在這種情況下,返回的數(shù)量就只能是激活的 CPU 核心數(shù)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android中的線程 線程,在Android中是非常重要的,主線程處理UI界面,子線程處理耗時(shí)操作。如果在主線程...
    shenhuniurou閱讀 883評(píng)論 0 3
  • 線程是程序員進(jìn)階的一道重要門(mén)檻。對(duì)于移動(dòng)開(kāi)發(fā)者來(lái)說(shuō),“將耗時(shí)的任務(wù)放到子線程去執(zhí)行,以保證UI線程的流暢性”是線程...
    vb12閱讀 1,480評(píng)論 0 2
  • 簡(jiǎn)介 1. 線程分類(lèi) 主線程(UI線程) : 處理和界面相關(guān)的事情. 子線程 : 處理耗時(shí)操作. Android中...
    王世軍Steven閱讀 979評(píng)論 0 2
  • 參考Android線程的正確使用姿勢(shì)Android性能優(yōu)化典范之多線程篇 Android多線程編程的總結(jié)Andro...
    合肥黑閱讀 702評(píng)論 1 2
  • 吃晚飯時(shí),我喂快兩歲的二寶吃飯,小家伙正吃著就站起來(lái)跑,我端著飯,揪他肩上衣服,讓他坐下,沒(méi)想到小家伙倚著我手打了...
    冬陽(yáng)一縷閱讀 463評(píng)論 9 9

友情鏈接更多精彩內(nèi)容