Android 多線程(一)

簡介

1. 線程分類

  • 主線程(UI線程) : 處理和界面相關的事情.
  • 子線程 : 處理耗時操作.

Android中規定, 只有在主線程中可以進行UI操作, 但是同時, 主線程中不能進行耗時操作,否則會產生ANR,因此耗時操作必須放到子線程中進行處理.

2. Android中多線程技術

  • Thread
  • AsyncTask : 底層用到了線程池.

    封裝了線程池和Handler, 主要使用場景是在子線程中更新UI.

  • IntentService : 底層使用Thread

    內部采用HandlerThread來執行任務,當任務執行完成后IntentService就會自動退出. 從任務執行的角度看,IntentService很像是一個后臺線程,其實他是一個Service,正是因為他是服務所以不容易被系統殺死從而保證任務的正常進行.

  • HandlerThread : 底層使用Thread

    具有消息循環的線程, 內部可以使用Handler.

注意一 : 從Android 3.0 開始如果在子線程中進行網絡操作就會產生NetworkOnMainThreadException

AsyncTask

AsyncTask 是一個輕量級異步任務類, AsyncTask 封裝了Thread和Handler, 通過AsyncTask可以更加方便地執行后臺任務以及在主線程中訪問UI, 但是AsyncTask并不適合進行特別耗時的后臺任務,對于特別耗時的后臺任務來說建議使用線程池.

1. AsyncTask 簡單使用

  • 創建自定義任務類,繼承自AsyncTask.
/**
 * 自定義 AsyncTask 類.
 * P1 : 輸入參數類型
 * P2 : 進度類型
 * P3 : 返回值類型
 */
private class DownloadTask extends AsyncTask<Integer, Integer, Integer> {
    /**
     * 1. 主線程
     * 2. 執行任務之前執行, 常用來初始化任務,顯示UI等.
     */
    @Override
    protected void onPreExecute() {
        Log.d(TAG, "onPreExecute: " + Thread.currentThread().getId());
    }
    /**
     * 1. 子線程
     * 2. onPreExecute執行后會立即執行這個方法.任務邏輯就是在這個方法中進行的.
     * 3. 可以通過 publicProgress(Progress...) 將任務進度傳遞到onProgressUpdate.
     * 4. 任務結果通過返回值返回, return.傳遞到onPostExecute.
     * @param params P2
     * @return P3
     */
    @Override
    protected Integer doInBackground(Integer... params) {
        Log.d(TAG, "doInBackground: " + Thread.currentThread().getId());
        for (int i = 0; i < params[0]; i++) {
            // 檢查任務是否被取消
            if (isCancelled()) {
                return -1;
            }
            publishProgress(i);
        }
        return 0;
    }
    /**
     * 1. 主線程
     * 2. publicProgress被調用后會將進度傳遞到這個方法中.可以在這進行UI更新操作.
     * @param values 任務進度.
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        Log.d(TAG, "onProgressUpdate: " + Thread.currentThread().getId());
        Log.d(TAG, "onProgressUpdate: " + values[0]);
    }
    /**
     * 任務執行結束后,就會進入這個回調.
     * @param integer 任務結果
     */
    @Override
    protected void onPostExecute(Integer integer) {
        Log.d(TAG, "onPostExecute: " + Thread.currentThread().getId());
        Log.d(TAG, "onPostExecute: " + integer);
    }
    /**
     * 任務取消后調用
     * @param integer doInBackground 返回值.
     */
    @Override
    protected void onCancelled(Integer integer) {
        super.onCancelled(integer);
    }
}
  • 啟動任務
// 創建并啟動任務
new DownloadTask().execute(5);
  • 輸出
