主頁 > 移動端開發 > Android中的執行緒(一)

Android中的執行緒(一)

2021-06-10 07:58:55 移動端開發

本文主要是對Android當中的執行緒相關的知識進行復習和總結,

文章目錄

      • new Thread
      • AsyncTask
      • HandlerThread
      • IntentService
      • JobIntentService
      • JobScheduler
      • WorkManager
      • 執行緒中斷
      • 守護執行緒
      • 執行緒優先級
      • 執行緒狀態
      • 執行緒池
      • 執行緒安全
      • 執行緒通信
      • kotlin協程

new Thread

缺乏統一管理,無限制創建,可能占用過多系統資源導致死機或oom,不推薦,

AsyncTask

場景:需要知曉任務執行的進度,多個任務串行執行
缺點:生命周期和宿主的生命周期不同步,有可能發生記憶體泄漏,默認情況所有任務串行執行

class MyAsyncTask extends AsyncTask<String, String, String> {
    private static final String TAG = "MyAsyncTask";

    @Override
    protected String doInBackground(String... params) {
        for (int i = 0; i < 100; i++) {
            publishProgress(params[0] + " === "+ (i * 10));
        }
        return params[0];
    }

    @Override
    protected void onPostExecute(String result) {
        Log.e(TAG, "result: " + result);
    }

    @Override
    protected void onProgressUpdate(String... values) {
        Log.e(TAG, "onProgressUpdate: " + values[0]);
    }
}

測驗代碼:

 private void testAsyncTask() {
     int count = 100;
     for (int i = 0; i < count; i++) {
         //串行
         new MyAsyncTask().execute("MyAsyncTask[ " +i+ " ]");
         //并行
         new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "MyAsyncTask[ " +i+ " ]");
         final String name = "MyAsyncTask[ " +i+ " ]";
         //串行
         AsyncTask.execute(new Runnable() {
             @Override
             public void run() {
                 for (int i = 0; i < 100; i++) {
                     Log.e("MyAsyncTask", "onProgressUpdate:  " + name + " === "+ (i * 10) );
                 }
             }
         });
         //并行
         AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
             @Override
             public void run() {
                 for (int j = 0; j < 100; j++) {
                     Log.e("MyAsyncTask", "onProgressUpdate:  " + name + " === "+ (j * 10));
                 }
             }
         });
     }
 }

AsyncTask原始碼決議:

AsyncTask的三個泛型引數:

public abstract class AsyncTask<Params, Progress, Result>
  • Params: doInBackground方法的接受引數型別
  • Progress:onProgressUpdate方法的接受引數型別
  • Result:onPostExecute方法的接受引數型別,以及doInBackground方法的回傳引數型別

AsyncTask的五個運行方法:

  • onPreExecute(): 在主執行緒中執行,在異步任務執行之前被呼叫,一般用于一些準備作業
  • doInBackground(Params… params) :在子執行緒運行,用來處理后臺任務,params就是execute(Params… params)方法傳遞的引數
  • onProgressUpdate(Progress… values):在主執行緒運,在doInBackground中可以呼叫publishProgress來更新進度
  • onPostExecute(Result result):在主執行緒運行,后臺任務處理完畢呼叫,并回傳doInBackground的結果
  • publishProgress(Progress… values): 在doInBackground中呼叫,來開啟進度更新

注意:

  • AsyncTask的實體必須在UI執行緒中創建
  • execute必須在UI執行緒中呼叫
  • 不能在doInBackGround中更改UI組件資訊
  • 一個AsyncTask的實體只能執行一次任務,執行第二次會拋出例外

AsyncTask建構式

    /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     *
     * @hide
     */
    public AsyncTask(@Nullable Looper callbackLooper) {
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

首先會嘗試初始化一個主執行緒mHandler 物件,使用 Looper.getMainLooper().然后創建了兩個物件:WorkerRunnableFutureTask,分別賦值為mWorkermFuture,這兩個物件初始化都實作了兩個回呼方法,當用戶執行了execute方法的時候在特定情況下會觸發這兩個物件的回呼方法,

這里能夠看到:doInBackground是在WorkerRunnablecall方法中被回呼執行的

WorkerRunnable:

private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
   Params[] mParams;
}

它是一個實作了 Callable 介面的抽象類

Callable介面:

public interface Callable<V> {
    V call() throws Exception;
}

Runnable 是一對兄弟,主要用在執行緒呼叫中,區別是 Runnablerun() 方法沒有回傳值,而 Callablecall() 方法有回傳值,Callable需要和ExcutorService結合使用,其中ExecutorService也是一個執行緒池物件繼承自Executor介面,ExecutorService提供的方法:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
  • submit(Callable task),傳遞一個實作Callable介面的任務,并且回傳封裝了異步計算結果的Future,
  • submit(Runnable task, T result),傳遞一個實作Runnable介面的任務,并且指定了在呼叫Future的get方法時回傳的result物件,
  • submit(Runnable task),傳遞一個實作Runnable介面的任務,并且回傳封裝了異步計算結果的Future,

FutureTask:

public class FutureTask<V> implements RunnableFuture<V> {

    public FutureTask(Callable<V> callable) {
        if (callable == null) throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
 
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

    public boolean isCancelled() {
        return state >= CANCELLED;
    }

    public boolean isDone() {
        return state != NEW;
    }
    
	public void run() {
		...
	}
	
	public boolean cancel(boolean mayInterruptIfRunning) {...}
	protected void done() { }
	public V get() {...}
	public void set(V v) {...}
}

它一個實作 RunnableFuture 介面的類,建構式可接受 Callable 引數型別,也就是 AsyncTask建構式中的 WorkerRunnable型別的 mWorker物件,同時它具備run方法以及canceldone和狀態判斷等方法,

至此,我們能看到: WorkerRunnablecall() 方法是在 FutureTaskrun() 方法中呼叫的
在這里插入圖片描述
RunnableFuture

繼承 RunnableFuture兩個介面,run方法是來自Runnable介面

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
public interface Future<V> {
     //取消任務
    boolean cancel(boolean mayInterruptIfRunning);

    //如果任務完成前被取消,則回傳true,
    boolean isCancelled();

    //如果任務執行結束,無論是正常結束或是中途取消還是發生例外,都回傳true,
    boolean isDone();

    //獲取異步執行的結果,如果沒有結果可用,此方法會阻塞直到異步計算完成,
    V get() throws InterruptedException, ExecutionException;

    // 獲取異步執行結果,如果沒有結果可用,此方法會阻塞,但是會有時間限制,
    //如果阻塞時間超過設定的timeout時間,該方法將回傳null,
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; 
}

Future介面能夠中斷執行中的任務, 判斷任務是否完成獲取任務執行完成后的結果(get),說白了就是對具體的Runnable或者Callable物件任務執行的結果進行獲取(get()),取消(cancel()),判斷是否完成等操作,

因為 RunnableFuture 同時繼承了 RunnableFuture兩個介面,所以 FutureTask 既可以被當做Runnable使用也可以被當做Future來使用,而 FutureTaskRunnableFuture 的實作類,那么接下來關鍵就是FutureTaskrun方法在何時被執行?

AsyncTask的execute()方法:

    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

呼叫了 executeOnExecutor 方法,sDefaultExecutor是一個執行緒池物件

executeOnExecutor方法:

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        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();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

這里先呼叫了 onPreExecute() 方法,然后執行了exec.execute(mFuture) ,也就是為啥onPreExecute() 是在主執行緒執行的,而 FutureTask 物件是被放入執行緒池中執行,

并且這里判斷狀態不等于 PENDING 時,只要狀態等于 RUNNING 或者 FINISHED就會拋例外!這也是為啥 一個AsyncTask的實體只能執行一次,第二次就會報錯的原因!

SerialExecutor:

 private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
 public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
 
 private static class SerialExecutor implements Executor {
 		// 雙端佇列 既可以當成佇列使用 也可以當成堆疊使用
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive; // 當前正在執行的那個 mFutureTask

        public synchronized void execute(final Runnable r) {
        	// offer 向隊尾插入元素,成功回傳 true
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            //判斷如果沒有Runnable在執行,就呼叫scheduleNext
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
        	// poll 獲取并洗掉隊首元素,失敗回傳null
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive); // 由執行緒池真正的執行任務
            }
        }
    }

sDefaultExecutor 是一個 SerialExecutor 型別的 全域靜態的常量 物件 SERIAL_EXECUTORSerialExecutorExecutor 的實作類,在 execute() 方法中呼叫了 Runnable 物件的 run 方法,因此我們的 mFuture 物件最終是在這里被呼叫的,(前面提過 FutureTask既是Runnable的實作也是Future的實作),

這里 SerialExecutor 實際維護了一個佇列,每當 execute() 執行并傳入一個 FutureTask物件,這個物件就會被加入到隊尾,然后判斷當前如果沒有正在執行的FutureTask物件,就從隊首獲取一個FutureTask放入執行緒池中執行,
在這里插入圖片描述
而加入佇列中的 FutureTask物件也不是直接添加的,而是又包了一層 Runnable 物件,這個包一層又有什么用呢?看它這里使用了 try-finally 保證了scheduleNext()方法必被呼叫,也就是說當前這個 FutureTaskrun方法被執行完以后,會自動取下一個FutureTask放入執行緒池中執行,

也就是說當我們new一個AsyncTask任務開始execute時, 并不一定立馬執行,要看它里面的任務佇列情況,如果它是第一個就會立馬執行,如果它前面還有,那么就會排隊等待,也就是一個串行的任務佇列,

至此,我們能得到一個大體的執行流程:
new AsyncTask() --> new FutureTask() --> AsyncTask#execute() —> SerialExecutor#execute() --> mTasks.offer() --> mTasks.poll() --> scheduleNext() --> THREAD_POOL_EXECUTOR.execute() --> FutureTask#run() -->WorkerRunable#call()–>doInBackground()

postResult方法:
再回來看AsyncTask建構式中的結果回傳
在這里插入圖片描述
最終呼叫postResult方法:

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
    
    ....
    
    private Handler getHandler() {
        return mHandler;
    }
    
	....
    
    private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }

這里沒什么神秘的了,就是通過Handler發訊息,而這里的Handler物件就是建構式第一行代碼初始化的運行在主執行緒的Handler物件(使用了 Looper.getMainLooper() ),

    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // mTask就是當前的AsyncTask物件
                    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;
    }

InternalHandler 內部接受到訊息后呼叫了 AsyncTaskfinish方法,這個方法里判斷如果被取消的話就回呼 onCancelled() 回呼方法(判斷取消使用了AtomicBoolean),否則呼叫我們熟悉的 onPostExecute(Result result) 方法回傳結果,同時把內部狀態置為 FINISHED, 至此一個異步程序完畢,

這里的handler內部還處理了一個MESSAGE_POST_PROGRESS訊息,它就是publishProgress方法發送的訊息:

    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }

可以完善一下完整流程:
new AsyncTask() --> new FutureTask() --> AsyncTask#execute() —> SerialExecutor#execute() --> mTasks.offer() --> mTasks.poll() --> scheduleNext() --> THREAD_POOL_EXECUTOR.execute() --> FutureTask#run() -->WorkerRunable#call()–>doInBackground() --> postResult() --> InternalHandler#sendMessage --> onPostExecute()

一句話總結:建構式創建異步任務物件的載體FutureTaskdoInBackground()在該載體中被回呼執行,然后execute()會不斷的向內部SerialExecutor(全域靜態常量)的串行列隊(ArrayDeque)的隊尾添加任務,同時不斷的從隊首取出任務放入執行緒池中執行,執行完畢再通過Handler發訊息到主執行緒來回呼onPostExecute()

THREAD_POOL_EXECUTOR:

    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;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    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;
    }

內部執行緒池的創建引數(假設CPU核的數量是N):

  • CORE_POOL_SIZE: 執行緒池中核心執行緒數,這里是在[2,4]的范圍內,也就是最少2個執行緒,最大4個執行緒,并且小于4時期望是 N - 1 即比CPU核心總數少1,避免整個設備CPU飽和無法執行其他任務
  • MAXIMUM_POOL_SIZE: 執行緒池的最大執行緒數 = 2 * N + 1
  • KEEP_ALIVE_SECONDS: 執行緒數超出執行緒池之后,空置執行緒等待任務的超時時間,30s
  • sPoolWorkQueue: 持有 Runnable Task的阻塞佇列,長度是 128

雖然這里執行緒池能支持并發執行,但是AsyncTask內部的SerialExecutor是持有了一個串行的佇列依次呼叫執行緒池執行,所以AsyncTask本身還是串行的,這也就解釋了為啥 new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"xxx")AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {...}) 這兩個方式是可以并行的,而其他方式是串行的,因為這倆是直接指定了運行在并發執行緒池上的,而其他不顯示指定Executor方式是統一執行在內部的SerialExecutor物件上的,而這個物件是串行佇列的,

AsyncTask 的缺陷:

  • 生命周期的不同步: 如果AsyncTask被宣告為Activity的非靜態的內部類,Activity退出了,AsyncTask還在后臺運行,那么就會一直持有Activity實體造成記憶體泄漏,如果Activity銷毀重建了,那么AsyncTaskonPostExecute()中要訪問的 mView 不存在了也會導致空指標
  • 全域串行佇列任務的不可靠性: 由于是串行的,并且它內部的SERIAL_EXECUTOR,在單個行程中是全域靜態唯一,這會導致一個明顯的缺陷就是無法保證你添加到里面的任務一定會被執行或者按照你期望的時間被執行,假如你接入了一個第三方庫,它的內部在不停的執行new AsyncTask().execute(),那么AsyncTask內部就會排隊了很多任務,而你的任務何時被執行甚至能不能被執行都不確定,這會造成難以預料的邏輯錯誤,

HandlerThread

場景:適用于主執行緒需要和作業執行緒通信, 適用于持續性任務, 比如輪訓的場景,所有任務串行執行
缺點:不會像普通執行緒一樣主動銷毀資源,會一直運行著,所以可能會造成記憶體泄漏

測驗代碼:

     private void testThreadHandler() {
        HandlerThread thread = new HandlerThread("concurrent-thread");
        thread.start();
        //串行執行
        ThreadHandler handler = new ThreadHandler(thread.getLooper()) {
            @Override
            public void handleMessage(@NonNull Message msg) {
                //運行在子執行緒中
                switch (msg.what) {
                    case MSG_WHAT_FLAG_1:
                        //Log.e(TAG, "handleMessage: threadId = " + Thread.currentThread().getId());
                        Log.e(TAG, "handleMessage: " + MSG_WHAT_FLAG_1 +" msg.arg1 = " + msg.arg1);
                        //通知主執行緒去更新UI
                        //mUIHandler.sendMessage(msg1);
                        break;
                    default:
                        break;
                }
            }
        };
        handler.sendEmptyMessage(MSG_WHAT_FLAG_1);
        for (int i = 0; i < 100; i++) {
            Message message = handler.obtainMessage();
            message.what = MSG_WHAT_FLAG_1;
            message.arg1 = i+1;
            handler.sendMessageDelayed(message, 1000);
        }
        //Log.e(TAG, "testThreadHandler: threadId = " + Thread.currentThread().getId());
        //退出執行緒
//        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
//            thread.quitSafely();
//        } else {
//            thread.quit();
//        }
    }

    //定義成靜態,防止記憶體泄漏
    static class ThreadHandler extends Handler {
        public ThreadHandler(Looper looper) {
            super(looper);
        }

        public ThreadHandler(Looper looper, Callback callback) {
            super(looper, callback);
        }
    }

第二種使用方式,Handler傳一個callback:

    ThreadHandler handler = new ThreadHandler(thread.getLooper(), new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            //在子執行緒中進行相應的網路請求
            switch (msg.what) {
                case MSG_WHAT_FLAG_1:
                    Log.e(TAG, "handleMessage: threadId = " + Thread.currentThread().getId());
                    Log.e(TAG, "handleMessage: " + MSG_WHAT_FLAG_1 +" msg.arg1 = " + msg.arg1);
                    break;
                default:
                    break;
            }
            //通知主執行緒去更新UI
            //mUIHandler.sendMessage(msg1);
            return false;
        }
    });

new HandlerThread() thread.start()后 getLooper() 然后傳給 Handler創建即可,然后Handler處理訊息就是在子執行緒中了

HandlerThread 的原始碼決議:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();//生成執行緒id
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    /**
     * @return a shared {@link Handler} associated with this thread
     * @hide
     */
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }
    
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }
    
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }
    
    public int getThreadId() {
        return mTid;
    }
}

HandlerThread 本身繼承了 Thread 類,原始碼非常短,主要看 run 方法
在這里插入圖片描述
在創建HandlerThread物件后必須呼叫start()方法才能進行其他操作,而呼叫start()方法后run方法將會被呼叫,run方法做的事:

  • 執行Looper.prepare()代碼,創建Looper物件并系結到當前執行緒,這樣我們才可以把Looper物件賦值給Handler物件,進而確保Handler物件中的handleMessage方法是在異步執行緒執行的,
  • mLooper = Looper.myLooper()獲取創建的Looper物件賦值給HandlerThread的內部變數,并通過 notifyAll() 方法去喚醒等待執行緒
  • 執行Looper.loop()開啟looper回圈陳述句

Looper.myLooper() 后面為什么有個 notifyAll() 呢?
HandlerThreadgetLoope()方法:

	//該方法主要作用是獲得當前HandlerThread執行緒中的mLooper物件
    public Looper getLooper() {
        //如果執行緒不是活動的,則直接回傳null
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        //同步代碼塊:如果執行緒已經啟動,但是Looper還未創建的話,就阻塞等待,直到run()中的Looper物件創建成功
        synchronized (this) {
            //阻塞-等待機制
            //開啟回圈,呼叫wait()去阻塞執行緒,當run()中的notifyAll()呼叫之后,
            //通知當前執行緒的wait方法結束阻塞,跳出回圈,確保已經創建了mLooper物件,
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    } 

首先,HandlerThreadgetLoope()一定是在主執行緒或其他執行緒呼叫的,所以這里有個同步問題:

  • 當呼叫 getLoope() 方法時,如果當前HandlerThread還沒有啟動,就直接回傳;
  • 如果執行緒啟動了,但是 mLooper 物件還沒有完成創建,就呼叫 wait() 一直等待,直到 run 方法中的 mLooper 被賦值完成,呼叫 notifyAll() 來通知執行緒結束阻塞,

quit() 和 quitSafely() 方法

這倆個方法內部分別呼叫了 Looperquit()quitSafely() 方法,這兩個方法又分別呼叫了mQueue.quit(false)mQueue.quit(true),其中 mQueue 是Looper內部的MessageQueue物件

看一下 MessageQueuequit() 方法:
在這里插入圖片描述
根據是否safe, 最終分別調用了 removeAllMessagesLocked()removeAllFutureMessagesLocked 方法

  • removeAllMessagesLocked(): 該方法主要是 MessageQueue 訊息池中所有的訊息全部清空,無論是延遲訊息還是非延遲訊息(延遲訊息是指通過sendMessageDelayed或通過postDelayed等方法發送)
  • removeAllFutureMessagesLocked : 該方法 只會清空MessageQueue訊息池中所有的延遲訊息,并將訊息池中所有的非延遲訊息派發出去讓Handler去處理完成后才停止Looper回圈,

可見 quitSafely 相比于 quit 方法安全的原因在于清空訊息之前會派發所有的非延遲訊息

需要注意的是Looperquit方法是基于API 1,而quitSafely方法則是API 18才支持的,

IntentService

適用于跨頁面讀取任務執行的進度,結果,比如后臺上傳圖片,批量操作資料庫等,任務執行完成功后,就會自我結束,所以不需要手動stopservice,這是他跟service的區分,
測驗代碼:

public class MyIntentService extends IntentService {
    private final static String TAG = MyIntentService.class.getSimpleName();

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        if (intent != null) {
            String name = intent.getStringExtra("name");
            for (int i = 0; i < 100; i++) {
                Log.e(TAG, "onHandleIntent: " + name + " === "+ i );
            }
        }
        Log.e(TAG, "onHandleIntent: finish");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: ");
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy: ");
    }
}
	private void testService() {
        Intent service = new Intent(this, MyIntentService.class);
        for (int i = 0; i < 2; i++) {
            service.putExtra("name", "test"+i);
            startService(service);
        }
    }