//
D/MainActivity: onPreExecute: 1
//
D/MainActivity: doInBackground: 161
//
D/MainActivity: onProgressUpdate: 1 // 線程id
D/MainActivity: onProgressUpdate: 0 // 進度
D/MainActivity: onProgressUpdate: 1
D/MainActivity: onProgressUpdate: 1 // 進度
D/MainActivity: onProgressUpdate: 1
D/MainActivity: onProgressUpdate: 2 // 進度
D/MainActivity: onProgressUpdate: 1
D/MainActivity: onProgressUpdate: 3 // 進度
D/MainActivity: onProgressUpdate: 1
D/MainActivity: onProgressUpdate: 4 // 進度
//
D/MainActivity: onPostExecute: 1 // 線程id
D/MainActivity: onPostExecute: 0 // 結果

代碼中已經有了詳細的注釋,不進行過多解釋,通過Log也證實了.這里說下取消任務

AsyncTask 提供了 cancel(boolean) 取消任務.

  1. 參數含義:是否立即中斷線程執行,true表示立即終止線程,false表示允許任務完成.
  2. 調用了cancel()onCancelled() 回調會被執行(UI線程), onPostExecute() 不會被執行了.
  3. 調用后 isCancelled 會返回 true.
  4. 建議onInBackground() 中檢查 isCancelled 以便盡快結束任務.

2. AsyncTask使用注意

  • AsyncTask這個類必須在主線程中進行加載. 在Android 4.1 以后系統自動完成這一過程.Android 6.0 以后可以在子線程中加載
  • AsyncTask必須在主線程中創建.
  • execute() 必須在UI線程中進行調用.
  • 不要在直接調用 onPreExecute(), onPostExecute(), onInBackground() ,onProgressUpdate() 等方法.
  • 一個AsyncTask 對象 只能調用一次execute方法,如果執行多個任務就創建多個任務.
  • Android3.0 以后 ,AsyncTask用一個線程串行執行任務.

3. AsyncTask的工作原理

  • 首先分析execute() 方法.由于它是直接調用了executeOnExecutor()因此主要分析一下后者
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    // 調用 : executeOnExecutor 
    // sDefaultExecutor 是一個串行的線程池Executor.
    return executeOnExecutor(sDefaultExecutor, params);
}
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
    // 檢查狀態, 此處可用證明為什么一個AsyncTask對象只可以調用一次execute方法.
    if (mStatus != Status.PENDING) {
        switch (mStatus) {
            case RUNNING:  // 如果任務已經執行,再次執行就會拋出異常.
                throw new IllegalStateException("Cannot execute task:"
                        + " the task is already running.");
            case FINISHED: // 如果任務已經執行完了,再次執行就會拋出異常.
                throw new IllegalStateException("Cannot execute task:"
                        + " the task has already been executed "
                        + "(a task can be executed only once)");
        }
    }

    // 設置為運行狀態.
    mStatus = Status.RUNNING;
    // 首先調用 onPreExecute, 證實了onPreExecute他回調最先執行.
    // onPreExecute在主線程中執行, 這也是AsyncTask的execute()必須在主線程調用的一個的原因.
    onPreExecute();
    // 保存參數
    mWorker.mParams = params;
    // 執行線程
    // mFuture 是一個Runnable.
    exec.execute(mFuture);

    return this;
}

代碼中已經有了詳解的注釋這里就不過多的解釋了.

  1. @MainThread 注解說明這兩個方法都必須在主線程中執行,因此execute()方法必須在主線程中調用.
  2. if (mStatus != Status.PENDING){...}中的判斷說明了,一個AsyncTask對象只可以調用一次execute() 方法.
  3. sDefaultExecutor是一個串行的線程池主要用于任務排隊,并不負責任務的實際執行,源碼分析如下
// sDefaultExecutor 定義
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
// SERIAL_EXECUTOR : 用于任務排隊.
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
// SerialExecutor 串行線程池
private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;
    // 執行Runnable
    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    r.run();
                } finally {
                    // 調度下一個.
                    scheduleNext();
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }
    // 啟動下一個Runnable
    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            // 執行任務.
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}
// mFuture是一個Runnable
private final FutureTask<Result> mFuture;
  1. THREAD_POOL_EXECUTOR 也是一個線程池,負責任務的執行.