如果兩次緊接著啟動IntentService第一次會執行onCreate, 然后執行兩次onStartCommand,兩次執行完最終執行onDestroy銷毀,

使用IntentService注意的兩點:

  • IntentService需要一個無參的建構式,否則啟動不了
  • IntentService需要在manifest里注冊,否則啟動不了
  • 除了onHandleIntent構造方法之外最好不要覆寫任何其他方法,覆寫的話必須呼叫super, 否則容易出錯

IntentService原始碼決議:

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
    
    public IntentService(String name) {
        super();
        mName = name;
    }

    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
    
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }
    
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }
    
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

繼承了Service,然后利用了HandlerThread,在 onCreate() 方法中創建 HandlerThread 并啟動,使用它的Looper創建了一個內部的ServiceHandler來處理訊息,onHandleIntent方法就是執行在 ServiceHandlerhandleMessage方法中,onHandleIntent執行完緊接著執行stopSelf停止ServiceonStartCommand 方法呼叫了 onStart 方法,里面就是給ServiceHandler發送訊息,

所以如果多次啟動IntentService, 相當于向ServiceHandler發送了多個訊息,而這些訊息會處于Looper訊息回圈當中,只有這些都被處理完畢,單執行緒退出,Service才會真正的銷毀,銷毀之后再次啟動IntentService時,又會重新走一個完整的onCreateonDestroy的流程,會重新創建一個 HandlerThread

JobIntentService

適用于 Android 8.0 以后的后臺服務執行后臺靜默任務, Android 8.0 以后系統不允許應用創建后臺服務,使用Service做后臺服務需要使用ContextCompat.startForegroundService啟動前臺服務,這會在通知欄顯示一個正在運行的Notification表示該Service是一個前臺服務,給用戶帶來不友好的用戶體驗,Android 8.0 引入了一個新的 JobIntentService 類來專門處理后臺服務作業,服務添加的任務執行在單獨的執行緒當中,

manifest檔案中必須添加以下配置:

<uses-permission android:name="android.permission.WAKE_LOCK"/>
<service
     android:name=".async.test.MyJobIntentService"
     android:permission="android.permission.BIND_JOB_SERVICE"
     />

測驗代碼:

public class MyJobIntentService extends JobIntentService {
    private final static String TAG = MyJobIntentService.class.getSimpleName();
    private boolean mExit = false;

    /**
     * 這個Service 唯一的id
     */
    static final int JOB_ID = 10111;

    /**
     * Convenience method for enqueuing work in to this service.
     */
    static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, MyJobIntentService.class, JOB_ID, work);
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        final String name = intent.getStringExtra("name");
        //模擬在后臺每隔5s執行一次任務
        while (!mExit) {
            for (int i = 0; i < 100; i++) {
                Log.e(TAG, "onHandleWork: " + name + " === "+ i );
            }
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Log.e(TAG, "onHandleWork: finish");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: ");
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: ");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy: ");
    }
}
     private void testJobIntentService() {
        Intent intent = new Intent();
        intent.putExtra("name", "aaa");
        MyJobIntentService.enqueueWork(AsyncTaskTestActivity.this, intent);
    }

這里故意在onHandleWork方法里寫了死回圈來模擬每隔5s執行一次后臺任務,測驗會發現基本能保持一直在后臺定期運行,不被系統殺死,并且通知欄也無需任何通知,當然這也不是絕對的,Doze模式 除外,當然你也可以啟動多次 MyJobIntentService.enqueueWork() 來做不同的事,

JobIntentService 原始碼分析:

enqueueWork

	//JobIntentService的入口方法
    public static void enqueueWork(@NonNull Context context, @NonNull ComponentName component, int jobId, @NonNull Intent work) {
        if (work == null) {
            throw new IllegalArgumentException("work must not be null");
        } else {
            synchronized(sLock) {
                JobIntentService.WorkEnqueuer we = getWorkEnqueuer(context, component, true, jobId);//根據版本獲取不同的WorkEnqueuer
                we.ensureJobId(jobId);//每個jobIntentService 唯一對應一個JobId,所有給這個service的work都必須相同
                we.enqueueWork(work);//呼叫WorkEnqueuer
            }
        }
    }

getWorkEnqueuer:

    static JobIntentService.WorkEnqueuer getWorkEnqueuer(Context context, ComponentName cn, boolean hasJobId, int jobId) {
        JobIntentService.WorkEnqueuer we = (JobIntentService.WorkEnqueuer)sClassWorkEnqueuer.get(cn);
        if (we == null) {
            if (VERSION.SDK_INT >= 26) {
                if (!hasJobId) {
                    throw new IllegalArgumentException("Can't be here without a job id");
                }
				// 8.0 的實作
                we = new JobIntentService.JobWorkEnqueuer(context, cn, jobId);
            } else {
                we = new JobIntentService.CompatWorkEnqueuer(context, cn);
            }

            sClassWorkEnqueuer.put(cn, we);
        }

        return (JobIntentService.WorkEnqueuer)we;
    }

可以看到8.0 的WorkEnqueuer實作是一個JobWorkEnqueuer物件:

    @RequiresApi(26)
    static final class JobWorkEnqueuer extends JobIntentService.WorkEnqueuer {
        private final JobInfo mJobInfo;
        private final JobScheduler mJobScheduler;

        JobWorkEnqueuer(Context context, ComponentName cn, int jobId) {
            super(context, cn);
            this.ensureJobId(jobId);
            Builder b = new Builder(jobId, this.mComponentName); 
            this.mJobInfo = b.setOverrideDeadline(0L).build();
            this.mJobScheduler = (JobScheduler)context.getApplicationContext().getSystemService("jobscheduler");
        }

        void enqueueWork(Intent work) {
            this.mJobScheduler.enqueue(this.mJobInfo, new JobWorkItem(work));
        }
    }

最終呼叫的是 JobScheduler.enqueue() 方法執行,mJobScheduler 中包含 mComponentName 是在父類建構式賦值的,在呼叫 enqueueWork(context, new ComponentName(context, cls), jobId, work) 方法時傳遞的,包含了當前Service的class類物件資訊,

看下JobIntentService的 onCreate 方法做了什么:

    public void onCreate() {
        super.onCreate();
        if (VERSION.SDK_INT >= 26) {
            this.mJobImpl = new JobIntentService.JobServiceEngineImpl(this);
            this.mCompatWorkEnqueuer = null;
        } else {
            this.mJobImpl = null;
            ComponentName cn = new ComponentName(this, this.getClass());
            this.mCompatWorkEnqueuer = getWorkEnqueuer(this, cn, false, 0);
        }
    }

初始化創建了一個JobServiceEngineImpl物件,保存為mJobImpl,看mJobImpl用到的地方:

    public IBinder onBind(@NonNull Intent intent) {
        if (this.mJobImpl != null) {
            IBinder engine = this.mJobImpl.compatGetBinder();
            return engine;
        } else {
            return null;
        }
    }

	JobIntentService.GenericWorkItem dequeueWork() {
        if (this.mJobImpl != null) {
            return this.mJobImpl.dequeueWork();
        } else {
            synchronized(this.mCompatQueue) {
                return this.mCompatQueue.size() > 0 ? (JobIntentService.GenericWorkItem)this.mCompatQueue.remove(0) : null;
            }
        }
    }

一個是 onBind 方法中獲取IBinder的實作,IPC使用,還有就是進行出隊操作

JobServiceEngineImpl

	@RequiresApi(26)
    static final class JobServiceEngineImpl extends JobServiceEngine implements JobIntentService.CompatJobEngine {
        static final String TAG = "JobServiceEngineImpl";
        static final boolean DEBUG = false;
        final JobIntentService mService;
        final Object mLock = new Object();
        JobParameters mParams;

        JobServiceEngineImpl(JobIntentService service) {
            super(service);
            this.mService = service;
        }

        public IBinder compatGetBinder() {
            return this.getBinder();
        }

        public boolean onStartJob(JobParameters params) {
            this.mParams = params;
            this.mService.ensureProcessorRunningLocked(false);
            return true;
        }

        public boolean onStopJob(JobParameters params) {
            boolean result = this.mService.doStopCurrentWork();
            synchronized(this.mLock) {
                this.mParams = null;
                return result;
            }
        }

        ...
    }

onStartJob 方法呼叫了Service物件的 ensureProcessorRunningLocked

    void ensureProcessorRunningLocked(boolean reportStarted) {
        if (this.mCurProcessor == null) {
            this.mCurProcessor = new JobIntentService.CommandProcessor();
            if (this.mCompatWorkEnqueuer != null && reportStarted) {
                this.mCompatWorkEnqueuer.serviceProcessingStarted();
            }

            this.mCurProcessor.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Void[0]);
        }
    }

這里new了一個 CommandProcessor 物件,并且呼叫了 它的executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Void[0]),有點眼熟,沒錯,這是一個AsyncTask物件:

    final class CommandProcessor extends AsyncTask<Void, Void, Void> {
        CommandProcessor() {
        }

        protected Void doInBackground(Void... params) {
            JobIntentService.GenericWorkItem work;
            while((work = JobIntentService.this.dequeueWork()) != null) {
                JobIntentService.this.onHandleWork(work.getIntent());
                work.complete();
            }
            return null;
        }

        protected void onCancelled(Void aVoid) {
            JobIntentService.this.processorFinished();
        }

        protected void onPostExecute(Void aVoid) {
            JobIntentService.this.processorFinished();
        }
    }

doInBackground 當中呼叫了onHandleWork方法,并從JobServiceEngineImpl的當中取出 work Intent 引數進行回呼給onHandleWork方法處理,

總結:JobIntentService主要封裝了 JobScheduler的使用,對開發者更友好,真正的任務是執行在AsyncTask的執行緒池上的

JobScheduler

適用于Android 5.0 以上執行任務調度,JobScheduler是一個系統提供的框架,旨在應用行程、而非系統行程內執行各種任務調度,其原理是通過bindservice的方式啟動對應應用行程的service,并在Service中進行作業,在執行一個Job時,將持有一個系統的WakeLock鎖,以防止系統休眠進入Suspend

在創建一個作業時,會設定多個約束條件,比如可以指定特定的網路、是否只在充電時執行作業等,JobScheduler框架會根據這些約束條件,智能地執行作業,并盡可能對作業進行批操作和推遲,以防止頻繁喚醒系統而影響功耗,

測驗代碼:

創建一個Service類繼承JobService

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
    private final static String TAG = MyJobService.class.getSimpleName();

    @Override
    public boolean onStartJob(final JobParameters params) {
        Log.e(TAG, "onStartJob: " + params.getJobId());
        PersistableBundle extras = params.getExtras();
        String name = extras.getString("name");
        int key = extras.getInt("key");
        Log.e(TAG, "onStartJob: key = " + key);
        for (int i = 0; i < 100; i++) {
            Log.e(TAG, "onStartJob: " + name + " === "+ i );
        }
        //設定true, 系統會再次調度任務,如果設定定時任務這個一定要為true, 否則是一次性任務
        jobFinished(params, true);
        //回傳true表示繼續執行
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        Log.e(TAG, "onStopJob: " + params.getJobId());
        //回傳false表示停止后不再重試執行
        return false;
    }
}

關鍵注意這個 jobFinished(params, true) 如果想設定定時任務這個一定要為true, 否則是一次性任務,

manifest中配置權限:

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

<service android:name=".MyJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"/>

創建JobInfo物件并執行:

 private void testJobService() {
 	 // 5.0 以上才支持
     if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
         JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);  //獲取JobScheduler系統服務
         ComponentName jobService = new ComponentName(this, MyJobService.class);

         PersistableBundle extras = new PersistableBundle();
         extras.putString("name", "aaaa");
         extras.putInt("key", 123);

         JobInfo jobInfo = new JobInfo.Builder(123, jobService) //任務Id等于123
                 .setMinimumLatency(5000) // 設定延遲調度時間
                 .setOverrideDeadline(5000) // 設定該Job截止時間,當到期沒達到指定條件也會開始執行該Job
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)// 網路條件,默認值NETWORK_TYPE_NONE
                 .setRequiresCharging(false)// 設定是否在設備充電時執行Job
                 .setRequiresDeviceIdle(false)// 設定是否在設備空閑時執行Job
                 .setPersisted(true) // 設備重啟后是否繼續執行
                 .setBackoffCriteria(3000, JobInfo.BACKOFF_POLICY_LINEAR) //設定退避/重試策略
                 .setExtras(extras) // 傳遞額外引數
                 .build();
         scheduler.schedule(jobInfo);
     }
 }

取消任務:

 scheduler.cancel(123); //取消jobId=123的任務
 scheduler.cancelAll(); //取消當前uid下的所有任務

需要注意的是,onStartJobonStopJob方法都是執行在主執行緒中的,如果要執行耗時任務需要在里面再開辟子執行緒執行,這點與JobIntentServiceonHandleWork方法不同,

JobScheduler 原始碼分析:

JobScheduler的整個實作程序比較復雜,可參考如下大神文章

理解JobScheduler機制
Android之JobScheduler運行機制原始碼分析
Android 9.0 JobScheduler(二) JobScheduler框架結構簡述及JobSchedulerService的啟動
Android 9.0 JobScheduler(三) 從Job的創建到執行
Android 9.0 JobScheduler(四) Job約束條件的控制

JobService的onBind:

    private JobServiceEngine mEngine;

    /** @hide */
    public final IBinder onBind(Intent intent) {
        if (mEngine == null) {
            mEngine = new JobServiceEngine(this) {
                @Override
                public boolean onStartJob(JobParameters params) {
                    return JobService.this.onStartJob(params);
                }

                @Override
                public boolean onStopJob(JobParameters params) {
                    return JobService.this.onStopJob(params);
                }
            };
        }
        return mEngine.getBinder();
    }

主要是通過 JobServiceEngine 這個類來處理的

JobServiceEngine類:

    public JobServiceEngine(Service service) {
        mBinder = new JobInterface(this);
        mHandler = new JobHandler(service.getMainLooper());
    }
    
	static final class JobInterface extends IJobService.Stub {
        final WeakReference<JobServiceEngine> mService;

        JobInterface(JobServiceEngine service) {
            mService = new WeakReference<>(service);
        }

        @Override
        public void startJob(JobParameters jobParams) throws RemoteException {
            JobServiceEngine service = mService.get();
            if (service != null) {
                Message m = Message.obtain(service.mHandler, MSG_EXECUTE_JOB, jobParams);
                m.sendToTarget();
            }
        }

        @Override
        public void stopJob(JobParameters jobParams) throws RemoteException {
            JobServiceEngine service = mService.get();
            if (service != null) {
                Message m = Message.obtain(service.mHandler, MSG_STOP_JOB, jobParams);
                m.sendToTarget();
            }
        }
    }

    /**
     * Runs on application's main thread - callbacks are meant to offboard work to some other
     * (app-specified) mechanism.
     * @hide
     */
    class JobHandler extends Handler {
        JobHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            final JobParameters params = (JobParameters) msg.obj;
            switch (msg.what) {
                case MSG_EXECUTE_JOB:
                    try {
                        boolean workOngoing = JobServiceEngine.this.onStartJob(params);
                        ackStartMessage(params, workOngoing);
                    } catch (Exception e) {
                        Log.e(TAG, "Error while executing job: " + params.getJobId());
                        throw new RuntimeException(e);
                    }
                    break;
                case MSG_STOP_JOB:
                    try {
                        boolean ret = JobServiceEngine.this.onStopJob(params);
                        ackStopMessage(params, ret);
                    } catch (Exception e) {
                        Log.e(TAG, "Application unable to handle onStopJob.", e);
                        throw new RuntimeException(e);
                    }
                    break;
                case MSG_JOB_FINISHED:
                    final boolean needsReschedule = (msg.arg2 == 1);
                    IJobCallback callback = params.getCallback();
                    if (callback != null) {
                        try {
                            callback.jobFinished(params.getJobId(), needsReschedule);
                        } catch (RemoteException e) {
                            Log.e(TAG, "Error reporting job finish to system: binder has gone" +
                                    "away.");
                        }
                    } else {
                        Log.e(TAG, "finishJob() called for a nonexistent job id.");
                    }
                    break;
                default:
                    Log.e(TAG, "Unrecognised message received.");
                    break;
            }
        }

JobServiceEngine 內部持有兩個類 JobInterfaceJobHandlerJobHandler 是運行在主執行緒的,JobInterface 就是接受到訊息后呼叫JobHandler 發送到主執行緒執行onStartJob

JobService內的簡單呼叫時序:
在這里插入圖片描述
整個系統的類結構:
在這里插入圖片描述
其中:

  • JobScheduler 是系統SystemServer行程暴露給Application 層的 JobSchedulerService 介面
  • Zygote行程啟動后會啟動SystemServer行程,在SystemServer行程啟動程序中會啟動系統中的關鍵服務,如AMSPMS以及這里要用到的JobSchedulerService等,
  • JobInfo 封裝Job的所有資訊
  • JobServiceJSS 調度的執行終端
  • JobServiceEngine是應用端的 JobServiceJSS 互動的橋梁

整個框架的實作原理,可以簡單的理解為:

  • 首先通過JobScheduler創建一個Job,接著JobSchedulerService將負責調度job
  • 通過不同的StateControllerJob進行跟蹤,判斷是否滿足調度條件
  • 當滿足條件后,通過JobServiceContext和客戶端的JobService進行系結并對Job進行生命周期管理
  • 然后在生命周期的某個階段中通過JobServiceEngine呼叫客戶端方法來執行Job任務,執行完成后,又在生命周期的某個階段回呼JobCompletedListener介面通知JobSchedulerService

WorkManager

WorkManager 適用于可延期作業,即不需要立即運行但需要可靠運行的作業,即使退出應用或重啟設備也不影響作業的執行,例如:向后端服務發送日志或分析資料、定期將應用資料與服務器同步等,

WorkManager屬于 Android Jetpack的一部分,需要單獨添加依賴庫使用,是androidx的api,

在這里插入圖片描述

WorkManager是一個包裝類,在API 23+及以上WorkManager內部使用 JobScheduler實作,其他情況下使用 BroadcastReceiver 和 AlarmManager實作,所以掌握使用 JobScheduler即可,詳細使用不展開了,可參考:

Android使用 WorkManager 調度任務
Android WorkManager,看這一篇就夠了

dependencies {
    // Other dependencies
    implementation "androidx.work:work-runtime:$versions.work"
}

簡單使用:

public class BlurWorker extends Worker {
    public BlurWorker(
            @NonNull Context appContext,
            @NonNull WorkerParameters workerParams) {
        super(appContext, workerParams);
    }
 
    @NonNull
    @Override
    public Result doWork() {
        Context applicationContext = getApplicationContext();
        try {
  			//do Something
            // If there were no errors, return SUCCESS
            return Result.success();
        } catch (Throwable throwable) {
            return Result.failure();
        }
    }
}

呼叫:

 WorkManager.getInstance(application)
 			.enqueue(OneTimeWorkRequest.from(BlurWorker.class));

WorkManager支持一次性作業和周期性作業,分別使用 OneTimeWorkRequestPeriodicWorkRequest構建,

另外,WorkManager還支持多個作業任務的鏈式呼叫:

WorkManager.getInstance(...)
    .beginWith(Arrays.asList(workA, workB))
    .then(workC)
    .enqueue();

執行緒中斷

其他執行緒通過呼叫某個執行緒的 interrupt 方法對其進行中斷操作,當一個執行緒呼叫 interrupt 方法時,執行緒的中斷狀態(標識位)將被置位(改變),這是每個執行緒都具有的boolean標志,每個執行緒都應該不時的檢查這個標志,來判斷執行緒是否被中斷,

while(!Thread.currentThread().isInterrupted()){
   //do something
}

一般情況下我們可以這樣來檢測執行緒是否被中斷,但是如果此時執行緒處于阻塞狀態(sleep或者wait),就無法檢查中斷狀態,此時會拋出 InterruptedException例外, 如果每次迭代之后都呼叫sleep方法(或者其他可中斷的方法),isInterrupted檢測就沒必要也沒用處了,如果在中斷狀態被置位時呼叫sleep方法,它不會休眠反而會清除這一休眠狀態并拋出InterruptedException,所以如果在回圈中呼叫sleep,不要去檢測中斷狀態,只需捕獲InterruptedException

public void run(){
	while(more work to do ){
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			//thread was interrupted during sleep
			e.printStackTrace();
		}finally{
			//clean up , if required
		}
	}
}

執行緒中斷相關的幾個方法:

  • void interrupt():向執行緒發送中斷請求,執行緒的中斷狀態將會被設定為true,如果當前執行緒被一個sleep呼叫阻塞,那么將會拋出interrupedException例外,
  • static boolean interrupted():測驗當前執行緒(當前正在執行命令的這個執行緒)是否被中斷,注意這是個靜態方法,呼叫這個方法會產生一個副作用那就是它會將當前執行緒的中斷狀態重置為false
  • boolean isInterrupted():判斷執行緒是否被中斷,這個方法的呼叫不會產生副作用即不改變執行緒的當前中斷狀態,

守護執行緒

通過 thread.setDaemon(true) 方法可以將執行緒轉化為守護執行緒,而守護執行緒的唯一作用就是為其他執行緒提供服務,

計時執行緒就是一個典型的例子,它定時地發送“計時器滴答”信號告訴其他執行緒去執行某項任務,當只剩下守護執行緒時,虛擬機就退出了,因為如果只剩下守護執行緒,程式就沒有必要執行了,另外JVM的垃圾回收、記憶體管理等執行緒都是守護執行緒,還有就是在做資料庫應用時候,使用的資料庫連接池,連接池本身也包含著很多后臺執行緒,監控連接個數、超時時間、狀態等等,

還有一點需要特別注意的是在java虛擬機退出時Daemon執行緒中的finally代碼塊并不一定會執行,因此在構建Daemon執行緒時,不能依靠finally代碼塊中的內容來確保執行關倍訓清理資源的邏輯,

執行緒優先級

關于時間片:

在現代作業系統中基本采用時分的形式調度運行的執行緒,作業系統會分出一個個時間片,執行緒會分配到若干時間片,當執行緒的時間片用完了就會發生執行緒調度,并等待著下一次分配,執行緒分配到的時間片多少也決定了執行緒使用處理器資源的多少,而執行緒優先級就是決定執行緒需要多或者少分配一些處理器資源的執行緒屬性,

  • Java的執行緒的優先級具有繼承性,在某執行緒中創建的執行緒會繼承此執行緒的優先級,那么我們在UI執行緒中創建了執行緒,則執行緒優先級是和UI執行緒優先級一樣,平等的和UI執行緒搶占CPU時間片資源,
  • JDK Api 通過getPriority()setPriority()獲取和設定優執行緒的先級,取值范圍為 [1~10], 優先級越高,獲取CPU時間片的概率越高,UI執行緒優先級為5,
Thread thread = new Thread(){
   @Override
   public void run() {...}
};
thread.start();

int jdkUIPriority = Thread.currentThread().getPriority();
int jdkThreadPriority = thread.getPriority();

Log.e(TAG, "jdkUIPriority = " + jdkUIPriority); // 5
Log.e(TAG, "jdkThreadPriority = " + jdkThreadPriority); // 5
  • Android Api 通過Process.getThreadPriority()Process.setThreadPriority() 可以為執行緒設定更加精細的優先級(-20~19),優先級priority的值越低,獲取CPU時間片的概率越高,UI執行緒優先級為 -10
Thread thread = new Thread(){
  @Override
   public void run() {
       int androidThreadPriority = Process.getThreadPriority(0);
       Log.e(TAG, "androidThreadPriority: " + androidThreadPriority); // 0
   }
};
thread.start();
int androidUIPriority = Process.getThreadPriority(0); //傳0回傳呼叫處所在的執行緒優先級
Log.e(TAG, "androidUIPriority: " + androidUIPriority); // -10

執行緒狀態

狀態說明
NEW初始狀態,創建執行緒物件,還沒呼叫start方法
RUNNABLE運行狀態, 把"運行中"和"就緒"統稱為運行狀態,呼叫start方法后
BLOCKED阻塞狀態,執行緒獲取的同步鎖被別的執行緒占用,暫時停止運行
WAITING等待狀態,運行的執行緒執行wait()方法,需要其他執行緒通知喚醒
TIME_WAITING超時等待狀態,運行的執行緒執行sleep()join()方法,在指定的時間超時后自行恢復
TERMINATED終止狀態,執行緒已經執行完畢,執行完run方法或遇到例外退出

在這里插入圖片描述

  • Thread.sleep(long):使當前執行緒進入阻塞狀態,在指定時間內不會執行,

  • Object.wait()Object.wait(long) :使當前執行緒進入等待狀態,直到其他執行緒呼叫物件的notifynotifyAll方法,執行緒會釋放掉它所占有的"鎖標志",從而使別的執行緒有機會搶占該鎖, 當前執行緒必須擁有當前物件鎖,如果當前執行緒不是此鎖的擁有者,會拋出IllegalMonitorStateException例外,

  • Object.notifyAll()Object.notify(): 喚醒當前物件鎖的等待執行緒,Object.notifyAll()喚醒所有等待執行緒,Object.notify()喚醒其中一個執行緒;也必須擁有相同的物件鎖,否則也會拋出IllegalMonitorStateException例外,

  • wait()notify()必須在synchronized函式或synchronized代碼塊中進行呼叫,如果在non-synchronized函式或non-synchronized代碼塊中進行呼叫,雖然能編譯通過,但在運行時會發生IllegalMonitorStateException的例外,

  • Thread.yield(): 暫停當前正在執行的執行緒物件,yield()只是使當前執行緒重新回到可執行狀態,所以執行yield()的執行緒有可能在進入到可執行狀態后馬上又被執行,yield()只能使同優先級或更高優先級的執行緒有執行的機會,

  • Thread.Join():把指定的執行緒加入到當前執行緒,可以將兩個交替執行的執行緒合并為順序執行的執行緒,比如在執行緒B中呼叫了執行緒A的Join()方法,直到執行緒A執行完畢后,才會繼續執行執行緒B,

執行緒池

ThreadPoolExecutor 適用于處理大量耗時較短的任務場景,可以有效控制執行緒數量、運行周期等 ,

Executor類繼承關系:

ThreadPoolExecutor extends AbstractExecutorService implements ExecutorService extends Executor

其中,AbstractExecutorService是抽象類,ExecutorServiceExecutor是介面,
在這里插入圖片描述

執行方法:可以通過executesubmit方法執行Runnable或者Callable<V>的任務

void execute(Runnable command);
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
  • RunnableCallable<V>區別是一個不能回傳值,一個可以回傳值,
  • executesubmit的區別是通過submit提交的任務會回傳一個FutureTask物件,以便將來獲取回傳值,
  • FutureTask<V> 實作 RunnableFuture<V> 介面, RunnableFuture<V>同時繼承了RunnableFuture兩個介面,

ThreadPoolExecutor建構式
在這里插入圖片描述
建構式的引數含義:

引數說明
corePoolSize核心執行緒數,核心執行緒會一直存活,即使它們處理閑置狀態,但如果設定了allowCoreThreadTimeOuttrue 則核心執行緒也會超時退出,
maximumPoolSize最大執行緒數,執行緒池中可允許創建的最大執行緒數,當活動執行緒數到達這個數值后,后續的新任務將會被阻塞,
keepAliveTime超時時間,當執行緒池中的執行緒數大于核心執行緒數時,非核心執行緒閑置時長超過keepAliveTime后就會退出,直到執行緒數量等于corePoolSize,如果設定了allowCoreThreadTimeout設定為true,則所有執行緒均會執行超時策略直到執行緒數量為0,
unitkeepAliveTime引數的時間單位, 納秒/微妙/毫秒/秒/分鐘/小時/天,
workQueue保存任務的 阻塞佇列,只會保存通過execute方法提交到執行緒池的Runnable任務,
threadFactory執行緒工廠,為執行緒池提供創建新執行緒的功能,ThreadFactory是一個介面,它只有一個方法:Thread newThread(Runnable r)
handler飽和策略,當執行緒池關倍訓飽和時,呼叫HandlerrejectExecution方法通知呼叫者,默認拋出RejectExecutorException例外,