// 定義
public static final Executor THREAD_POOL_EXECUTOR;
// 靜態加載線程池.
static {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
            sPoolWorkQueue, sThreadFactory);
    threadPoolExecutor.allowCoreThreadTimeOut(true);
    THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
  1. sHandler 負責線程環境的切換.
// 7.0 代碼
// 負責線程切換
private static InternalHandler sHandler;
// 獲取Handler
private static Handler getHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler();
        }
        return sHandler;
    }
}
// 該類負責實現線程的切換
private static class InternalHandler extends Handler {
    // 7.0 
    public InternalHandler() {
        super(Looper.getMainLooper());
    }
    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_RESULT: // 任務執行結束
                // There is only one result
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS: // 更新進度.
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}
// 任務結束函數
private void finish(Result result) {
    if (isCancelled()) {
        // 任務被取消
        onCancelled(result);
    } else {
        // 任務正常完成
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

Android 5.0 代碼

// 靜態加載.
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult result = (AsyncTaskResult) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_RESULT:
                // There is only one result
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}
  1. sHandler 是一個靜態的Handler對象,Android 6.0 以前,為了能夠將執行環境切換到主線程,這就要求sHandler這個對象必須在主線程中進行創建.
  2. Android 6.0 以后 sHandler 使用了懶加載,因此,在子線程中也可以進行加載AsyncTask.同時在ActivityThread的main() 方法中也去掉了AsyncTask.init(); 也證實了這一點.

5 總結

  • AsyncTask 本質上就是封裝了線程池和Handler
  • Android 6.0 以后可以在子線程中加載AsyncTask.

HandlerThread

HandlerThread 繼承了Thread ,添加加了Looper, 實現方式如下:

  • 通過Looper.prepare() 來創建消息隊列.
  • 通過Looper.loop() 來開啟消息循環.
@Override
public void run() {
    mTid = Process.myTid();
    // 創建消息隊列.
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    // 子類實現.
    onLooperPrepared();
    // 開啟運行循環.
    Looper.loop();
    mTid = -1;
}
  • 普通的Thread主要用于進行耗時操作.HandlerThread內部常見了消息隊列,外界需要通過Handler的消息方式通知HandlerThread來執行一個任務.由于HandlerThread的run() 是一個無限循環(Looper.loop()),因此當明確不需要時需要主動調用,quit() 或者 quitSafely() 來終止線程的執行.

關于HandlerThread的使用方式參考IntentService源碼

IntentService

1. 簡介

  1. IntentService是一種特殊的服務, 他繼承自Service并且他是一個抽象類.
  2. IntentService用于后臺耗時任務, 任務執行結束后自動停止.
  3. 由于它是一個服務因此有著比普通Thread更高的優先級,不容易被系統殺死.因此他適合執行一些優先級較高的后臺任務.

2. 工作原理

  • IntentService 其實就是封裝了 HandlerHandlerThread從下面的源碼分析中可以看出來.
// 1. Handler
private volatile ServiceHandler mServiceHandler;
// 定義Handler
private final class ServiceHandler extends Handler {
    public ServiceHandler(Looper looper) {
        super(looper);
    }
    @Override
    public void handleMessage(Message msg) {
        // onHandleIntent 這個方法是子類來實現的.
        onHandleIntent((Intent)msg.obj);
        // 停止當前服務.
        stopSelf(msg.arg1);
    }
}
// IntentService 的onCreate方法
@Override
public void onCreate() {
    // TODO: It would be nice to have an option to hold a partial wakelock
    // during processing, and to have a static startService(Context, Intent)
    // method that would launch the service & hand off a wakelock.
    super.onCreate();
    // 創建并啟動了HandlerThread.
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();
    // 保存Looper
    mServiceLooper = thread.getLooper();
    // 創建Handler , 并且將創建的Handler 和 HandlerThread 的Looper綁定在一起.
    mServiceHandler = new ServiceHandler(mServiceLooper);
}
  1. mServiceHandler就是一個Handler , 并且他的handleMessage處理邏輯也十分簡單,首先調用onHandleIntent將結果返給子類處理. 然后調用stopSelf() 來停止服務.
  2. 在onCreate() 方法中創建了一個HandlerThread 并且將Handler和他綁定.

IntentService工作原理如下:

  1. 在onCreate() 中創建Handler和HandlerThread.并綁定.
  2. onStart() 中通過Handler 發送消息.
  3. 在 Handler中處理調用onHandleIntent()進行耗時操作.
  4. 數據通過Intent進行傳輸.
  5. 任務執行結束后,后停止服務.
  6. onDestory() 中退出HandlerThread.
工作原理

3. 使用

// 定義
public class LocalIntentService extends IntentService{
    /**
     * 構造方法
     */
    public LocalIntentService() {
        super("123");
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        String action = intent.getStringExtra("task_action");
        Log.d(TAG, "onHandleIntent: " + action);
        // 延時
        SystemClock.sleep(500);
    }
    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy: 服務結束");
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }
}
Intent intent = new Intent(this, LocalIntentService.class);
intent.putExtra("task_action", "任務 1");
startService(intent);
// 任務二
intent.putExtra("task_action", "任務 2");
startService(intent);
SystemClock.sleep(2000);
// 任務三
intent.putExtra("task_action", "任務 3");
startService(intent);