執行緒池的邏輯結構圖:
在這里插入圖片描述
執行緒池執行任務程序:

1. 當執行緒數小于核心執行緒數時,創建執行緒,
2. 當執行緒數大于等于核心執行緒數,且任務佇列未滿時,將任務放入任務佇列,
3. 當執行緒數大于等于核心執行緒數,且任務佇列已滿
a). 若執行緒數小于最大執行緒數,創建執行緒
b). 若執行緒數等于最大執行緒數,拋出例外,拒絕任務

在這里插入圖片描述

JDK自帶的幾種執行緒池:

Executors.newFixedThreadPool();      // 固定執行緒數的執行緒池 
Executors.newSingleThreadExecutor(); // 執行緒數為1的執行緒池
Executors.newCachedThreadPool();     // 可復用執行緒池 
Executors.newScheduledThreadPool();  // 可執行定時任務的執行緒池 
Executors.newSingleThreadScheduledExecutor(); // 單執行緒執行定時任務的執行緒池
Executors.newWorkStealingPool(); // 搶占式操作的執行緒池

Executors是一個專門用來創建執行緒池的工廠類,

FixedThreadPool

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

最大特點是 corePoolSize = maximumPoolSize,即核心執行緒數和最大執行緒數相等

  • 如果當前運行執行緒數少corePoolSize,則創建一個新的執行緒來執行任務,
  • 當前執行緒數量達到corePoolSize后,新的任務將加入LinkedBlockingQueue佇列中等待,
  • 執行緒池中執行緒任務執行完畢后,會回圈中反復從 LinkedBlockingQueue獲取任務來執行
  • 由于使用無界佇列,運行中的 FixedThreadPool不會拒絕任務,所以不會去呼叫 RejectExecutionHandlerrejectExecution方法拋出例外,

FixedThreadPool使用的 LinkedBlockingQueue 的默認大小是 Integer.MAX_VALUE,因此是無界佇列,這也會導致一個缺點就是運行時可能造成大量任務的堆積,

SingleThreadExecutor

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

單執行緒池可以看作是FixedThreadPool的特例,核心執行緒數和最大執行緒數都是1

  • 如果當前執行緒池中執行緒正在執行任務,新添加的任務同樣會被加入到無界佇列中等待執行,
  • 等一個任務執行完以后再按順序從佇列中取下一個任務來執行,即同一時刻保證只有一個任務會被執行,這種特點可以被用來處理共享資源的問題而不需要考慮同步的問題

由于使用無界佇列,它的缺陷也和FixedThreadPool相同,

CachedThreadPool

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

CachedThreadPool適合處理大量任務周期比較短的異步任務場景,它的最大特點是核心執行緒數為0,最大執行緒數無限制Integer.MAX_VALUE,即2147483647),這意味著運行時可能創建足夠多的執行緒來執行任務,同時它的閑置超時時間是60s

CachedThreadPool中的新創建的執行緒在執行完一個任務以后,不會馬上銷毀而是被回圈復用來執行下一個任務,以此來避免創建新的執行緒,

CachedThreadPool的缺陷也很明顯,如果主執行緒提交任務的速度高于執行緒池中執行緒處理任務的速度時,CachedThreadPool將會不斷的創建新的執行緒,在極端情況下,可能會因為創建過多執行緒而耗盡CPU和記憶體資源,

ScheduledThreadPool

 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
     return new ScheduledThreadPoolExecutor(corePoolSize);
 }
 public ScheduledThreadPoolExecutor(int corePoolSize) {
      super(corePoolSize, Integer.MAX_VALUE,
            DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
            new DelayedWorkQueue());
  }

ScheduledThreadPool是使用的單獨的一個類,適用于延時或周期性定時的執行后臺任務,它的繼承關系:
ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService extends ExecutorService

它的最大執行緒數也沒有限制(Integer.MAX_VALUE),使用的阻塞佇列為延時優先級佇列DelayedWorkQueue(最小堆),DelayedWorkQueue保證添加到佇列中的任務,按照任務的延時時間進行排序,延時時間少的任務首先被獲取,

執行延遲或周期執行任務的方法:

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)//延時執行任務
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay,TimeUnit unit)//延時執行任務
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)//初始化延時后,固定周期執行
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay, long delay, TimeUnit unit)//初始化延時后,固定延時時間執行

在對應方法內部都會創建一個ScheduledFutureTask物件(繼承FutureTask),并添加到DelayedWorkQueue佇列中等待執行,

由于ScheduledThreadPool的最大執行緒數是無限制的,并且所使用的DelayedWorkQueue也是無界佇列,因此ScheduledThreadPool的缺陷也是有可能會導致耗盡CPU和記憶體資源的風險,

SingleThreadScheduledExecutor

 public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
     return new DelegatedScheduledExecutorService
         (new ScheduledThreadPoolExecutor(1));
 }

同ScheduledThreadPool, 只不過是創建一個核心執行緒數為1的單執行緒來執行,在執行緒池關閉之前如果有一個任務執行失敗,并導致執行緒結束,系統會創建一個新的執行緒接著執行佇列里的任務,任務會保證按順序執行,同一時刻只會有一個任務在執行,因此也可以用來處理不需要考慮同步的資源共享問題,

WorkStealingPool

public static ExecutorService newWorkStealingPool() {
    return new ForkJoinPool
        (Runtime.getRuntime().availableProcessors(),
         ForkJoinPool.defaultForkJoinWorkerThreadFactory,
         null, true);
}

ThreadPoolExecutor不同,WorkStealingPoolJDK 1.8新增的執行緒池,利用的類是 ForkJoinPool,繼承關系:

ForkJoinPool extends AbstractExecutorService implements ExecutorService

ForkJoinPool建構式:

    public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode) {
        this(checkParallelism(parallelism),
             checkFactory(factory),
             handler,
             asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
             "ForkJoinPool-" + nextPoolId() + "-worker-");
        checkPermission();
    }

建構式的第一個引數parallelism是指并發執行緒數,沒有了核心執行緒數和最大執行緒數的概念,從控制并發執行緒數量的角度去處理問題,這一點與ThreadPoolExecutor有很大不同,在Executors類中的第一個引數傳的是Runtime.getRuntime().availableProcessors(),也就是默認并發數是按照JVM可用的CPU核心數量來的,

ForkJoinPool基于分治的思想,內部使用“作業竊取”演算法,能夠合理的使用CPU進行任務并行操作,最適合的場景是計算密集型任務,例如搜索、排序、大整數乘法等,

  • 每個作業執行緒都有自己的作業佇列WorkQueue,這是一個雙端佇列,它是執行緒私有的,自己從佇列頭存取任務,其它執行緒從尾部竊取任務;
  • ForkJoinTaskfork的子任務,將放入運行該任務的作業執行緒的隊頭,作業執行緒將以LIFO的順序來處理作業佇列中的任務
  • 為了最大化地利用CPU,空閑的執行緒將從其它執行緒的佇列中“竊取”(Stealing)任務來執行;它從作業佇列的尾部竊取任務,以減少競爭
  • 雙端佇列的操作:push()/pop()僅在其所有者作業執行緒中呼叫,poll()是由其它執行緒竊取任務時呼叫的
  • 當只剩下最后一個任務時,還是會存在競爭,通過CAS來實作

自定義執行緒池

由于Executors類當中提供的各種定義好的執行緒池或多或少都有一定的缺陷,如有必要,也可以自己手動構造一個滿足自己需求的執行緒池,ThreadPoolExecutor的構造器引數較多,最簡單的可以參考系統AsyncTask原始碼中構造的執行緒池,

 // CPU核心數
 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
 //執行緒池核心執行緒數:最少2個,最大CPU核心數-1個
 private static final int CORE_POOL_SIZE = Math.max(2, CPU_COUNT - 1);
 //執行緒池最大執行緒數: CPU核心數x2
 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2;

 public static final Executor THREAD_POOL_EXECUTOR;

 static {
     ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
             CORE_POOL_SIZE,
             MAXIMUM_POOL_SIZE,
             30, TimeUnit.SECONDS,
             new PriorityBlockingQueue(20), //優先級佇列
             new DefaultThreadFactory());
     threadPoolExecutor.allowCoreThreadTimeOut(true);
     THREAD_POOL_EXECUTOR = threadPoolExecutor;
 }

實作執行緒池中任務按優先級執行:

class PriorityRunnable(int priority,Runnable runnable) implements Runnable, Comparable{
 @override 
 public void run() {
  	//讓真正的runnable任務去執行 
  	runnable.run() 
 } 
 
 @override 
 public int compareTo(PriorityRunnable other) { 
	 //實作Comparable介面,復寫該方法,用以比較各任務的優先級排序 
	 return if (this.priority < other.priority) 1 else if (this.priority > other.priority) -1 else 0 } } 
 }

實作執行緒池的暫停/恢復:

class ThreadPoolExecutor {
    boolean isPaused;
    ReentrantLock lock = new ReentrantLock();
    Condition pauseCondition = lock.newCondition();

    void beforeExecute(Thread thread, Runnable runnable) {
        if (isPaused) {
            try {
                //使當前執行緒阻塞
                lock.lock();
                pauseCondition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    void pasue() {
        isPaused = true;
    }

    void resume() {
        isPaused = false;
        pauseCondition.signal();
    }
}

實作異步任務結果主動切換到主執行緒:

abstract class Callable<T> implements Runnable {
    Handler mainHandler;

    @Override
    public void run() {
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                onPrepare();
            }
        });

        final T t = onBackground();

        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                onCompleted(t);
            }
        });
    }

    public void onPrepare() {
        //任務開始前的準備,比如轉徑訓
    }

    //子執行緒真正的去執行
    abstract T onBackground();

    //執行完拋到主執行緒
    abstract void onCompleted(T t);
}

關閉執行緒池

ExecutorService 介面提供了兩個方法來關閉執行緒池:

  • shutdown() 啟動有序關閉,之前提交的任務會繼續執行,但不會接受新的任務,
  • shutdownNow() 嘗試停止所有正在執行的任務,不再處理還在池佇列中等待的任務,并回傳等待執行的任務串列,
    在這里插入圖片描述

需要注意的是,即便是呼叫 shutdownNow(),也不一定能保證執行緒池中的任務立即被停止,因為它停止執行緒是通過呼叫 Thread.interrupt() 方法來實作的,如果執行緒中沒有sleep 、wait、Condition、定時鎖等應用, interrupt()方法是無法中斷當前的執行緒的,所以,shutdownNow() 后有可能還是必須要等待所有正在執行的任務都執行完成了才能退出,

執行緒安全

這部分內容較多,單獨寫了一篇總結:Android中的執行緒(二)執行緒安全 & 執行緒同步

執行緒通信

Handler

Android執行緒通信主要是通過Handler來實作,整個應用的主執行緒的訊息機制都是基于Handler的實作,理解Handler的訊息機制,需要理解Looper、Handler、MessageQueue三者的關系,

Handler的建構式

    public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

建構式中會呼叫Looper.myLooper()獲取當前創建Handler所在執行緒的Looper物件,如果為空就會拋例外(提示必須先呼叫Looper.prepare()),不為空就獲取LooperMessageQueue物件并保存,

Handler的sendMessage方法

Handler的sendMessagexxx最終都會呼叫如下方法:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
		...
        return enqueueMessage(queue, msg, uptimeMillis);
    }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

所以發送訊息本質是呼叫enqueueMessage方法向MessageQueue佇列中插入一條訊息,

Looper

Looper主要作用就是啟動訊息回圈機制,不斷獲取和分發訊息,

public final class Looper {
	static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class
    final MessageQueue mQueue;
    final Thread mThread;
    
	private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    ...
}

Looper 內部持有一個ThreadLocal物件用來保存真正的Looper 實體物件,我們知道ThreadLocal是跟著執行緒走的,它只能獲取到系結到當前執行緒的Looper 實體物件,這也說明了每個執行緒的Looper物件都是隔離的,內部創建持有了一個MessageQueue 的訊息佇列成員,并且建構式有個引數quitAllowed來控制訊息佇列是否可以退出,

Looper.prepare()

如果在子執行緒中使用Handler就必須先呼叫 Looper.prepare(), 然后才能執行new Handler()的呼叫,

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

Looperprepare()方法中其實就是簡單是向sThreadLocal中設定創建的Looper物件,這意味著這個Looper物件系結到了當前呼叫者的執行緒上,并且prepare()方法只能呼叫一次,第二次呼叫內部會先判斷sThreadLocal獲取的Looper物件不為空就拋例外"Only one Looper may be created per thread",這保證了每個執行緒只會有一個Looper物件

另外還有個prepareMainLooper()方法,這個內部也是呼叫了prepare方法,不過傳遞的引數是false,也就是創建一個不會退出的Looper物件,這個是專門為主執行緒提供呼叫的,它將主執行緒的Looper物件保存到一個靜態成員sMainLooper ,方便開發者獲取,開發者不能呼叫這個方法,否則會例外,因為它內部也會檢查只能呼叫一次,這個方法是在應用行程啟動之后ActivityThread類的main方法中呼叫的,也就是我們通常說的主執行緒,
在這里插入圖片描述
myLooper()

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

    public static @NonNull MessageQueue myQueue() {
        return myLooper().mQueue;
    }

myLooper()方法就是從sThreadLocal中獲取系結到當前執行緒的Looper物件,方便其他人獲取,myQueue就是呼叫myLooper()再獲取Looper的mQueue成員,還有平時我們判斷當前代碼運行的地方是不是主執行緒的一種方法:Looper.myLooper() == Looper.getMainLooper() 這個就是利用了每個執行緒只會有一個唯一的Looper物件這點,只有呼叫了prepare方法之后,myLooper() 方法才會回傳值,否則就為null, 這就是new Handler之前一定要呼叫prepare的原因,

Looper.loop()

	/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        ...
 		//開啟死回圈
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
			...
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                ...
            }
        }
    }

Looper.loop()方法的主要邏輯比較簡單,獲取當前執行緒的Looper物件,如果沒有,就拋例外(這保證了loop方法之前必須呼叫prepare方法),如果有,就從Looper物件獲對應的MessageQueue 訊息佇列物件,然后直接開啟一個死回圈,并在for回圈中不斷呼叫訊息佇列的next()方法獲取下一條訊息,如果獲取到,就會呼叫msg.target.dispatchMessage去分發訊息物件,這里的targetHandler物件,如果佇列中沒有訊息就會阻塞在next()這里休眠等待,直到有訊息來臨,

退出方法:

 	public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }

通過呼叫quit()quitSafely()可以退出loop回圈體,都是呼叫的MessageQueuequit方法,二者區別是后者能保證已經在佇列中的訊息能安全的執行完,在呼叫退出方法之后,sendMessage方法發送的訊息將不會再被處理,

Handler的dispatchMessage方法

loop回圈中呼叫msg.target.dispatchMessage方法就是呼叫下面代碼,會進行訊息處理,如果有callback則優先呼叫對應的callback處理,沒有callback就呼叫handleMessage也就是平時會覆寫的這個方法,

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    public void handleMessage(Message msg) {
    	//開發者可覆寫的處理方法
    }

MessageQueue

MessageQueue是Message的載體,維護一個訊息佇列存盤訊息,這個訊息佇列是一個Message的單鏈表,

出隊next方法:

	Message next() {
        ...
        for (;;) {
        	...
            nativePollOnce(ptr, nextPollTimeoutMillis);//會阻塞等待
            ...
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
               ...
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
                ...
            }
            ...
        }
    }

這里同樣是開啟了一個for回圈死回圈,在for回圈中先呼叫nativePollOnce方法,這是一個native方法,如果此時有訊息過來,這里就會回傳繼續往下執行,找到符合觸發條件的訊息物件回傳,如果此時沒有訊息,那么會阻塞在這里等待并進入執行緒休眠狀態,

入隊enqueueMessage方法:

    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            if (mQuitting) {
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);//喚醒執行緒休眠
            }
        }
        return true;
    }

這里的關鍵是如果找到訊息,就呼叫nativeWake喚醒執行緒,那么在next方法中的nativePollOnce就會被喚醒恢復執行,取訊息進行分發,

下面這個圖可以形象的表達三者的關系:
在這里插入圖片描述
問題:為什么主執行緒的Looper的死回圈不會卡住CPU,或導致主執行緒被阻塞ANR?

首先在Looper.loop() 中死回圈不斷呼叫 MessageQueue#next():

MessageQueue#next() —> NativeMessageQueue#pollOnce —> NativeMessageQueue#epoll_wait

在底層會呼叫到epoll_wait方法去等待,這個方法只是讓當前執行緒休眠等待,并不是一直在那里瘋狂的跑,所以不會消耗CPU時間片,

然后,當發送新訊息時呼叫 MessageQueue#enqueueMessage() :

MessageQueue#enqueueMessage() —> NativeMessageQueue#wake —> NativeMessageQueue#write

在底層會呼叫到wake方法去喚醒等待的執行緒執行,

在這里插入圖片描述
在這里插入圖片描述

所以Looper死回圈不會導致CPU占用率高的原因是:
Looper中沒有訊息時,底層呼叫 epoll_wait 方法阻塞等待,不會消耗CPU時間片,而收到訊息時又會喚醒訊息佇列執行,主執行緒大多數時候都是處于休眠狀態,并不會消耗大量CPU資源,

也可以從另一個角度理解:我們所理解的UI主執行緒其實是 ActivityThread 類所在的執行緒,就是運行 main 函式的執行緒,ActivityThread 類本身并不是一個Thread類,只要ActivityThread 的Handler能正常接受處理訊息,界面就能處理繪制訊息,就不會卡住,

另外,關于ANR其實是AMS在啟動應用四大組件之前,先通過handler發送的一個延時訊息,當這個延時訊息的超時時間到,就會處理該訊息顯示一個應用無回應的系統彈窗,但是假如應用組件在這個超時時間內創建完畢并回傳,AMS就會從訊息佇列中移除這個延時訊息,所以ANR本身只是利用了handler手段實作的一種超時機制,跟looper的死回圈沒有太大關系,loop也不會導致ANR,

關于Handler的延時訊息

首先MessageQueue中存盤的訊息是一個按觸發時間排序的單鏈表,即按時間從小到大排序,那么按照佇列FIFO的原則,延時最小的訊息就會被優先取出執行,其次,Handler的延時訊息其實本質是延時處理,而不是延時發送,發送是立刻就加入了訊息佇列當中,底層會根據當前時間和msg的延時時間計算出需要等待的超時時間,超時時間到,才會喚醒阻塞等待的訊息佇列回圈處去繼續執行,

IdleHandler
在這里插入圖片描述

IdleHandler的作用是:當前執行緒Looper訊息佇列中的所有訊息處理完畢時回呼