運行代碼發現上述三個任務串行執行,最后會停止服務.

Android中的線程池

Android線程池的概念源自于Java的Executor, Executor 是一個接口,ThreadPoolExecutor 是他的實現類.ThreadPoolExecutor提過了一系列的參數來配置線程池.
Android中的線程池主要分為四類.后面會詳細介紹.

1. 線程池的好處

  • 重用線程池中的線程, 避免線程的創建和銷毀帶來的性能開銷.
  • 能有效的控制線程池的最大并發數, 避免大量線程之間因為搶奪子系統資源而導致的阻塞現象.
  • 能夠對象成進行簡單的管理, 并提供定時執行以及定時間隔循環執行的功能.

2. ThreadPoolExecutor

  • 構造方法
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory);
  1. corePoolSize : 線程池的核心線程數,默認情況下核心線程會在線程池中一直存活.如果ThreadPoolExecutor 的 allowCoreThreadTimeOut 屬性設置為true, 那么閑置的核心線程會有超時策略,超時時間由keepAliveTime指定.
  2. maximumPoolSize : 線程池最大的線程數.達到最大值后新任務會阻塞.
  3. keepAliveTime : 超時時間.
  4. unit : 超時單位,MILLISECONDS(毫秒),SECONDS(秒), MINUTES(分鐘)
  5. workQueue: 線程池中的任務隊列,通過線程池的execute方法提交的Runnable對象會存儲在這里.
  6. threadFactory : 線程工廠, 為線程池提供創建新線程的功能.

除了上面的主要參數以外, ThreadPoolExecuteor還有一個不常用的參數RejectedExecutionHandler handler. 當線程池無法執行新的任務時會調用handler 的rejectedExeception方法來通知調用者, 默認情況下 rejectedExecution 方法會拋出一個rejectedExecutionException異常,具體的可以查看API文檔.

  • ThreadPoolExecutor 執行時遵循以下規則:

    • 如果線程池中的線程為達到核心線程的數量, 那么會直接啟動一個核心線程來執行任務.
    • 如果線程池中的線程數量已經達到或者超過核心線程數量, 那么任務會插入任務隊列中進行排隊.
    • 如果過步驟2中無法將任務插入到任務隊列,一般是隊列已滿,這個時候如果線程數量沒有達到線程池的最大數量限制,那么會立即啟動一個非核心線程來執行任務.
    • 如果步驟3中線程數量已經到了線程池規定的最大值, 那么就拒絕執行此任務,ThreadPoolExecutor會調用RejectedExecutionHandler的rejectedExecution() 方法來通知調用者.
  • AsyncTask中的線程池配置

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
static {
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
            sPoolWorkQueue, sThreadFactory);
    threadPoolExecutor.allowCoreThreadTimeOut(true);
    THREAD_POOL_EXECUTOR = threadPoolExecutor;
}