所以,它十分適合處理這樣的場景:在UI繪制完畢后立馬去做一些事情,比如一些依賴于UI組件的初始化的作業,或者在UI主執行緒空閑的時候,做一些事情,比如說日志采集上報等等,

例如,通常我們在Activity的onCreate方法中嘗試獲取一個View控制元件的寬高時,得到的都是0,這是因為這時界面還在繪制,也就是主執行緒的Handler當中還有訊息在處理中,利用IdleHandler就可以解決這個問題,在onResume()里執行的正確姿勢:

@Override
protected void onResume() {
    super.onResume();
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
				// doSomething
                // 回傳false是一次性執行完就被移除,回傳true會反復執行
                return false;
            }
    });
} 

需要注意的地方:

  • Looper.myQueue()運行在哪個執行緒,queueIdle()就執行在哪個執行緒,如果是在主執行緒呼叫就是使用主執行緒的Looper,在子執行緒呼叫就是使用子執行緒的Looper,
  • queueIdle()方法在該執行緒的Looper所有訊息處理完畢時被回呼
  • 提供了一個合適并且正確的時機:在UI繪制完畢后去做一些事情

訊息屏障

Message訊息分為三種:普通訊息,屏障訊息和異步訊息,屏障訊息是通過MessageQueue類當中的兩個隱藏方法:postSyncBarrier()removeSyncBarrier()(只能反射呼叫)
在這里插入圖片描述

屏障訊息的主要特點是:發的訊息沒有設定msg.target屬性,也就是沒有所屬的Handler不需要進行分發,帶有時間戳,會阻塞訊息佇列中排在它后面的訊息(異步訊息除外),并且插入屏障訊息時不會喚醒執行緒,

例如系統的ViewRootImpl類當中就使用了屏障訊息: 在這里插入圖片描述
屏障訊息的主要目的是為了Block住它后面的普通訊息,為系統訊息(如界面繪制、事件分發)提供綠色通道,優先執行,

kotlin協程

協程的概念

協程本身并不是執行緒的概念,但是協程卻可以很方便的實作執行緒間的切換(通過掛起和恢復)

  • 對作業系統來說,執行緒是最小的執行單元,行程是最小的資源管理單元,行程管理著執行緒,
  • 對于執行緒而言,可以擁有多個協程,協程是一種比執行緒更加輕量級的存在,
  • 協程不是被作業系統內核所管理,而完全是由程式所控制(也就是用戶執行),
  • 協程的好處就是性能得到了很大的提升,不會像執行緒切換那樣消耗資源,
  • 協程的開銷遠遠小于執行緒的開銷,

在其他語言當中,協程已經得到廣泛應用,如Lua、 Python、 Go等,Java本身不支持協程,但是Kotlin做到了支持,可以理解為一種開發語言上的支持,但是它本身對作業系統是不可見的,

協程的目的是為了 讓多個任務之間更好的協作,解決異步回呼嵌套,能夠以同步的方式編排代碼完成異步作業,將異步代碼像同步代碼一樣直觀, 同時它也是一個 并發流程控制 的解決方案,

協程主要是讓原來要使用“異步+回呼”寫出來的復雜代碼, 簡化成看似同步寫出來的方式,榷訓了執行緒的概念(對執行緒的操作進一步抽象)

Kotlin應用場景1: 解決異步回呼嵌套
在這里插入圖片描述
普通寫法,回呼地獄:多層回呼嵌套

//客戶端順序進行三次網路異步請求,并用最終結果更新UI 
request1(parameter) { value1 -> 
	request2(value1) { value2 -> 
		request3(value2) { value3 -> 
			updateUI(value3) 
		} 
	} 
}

協程寫法:

GlobalScope.launch(Dispatchers.Main){ 
	val value1 = request1() 
	val value2 = request2(value1) 
	val value3 = request2(value2)
	updateUI(value3) 
} 

suspend request1( ) 
suspend request2(..) 
suspend request3(..)

Kotlin應用場景2:并發流程控制
在這里插入圖片描述

//客戶端順序并發三次網路異步請求,并用最終結果更新UI 
fun request1 (parameter) { value1 ->
      request2(value1) { value2 ->
          this.value2 = value2
          if (request3) {
              updateUI()
          }
      }
      request3(value2) { value3 -> 
          this.value3 = value3 
          if (request2) {
              updateUI()
          }
      } 
}

fun updateUI ()

協程寫法:

    GlobalScope.launch(Dispatchers.Main) {
        val value1 = request1()
        val deferred2 = GlobalScope.async { request2(value1) }
        val deferred3 = GlobalScope.async { request3(value2) }
        updateUI(deferred2.await(), deferred3.await())
    }

    suspend request1()
    suspend request2(..)
    suspend request3(..)

協程的用法

引入gradle依賴:

//在kotlin專案中配合jetpack架構引入協程 
api 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' 
api 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0' 
api 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' 

//在kotlin專案但非jetpack 架構專案中引入協程 
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1" 
api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1' 

常用的創建協程的方法:

//創建協程時,可以通過Dispatchers.IO,MAIN,Unconfined指定協程運行的執行緒 
val job:Job = GlobalScope.launch(Dispatchers.Main) 
val deffered:Deffered = GlobalScope.async(Dispatchers.IO)
  • Job:協程構建函式的回傳值,可以把 Job 看成協程物件本身,包含了對協程的控制方法,
  • Deffered:Job的子類,實際上就增加了個await方法,能夠讓當前協程暫時掛起,暫停往下執行,當await方法有回傳值后,會恢復協程,繼續往下執行
方法說明
start()手動啟動協程
join()等待協程執行完畢
cancel()取消一個協程

協程的啟動:

    public fun CoroutineScope.launch(
            context: CoroutineContext = EmptyCoroutineContext,
            start: CoroutineStart = CoroutineStart.DEFAULT,
            block: suspend CoroutineScope.() ->Unit
    ): Job {
        val newContext = newCoroutineContext(context)
        val coroutine = StandaloneCoroutine(newContext, active = true)
        coroutine.start(start, coroutine, block)
    }

CoroutineContext : 可以理解為協程的背景關系,是一種key-value資料結構

CoroutineContextList
get(key: Key): Eget(int index)
plus(context: Element)add(int index, E element)
minusKey(key: Key<*>)remove(E element)

CoroutineDispatcher: 協程運行的執行緒調度器
在這里插入圖片描述

模式說明
Dispatchers.IO顯示指定協程運行的執行緒,為IO執行緒
Dispatchers.Main指定這個協程運行在主執行緒
Dispatchers.Default默認的,啟動攜程時會啟動一個執行緒
Dispatchers.Unconfined不指定,就是在當前執行緒運行,協程恢復后的運行的執行緒取決于協程掛起時所在的執行緒

CoroutineStart : 啟動模式,默認是DEAFAULT,也就是創建就啟動;還有一個是LAZY,意思是等你需要它的時候,再呼叫啟動

模式說明
CoroutineStart().DEAFAULT默認模式,創建并即啟動協程體,調度前若取消則直接取消
ATOMIC自動模式,同樣創建并即啟動協程體,但啟動前不可取消
LAZY延遲啟動模式,只有當呼叫start/join/await方法時才會啟動
UNDISPATCHED立即在當前執行緒執行協程體,直到遇到第一個掛起點

協程掛起,恢復原理逆向剖析

掛起函式 :被關鍵字 suspend 修飾的方法,在編譯階段,編譯器會修改方法的簽名. 包括回傳值,修飾符,入參,方法體實作,協程的掛起是靠掛起函式中實作的代碼,

    suspend fun request(): String {
        delay(2 * 1000)//suspend fun()
        println("after delay")
        return "result from request"
    }

    public static final Object request(Continuation completion) {
        ContinuationImpl requestContinuation = completion;
        if ((completion.label & Integer.MIN_VALUE) == 0)
            requestContinuation = new ContinuationImpl(completion) {
                @Override
                Object invokeSuspend(Object o) {
                    label |= Integer.MIN_VALUE;
                    return request(this);
                }
            };
        }
        switch(requestContinuation.label) {
            case 0: {
                requestContinuation.label = 1;
                Object delay = DelayKt.delay(2000, requestContinuation);
                if (delay == COROUTINE_SUSPENDED) {
                    return COROUTINE_SUSPENDED;
                }
            }
        } 
        System.out.println("after delay")
        return"result from request";
    }

協程掛起與協程恢復協程的核心是掛起----恢復,掛起,恢復的本質是return & callback回呼
在這里插入圖片描述
協程應用

如何讓普通函式適配協程,成為“真正的掛起函式”,即讓呼叫方以同步的方式拿到異步任務回傳結果 ,

    //場景描述
    fun getConfigContent() {
        parseAssetsFile("config.json") { fileContent ->
            println(fileContent)
        }
    }

    //CoroutineScene4.kt
    fun parseAssetsFile(fileName:String, callback(String)->Unit) {
        Thread(Runnable {
            //readfile(filename)
            Thread.sleep(2000)
            callback("assets file content")
        }).start()
    }

方案1:

    lifecycleScope.launch {
        val fileContent = lifecycleScope.async { parseAssetsFile() }.await()
        //delay()
        println(fileContent)
    }

    suspend parseAssetsFile(fileName:String): String {
        return "assets file content"
    }

方案2:

    lifecycleScope.launch {
        val fileContent = parseAssetsFile()
        println(fileContent)
    }

    suspend parseAssetsFile(fileName: String): String {
        //suspendCoroutine
        return suspendCancellableCoroutine { continuation ->
                Thread {
                    //io ....
                    continuation.resumeWith(Result.success("assets file content"))
                }.start()
        }
    }

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/286418.html

標籤:其他

上一篇:Android與Flutter混合開發

下一篇:【移動安全技術】-Java呼叫C函式_JNI

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more