3. Android中的四種線程池.

Android中常用有四種線程池,他們都是直接或者間接配置ThreadPoolExecutor 來實現自己的功能的.

3.1 FixedThreadPool

  • 通過Executors 的newFixedThreadPool 方法來創建.
  • 線程數量固定.
  • 線程處于空閑狀態時, 線程也不會被回收,除非線程池關閉了.正是這個原因所以它能夠更加快速第響應外界請求.
  • 當所有的線程都處于活動狀態時, 新任務處于等待狀態, 直到有線程空閑.
  • 沒有超時機制
  • 沒有任務熟練限制.

newFixedThreadPool方法源碼

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
}

3.2 CachedThreadPool

  • 通過Executors的newCachedThreadPool 來創建, 它是一種線程數量不定的線程池,他只有非核心線程并且最大線程數為integer.MAX_VALUE.
  • 當線程池中的活動都處于活動狀態時,會直接創建新的線程來處理任務.否則就利用空閑線程來處理任務.
  • 超時時間是 60s ,線程閑置60s就會被回收.
  • 這種線程適合大量的耗時較小的任務.

newCachedThreadPool源碼

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

3.3 ScheduledThreadPool

  • 通過Executors的newScheduledThreadPool方法來創建.
  • 核心線程數是固定的,非核心線程數沒有限制.當非核心線程一旦閑置就會被立即回收.
  • ScheduledThreadPool主要用來執行定時任務和具有固定周期的重復任務.

newScheduledThreadPool源碼

public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

3.4 SingleThreadPool

  • 通過Executors的newSingleThreadExecutor方法創建.
  • 只有一個核心線程,他可以確保所有的任務都在同一個線程中順序執行.
  • 他可以統一所有的外界任務到一個線程中,這樣可以避免線程同步問題.

newSingleThreadExecutor源碼

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

4. 四種常用線程池的用法

private void threadPoolTest() {
    // 創建任務
    Runnable command = new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "run: " + Thread.currentThread().getId());
            SystemClock.sleep(2000);
        }
    };
    // FixedThreadPool
    ExecutorService fixed = Executors.newFixedThreadPool(4);
    fixed.execute(command);
    // Cached
    ExecutorService cached = Executors.newCachedThreadPool();
    cached.execute(command);
    // Scheduled
    ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(4);
    // 2000 ms 后執行
    scheduled.schedule(command, 2000, TimeUnit.MILLISECONDS);
    // 延時 10 后每隔 1000ms 執行一次
    scheduled.scheduleAtFixedRate(command, 10, 1000, TimeUnit.MICROSECONDS);
    // Single
    ExecutorService single = Executors.newSingleThreadExecutor();
    single.execute(command);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容

  • Android中的線程 線程,在Android中是非常重要的,主線程處理UI界面,子線程處理耗時操作。如果在主線程...
    shenhuniurou閱讀 765評論 0 3
  • 當某個應用組件啟動且該應用沒有運行其他任何組件時,Android 系統會使用單個執行線程為應用啟動新的 Linux...
    小蕓論閱讀 1,731評論 0 12
  • 從用途上來說,線程分為主線程和子線程,主線程主要處理和界面相關的事情,子線程則往往用于執行耗時操作。 除了Thre...
    小柏不是大白閱讀 637評論 0 3
  • 花在昨夜秋雨中凋零 只剩下一段傷心的枝條 在陰冷的晨風中顫抖 已無法承受現實的殘酷 風偶爾吹起記憶的落葉 還沒有思...
    風過長河閱讀 213評論 0 2
  • 就是想用簡書記錄生活的點點滴滴,記錄曾經走過的每一個角落! 愛生活,愛自由
    IAM四十二閱讀 297評論 0 2