JetPack WorkManager 使用和分析

WorkManager是干啥的胆敞。

WorkManager is the recommended library for persistent work. Scheduled work is guaranteed to execute sometime after its [Constraints](https://developer.android.google.cn/reference/androidx/work/Constraints) are met. WorkManager allows observation of work status and the ability to create complex chains of work.
從上面可以知道:workmanager是一個(gè)google 推薦的library 用來(lái)執(zhí)行work的卧波,他有一些特點(diǎn)

  • persistent 穩(wěn)定(開機(jī)之后 條件滿足后自動(dòng)執(zhí)行)
  • Constraints 有約束條件
  • observation of work status 狀態(tài)可以被監(jiān)聽
  • create complex chains of work 可以合并鏈?zhǔn)降慕M合工作

WorkManager 的使用

使用從 添加依賴跪削、任務(wù)創(chuàng)建和開啟育谬、組合任務(wù)、任務(wù)監(jiān)聽和任務(wù)取消介紹任務(wù)的使用纳胧。

  • 添加依賴
    app 的build.gradle 添加依賴
    def work_version = "2.7.1"
    // (Java only)
    implementation "androidx.work:work-runtime:$work_version"
  • 普通任務(wù)創(chuàng)建和 開啟
    1 創(chuàng)建任務(wù) 創(chuàng)建一個(gè)class CWorker 繼承與Worker,任務(wù)都是在 doWork里面執(zhí)行挂捅,return 可以是Result.success Result.failer,Result.retry.
public class CWork extends Worker {
    public CWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }
    @NonNull
    @Override
    public Result doWork() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return Result.success();
    }
}

2 開啟任務(wù)
開啟任務(wù)需要生成一個(gè)WorkRequest ,然后workmanager執(zhí)行enqueue就開始執(zhí)行了护盈。

//   普通任務(wù) 開啟
    private void playCommon(){
         // 1 生成 WorkRequest
        OneTimeWorkRequest aWorkRequest = new OneTimeWorkRequest.Builder(AWork.class).build();
        //  2 workManager enqueue
        WorkManager.getInstance(this).enqueue(aWorkRequest);
    }
  • 延時(shí)任務(wù)
    workrequest 添加initialDelay參數(shù)就會(huì)延時(shí)執(zhí)行
   private void playDelay(){
        // 參數(shù)1 是時(shí)長(zhǎng) 參數(shù)2 是單位
        OneTimeWorkRequest aWorkRequest = new OneTimeWorkRequest.Builder(AWork.class)
                .setInitialDelay(1000, TimeUnit.MILLISECONDS) // 延時(shí)時(shí)間執(zhí)行
                .build();
        WorkManager.getInstance(this).enqueue(aWorkRequest);
    }
  • 條件任務(wù)
    任務(wù)可以添加約束條件挟纱,例如網(wǎng)絡(luò)約束(無(wú)網(wǎng)絡(luò),聯(lián)網(wǎng)腐宋,wifi紊服,計(jì)費(fèi)流量等)、保持電量充足的時(shí)候執(zhí)行胸竞、在充電的時(shí)候執(zhí)行欺嗤、storage不足的時(shí)候不執(zhí)行、待機(jī)下執(zhí)行等約束條件撤师。
    不滿足條件的時(shí)候work 就會(huì)被block剂府,滿足條件就會(huì)執(zhí)行。
private void playConstraints(){
       @SuppressLint("IdleBatteryChargingConstraints") Constraints constraints = new Constraints.Builder()
               .setRequiredNetworkType(NetworkType.NOT_REQUIRED)//設(shè)置網(wǎng)絡(luò)情況 例如 wifi 聯(lián)網(wǎng) 計(jì)費(fèi)流量等
               .setRequiresBatteryNotLow(true)//設(shè)置在電量不足的情況下不執(zhí)行
               .setRequiresCharging(false)//在充電時(shí)執(zhí)行
               .setRequiresStorageNotLow(true)//設(shè)置在storage不足的情況下不執(zhí)行
               .setRequiresDeviceIdle(false)//在待機(jī)情況下執(zhí)行  非待機(jī)下不執(zhí)行
               .build();
       OneTimeWorkRequest aWorkRequest = new OneTimeWorkRequest.Builder(AWork.class)
               .setConstraints(constraints) // 設(shè)置條件
               .build();
       WorkManager instance = WorkManager.getInstance(this);
       instance.enqueue(aWorkRequest);
   }
  • retry 任務(wù)
    retry 任務(wù)需要work 的dowork 方法返回值Result 的retry剃盾。才會(huì)再次執(zhí)行腺占,而且是當(dāng)retry的時(shí)候執(zhí)行了新的work,這個(gè)times 的值非static 的時(shí)候每次都是初始值。
public class RetryWork extends Worker {
    private String TAG = "RetryWork";
   public static int times = 0;
    public RetryWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }
    @SuppressLint("RestrictedApi")
    @NonNull
    @Override
    public Result doWork() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (times >= 2) {
            return Result.success();
        }else {
            times++;
            Log.d(TAG,"times :"+times);
            return Result.retry();
        }

    }
}

retry 的時(shí)候 setBackoffCriteria 可以設(shè)置retry 的時(shí)間和類型痒谴,
BackoffPolicy.LINEAR 是線性每次間隔時(shí)間一樣衰伯,BackoffPolicy.EXPONENTIAL 每次間隔時(shí)間會(huì)遞增。

    @RequiresApi(api = Build.VERSION_CODES.O)
    private void playRetry(){
        // 1 work return  Result.retry()
        OneTimeWorkRequest aWorkRequest = new OneTimeWorkRequest.Builder(RetryWork.class)
                .setBackoffCriteria(BackoffPolicy.LINEAR, Duration.ofSeconds(10)) //   重試設(shè)置10s后重試 線性間隔            
//                .setBackoffCriteria(BackoffPolicy.EXPONENTIAL,Duration.ofSeconds(10))// 重試設(shè)置10s后重試 線性間隔

                .build();
        WorkManager instance = WorkManager.getInstance(this);
        instance.enqueue(aWorkRequest);
    }
  • 周期任務(wù)
    PeriodicWorkRequest 和 OnetimeWorkRequest 類型相反积蔚,是周期性任務(wù)意鲸。
    周期任務(wù)區(qū)分為 設(shè)置 flex 時(shí)間和 不設(shè)置flex 時(shí)間兩種。
    1 不設(shè)置flex時(shí)間 每次間隔這段時(shí)間執(zhí)行一次
    三參數(shù) (AWork.class,30,TimeUnit.MINUTES)
    參數(shù)1 work
    參數(shù)2 時(shí)間間隔
    參數(shù)3 時(shí)間單位
    2 設(shè)置flex時(shí)間(AWork.class,30,TimeUnit.MINUTES,15,TimeUnit.MINUTES)
    第四個(gè)參數(shù)和第五個(gè)參數(shù)設(shè)置flex時(shí)間
    flex時(shí)間 需要少于 間隔時(shí)間。
    [ before flex | flex ][ before flex | flex ]...
    間隔時(shí)間被分為 before flex 和flex 時(shí)間段怎顾,任務(wù)的執(zhí)行在flex時(shí)間段里面读慎。
 // 周期任務(wù)
    private void playPeriod(){
        //
//        PeriodicWorkRequest aWorkRequest = new PeriodicWorkRequest.Builder(AWork.class,30,TimeUnit.MINUTES)
//                .build();
//                    [     before flex     |     flex     ][     before flex     |     flex     ]...
//           [   cannot run work   | can run work ][   cannot run work   | can run work ]...
        PeriodicWorkRequest aWorkRequest = new PeriodicWorkRequest.Builder(AWork.class,30,TimeUnit.MINUTES,15,TimeUnit.MINUTES)
                .build();
        WorkManager instance = WorkManager.getInstance(this);
        instance.enqueue(aWorkRequest);
    }
  • 任務(wù)添加標(biāo)記
    任務(wù)添加標(biāo)記,此標(biāo)記用來(lái)搜索標(biāo)記取消等可以使用槐雾。
  private void playTag(){
        // 參數(shù)1 是時(shí)長(zhǎng) 參數(shù)2 是單位
        OneTimeWorkRequest aWorkRequest = new OneTimeWorkRequest.Builder(AWork.class)
                .addTag("tag") // 標(biāo)記 主要用來(lái)查詢
                .build();
        WorkManager.getInstance(this).enqueue(aWorkRequest);
    }
  • 任務(wù)參數(shù)
    1 給任務(wù)傳參數(shù)
    setInputData 給任務(wù)傳參數(shù)
    work 里面通過 getInputData 獲取參數(shù)
    2 從任務(wù)傳出結(jié)果帶參數(shù)
    Result.success(data);或者 Result.failer(data) 可以設(shè)置結(jié)果夭委。
   @NonNull
    @Override
    public Result doWork() {
        Data inputData = getInputData();
        String param = inputData.getString("param1");
        Log.d(TAG,"doWork start param:"+param);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Data.Builder builder = new Data.Builder();
        @SuppressLint("RestrictedApi")
        Data data = builder.put("key", "work A").build();
        Log.d(TAG,"doWork end");
        return Result.success(data);
    }
 //  傳參
    private void playParam(){
        // 1 傳入 work  2 work 傳出結(jié)果
        Data data = new Data.Builder()
                .putString("param","input")
                .build();
        OneTimeWorkRequest aWorkRequest = new OneTimeWorkRequest.Builder(AWork.class)
                .setInputData(data) // 傳遞的數(shù)據(jù)
                .build();
        WorkManager instance = WorkManager.getInstance(this);
        instance.enqueue(aWorkRequest);
        instance.getWorkInfoByIdLiveData(aWorkRequest.getId()).observe(this, new Observer<WorkInfo>() {
            @Override
            public void onChanged(WorkInfo workInfo) {
                workInfo.getOutputData();
            }
        });
    }
  • 任務(wù)進(jìn)度設(shè)置
    work 里面設(shè)置進(jìn)度 setProgressAsync 設(shè)置
 @NonNull
    @Override
    public Result doWork() {
        setProgressAsync(new Data.Builder().putInt(PROGRESS, 0).build());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        setProgressAsync(new Data.Builder().putInt(PROGRESS, 100).build());
        Log.d(TAG,"doWork end");
        return Result.success();
    }

LiveData 監(jiān)聽里面獲取到進(jìn)度

//set play progress
    private void setPlayProgress(){
  ···
instance.getWorkInfoByIdLiveData(aWorkRequest.getId()).observe(this, new Observer<WorkInfo>() {
            @Override
            public void onChanged(WorkInfo workInfo) {
                Data progress = workInfo.getProgress();// 進(jìn)度
            }
        });
    }
  • unique 任務(wù)
    特殊任務(wù),同樣的名字的特殊任務(wù)會(huì)有沖突募强,可以設(shè)置沖突的解決方式株灸。
    ExistingWorkPolicy (REPLACE,KEEP擎值,APPEND慌烧,APPEND_OR_REPLACE)
    private void playUnique(){
        WorkManager instance = WorkManager.getInstance(this);
        OneTimeWorkRequest aWorkRequest = new OneTimeWorkRequest.Builder(AWork.class)
                .build();
//        ExistingWorkPolicy
//                ?REPLACE: 用新工作替換現(xiàn)有工作。此選項(xiàng)將取消現(xiàn)有工作鸠儿。
//                ?KEEP: 保留現(xiàn)有工作屹蚊,并忽略新工作。
//                ?APPEND: 將新工作附加到現(xiàn)有工作的末尾捆交。此選項(xiàng)會(huì)將新工作鏈接到現(xiàn)有工作淑翼,并在現(xiàn)有工作完成后運(yùn)行。并且現(xiàn)有工作將成為新工作的先決條件品追。如果現(xiàn)有工作為CANCELLED或者FAILED狀態(tài)玄括,新工作也會(huì)變?yōu)镃ANCELLED或者FAILED狀態(tài)。
//                ?APPEND_OR_REPLACE: 此選項(xiàng)類似于APPEND肉瓦,不過它不依賴于現(xiàn)有工作的狀態(tài)遭京。即使現(xiàn)有工作為CANCELLED或者FAILED狀態(tài),新工作仍舊會(huì)運(yùn)行泞莉。
        WorkContinuation uniqye_a = instance.beginUniqueWork("uniqye_A", ExistingWorkPolicy.APPEND, aWorkRequest);
        uniqye_a.enqueue();
    }
組合任務(wù)

1 通過 workmanager 的方法 beginWith (works).then(works)進(jìn)行哪雕,里面的works 可以是單參數(shù)和list。

/**
     * 2 合并A和B 然后執(zhí)行C
     */
    private void multipleWork(){
        OneTimeWorkRequest aWork = new OneTimeWorkRequest.Builder(AWork.class)
                .addTag("mul") // 設(shè)置 TAG//                .setExpedited()
                .build();
        OneTimeWorkRequest BWork = new OneTimeWorkRequest.Builder(BWork.class)
                .addTag("mul") // 設(shè)置 TAG//                .setExpedited()
                .build();
        OneTimeWorkRequest CWork = new OneTimeWorkRequest.Builder(CWork.class)
                .addTag("mul") // 設(shè)置 TAG//                .setExpedited()
                .build();
        WorkManager instance = WorkManager.getInstance(this);
        instance.cancelAllWork();
        instance.beginWith(Arrays.asList(aWork,BWork)).then(CWork).enqueue();
        instance.getWorkInfosByTagLiveData("mul").observe(this, new Observer<List<WorkInfo>>() {
            @Override
            public void onChanged(List<WorkInfo> workInfos) {
                for (WorkInfo workInfo : workInfos) {
                    Log.d(TAG,"onChanged workInfo:"+workInfo);
                }
            }
        });
    }

2 組合任務(wù)的組合
通過 WorkContinuation 復(fù)雜組合任務(wù)

 /**
     * 2 任務(wù)1  合并A和B 然后執(zhí)行C
     *   任務(wù)2  合并D和E 然后執(zhí)行F
     *   任務(wù)1 和2 同時(shí)
     */
    @SuppressLint("EnqueueWork")
    private void multipleWork2(){
        OneTimeWorkRequest aWork = new OneTimeWorkRequest.Builder(AWork.class)
                .addTag("mul") // 設(shè)置 TAG//                .setExpedited()
                .build();
        OneTimeWorkRequest BWork = new OneTimeWorkRequest.Builder(BWork.class)
                .addTag("mul") // 設(shè)置 TAG//                .setExpedited()
                .build();
        OneTimeWorkRequest CWork = new OneTimeWorkRequest.Builder(CWork.class)
                .addTag("mul") // 設(shè)置 TAG//                .setExpedited()
                .build();
        OneTimeWorkRequest dWork = new OneTimeWorkRequest.Builder(AWork.class)
                .addTag("mul") // 設(shè)置 TAG//                .setExpedited()
                .build();
        OneTimeWorkRequest eWork = new OneTimeWorkRequest.Builder(BWork.class)
                .addTag("mul") // 設(shè)置 TAG//                .setExpedited()
                .build();
        OneTimeWorkRequest fWork = new OneTimeWorkRequest.Builder(CWork.class)
                .addTag("mul") // 設(shè)置 TAG//                .setExpedited()
                .build();
        WorkManager instance = WorkManager.getInstance(this);
        instance.cancelAllWork();
        WorkContinuation work1 = instance.beginWith(Arrays.asList(aWork, BWork)).then(CWork);
        WorkContinuation work2 = instance.beginWith(Arrays.asList(eWork, dWork)).then(fWork);
        WorkContinuation.combine(Arrays.asList(work1,work2)).enqueue();
    }
搜索任務(wù)

通過 id,tag鲫趁,uniquename,斯嚎,searchRequest (組合搜索 id,tag,state,uniquename)

private void searchWork(){
        // 1  id
        WorkManager instance = WorkManager.getInstance(this);
        ListenableFuture<WorkInfo> workInfoById = instance.getWorkInfoById();
        WorkInfo workInfo= workInfoById.get();
        // 2  通過tag
        instance.getWorkInfosByTag("");
         // 3 通過uniqueName
        instance.getWorkInfosForUniqueWork("");
        // 4 通過組合條件
        WorkQuery.Builder query = WorkQuery.Builder.fromIds(Arrays.asList(UUID.fromString("ss")));
        WorkQuery.Builder.fromTags(Arrays.asList("tag1","tag2","tag3"));
        WorkQuery.Builder.fromStates(Arrays.asList(WorkInfo.State.BLOCKED,WorkInfo.State.FAILED));
        WorkQuery.Builder.fromUniqueWorkNames(Arrays.asList("name1","name2"));
        // 上面是 單獨(dú)某一個(gè)類型去查詢
        // 下面是根據(jù) && 類型去添加
//        query.addIds();
//        query.addTags();
//        query.addStates();
//        query.addUniqueWorkNames();

        instance.getWorkInfos(query.build());
    }

WorkQuery搜索是通過搜索數(shù)據(jù)庫(kù)搜索的
query.addIds addTags addStates 等方法都是&& 條件搜索的。

public static SupportSQLiteQuery workQueryToRawQuery(@NonNull WorkQuery querySpec) {
        List<Object> arguments = new ArrayList<>();
        StringBuilder builder = new StringBuilder("SELECT * FROM workspec");
        String conjunction = " WHERE";

        List<WorkInfo.State> states = querySpec.getStates();
        if (!states.isEmpty()) {
            List<Integer> stateIds = new ArrayList<>(states.size());
            for (WorkInfo.State state : states) {
                stateIds.add(WorkTypeConverters.stateToInt(state));
            }
            builder.append(conjunction)
                    .append(" state IN (");
            bindings(builder, stateIds.size());
            builder.append(")");
            arguments.addAll(stateIds);
            conjunction = " AND";
        }

        List<UUID> ids = querySpec.getIds();
        if (!ids.isEmpty()) {
            List<String> workSpecIds = new ArrayList<>(ids.size());
            for (UUID id : ids) {
                workSpecIds.add(id.toString());
            }
            builder.append(conjunction)
                    .append(" id IN (");
            bindings(builder, ids.size());
            builder.append(")");
            arguments.addAll(workSpecIds);
            conjunction = " AND";
        }

        List<String> tags = querySpec.getTags();
        if (!tags.isEmpty()) {
            builder.append(conjunction)
                    .append(" id IN (SELECT work_spec_id FROM worktag WHERE tag IN (");
            bindings(builder, tags.size());
            builder.append("))");
            arguments.addAll(tags);
            conjunction = " AND";
        }

        List<String> uniqueWorkNames = querySpec.getUniqueWorkNames();
        if (!uniqueWorkNames.isEmpty()) {
            builder.append(conjunction)
                    .append(" id IN (SELECT work_spec_id FROM workname WHERE name IN (");
            bindings(builder, uniqueWorkNames.size());
            builder.append("))");
            arguments.addAll(uniqueWorkNames);
            conjunction = " AND";
        }
        builder.append(";");
        return new SimpleSQLiteQuery(builder.toString(), arguments.toArray());
    }
監(jiān)聽任務(wù)

添加搜索的任務(wù)

 //
    private void addListener(){
        WorkManager instance = WorkManager.getInstance(this);
        // 通過查詢 liveData 然后 observer
        instance.getWorkInfoByIdLiveData("").observe(this, new Observer<WorkInfo>() {
            @Override
            public void onChanged(WorkInfo workInfo) {

            }
        });
        instance.getWorkInfosByTagLiveData("tag").observe(this, new Observer<List<WorkInfo>>() {
            @Override
            public void onChanged(List<WorkInfo> workInfos) {

            }
        });
    }
取消任務(wù)

根據(jù) id tag uniquename 等取消

 // cancel
    private void cancelWork(){
        WorkManager instance = WorkManager.getInstance(this);
        // 通過查詢 liveData 然后 observer
        instance.cancelAllWork();
        instance.cancelAllWorkByTag();
        instance.cancelWorkById();
        instance.cancelUniqueWork();
    }

工作原理

WorkManager 任務(wù)的穩(wěn)定性和條件滿足的 原理兩方面去分析

開機(jī)啟動(dòng)自啟動(dòng)任務(wù)

bootComplate 廣播接收

 <receiver
            android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
           ···>
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <action android:name="android.intent.action.TIME_SET" />
                <action android:name="android.intent.action.TIMEZONE_CHANGED" />
            </intent-filter>
        </receiver>


public class RescheduleReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
    
        if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
            try {
                WorkManagerImpl workManager = WorkManagerImpl.getInstance(context);
                final PendingResult pendingResult = goAsync();
                workManager.setReschedulePendingResult(pendingResult);
            } catch (IllegalStateException e) {
             
            }
        } else {
            Intent reschedule = CommandHandler.createRescheduleIntent(context);
            context.startService(reschedule);
        }
    }
}


WorkManagerImpl 里面 初始化

public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
        synchronized (sLock) {
            WorkManagerImpl instance = getInstance();
            if (instance == null) {
                Context appContext = context.getApplicationContext();
                if (appContext instanceof Configuration.Provider) {
                    initialize(
                            appContext,
                            ((Configuration.Provider) appContext).getWorkManagerConfiguration());
                    instance = getInstance(appContext);
                } else {
                    
                }
            }

            return instance;
        }
    }

public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
        ···

            if (sDelegatedInstance == null) {
                context = context.getApplicationContext();
                if (sDefaultInstance == null) { // 初始化
                    sDefaultInstance = new WorkManagerImpl(
                            context,
                            configuration,
                            new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
                }
                sDelegatedInstance = sDefaultInstance;
            }
        }
    }

 public WorkManagerImpl(
            @NonNull Context context,
            @NonNull Configuration configuration,
            @NonNull TaskExecutor workTaskExecutor,
            @NonNull WorkDatabase database) {
       ···
        List<Scheduler> schedulers =
                createSchedulers(applicationContext, configuration, workTaskExecutor);
        Processor processor = new Processor(
                context,
                configuration,
                workTaskExecutor,
                database,
                schedulers);
        internalInit(context, configuration, workTaskExecutor, database, schedulers, processor); // 初始化
    }

private void internalInit(···) {
···
        // Checks for app force stops.
        mWorkTaskExecutor.executeOnBackgroundThread(new ForceStopRunnable(context, this));
    }

調(diào)用到 ForceStopRunnable run

 @Override
    public void run() {
        try {
            if (!multiProcessChecks()) {
                return;
            }
            while (true) {
             ···
                try {
                    forceStopRunnable();
                    break;
                } catch (Exception exception) {
                    mRetryCount++;
                  ···
            }
        } finally {
            mWorkManager.onForceStopRunnableCompleted();
        }
    }

@VisibleForTesting
    public void forceStopRunnable() {
        boolean needsScheduling = cleanUp();
        if (shouldRescheduleWorkers()) {
            Logger.get().debug(TAG, "Rescheduling Workers.");
            mWorkManager.rescheduleEligibleWork();
            // Mark the jobs as migrated.
            mWorkManager.getPreferenceUtils().setNeedsReschedule(false);
        } else if (isForceStopped()) {
            Logger.get().debug(TAG, "Application was force-stopped, rescheduling.");
            mWorkManager.rescheduleEligibleWork();
        } else if (needsScheduling) {
            Logger.get().debug(TAG, "Found unfinished work, scheduling it.");   // 調(diào)用到這里 
            Schedulers.schedule(
                    mWorkManager.getConfiguration(),
                    mWorkManager.getWorkDatabase(),
                    mWorkManager.getSchedulers());
        }
    }

Schedulers 的schedule 這里


    public static void schedule(
            @NonNull Configuration configuration,
            @NonNull WorkDatabase workDatabase,
            List<Scheduler> schedulers) {
      ···

        WorkSpecDao workSpecDao = workDatabase.workSpecDao();
        List<WorkSpec> eligibleWorkSpecsForLimitedSlots;
        List<WorkSpec> allEligibleWorkSpecs;

        workDatabase.beginTransaction();
        try {
            // Enqueued workSpecs when scheduling limits are applicable.
            eligibleWorkSpecsForLimitedSlots = workSpecDao.getEligibleWorkForScheduling(
                    configuration.getMaxSchedulerLimit());

            // Enqueued workSpecs when scheduling limits are NOT applicable.
            allEligibleWorkSpecs = workSpecDao.getAllEligibleWorkSpecsForScheduling(
                    MAX_GREEDY_SCHEDULER_LIMIT);

            if (eligibleWorkSpecsForLimitedSlots != null
                    && eligibleWorkSpecsForLimitedSlots.size() > 0) {
                long now = System.currentTimeMillis();

                for (WorkSpec workSpec : eligibleWorkSpecsForLimitedSlots) {
                    workSpecDao.markWorkSpecScheduled(workSpec.id, now);
                }
            }
            workDatabase.setTransactionSuccessful();
        } finally {
            workDatabase.endTransaction();
        }

        if (eligibleWorkSpecsForLimitedSlots != null
                && eligibleWorkSpecsForLimitedSlots.size() > 0) {

            WorkSpec[] eligibleWorkSpecsArray =
                    new WorkSpec[eligibleWorkSpecsForLimitedSlots.size()];
            eligibleWorkSpecsArray =
                    eligibleWorkSpecsForLimitedSlots.toArray(eligibleWorkSpecsArray);

            // Delegate to the underlying schedulers.
            for (Scheduler scheduler : schedulers) {
                if (scheduler.hasLimitedSchedulingSlots()) {  // SystemJobScheduler 
                    scheduler.schedule(eligibleWorkSpecsArray);
                }
            }
        }

        if (allEligibleWorkSpecs != null && allEligibleWorkSpecs.size() > 0) {
            WorkSpec[] enqueuedWorkSpecsArray = new WorkSpec[allEligibleWorkSpecs.size()];
            enqueuedWorkSpecsArray = allEligibleWorkSpecs.toArray(enqueuedWorkSpecsArray);
            // Delegate to the underlying schedulers.
            for (Scheduler scheduler : schedulers) {
                if (!scheduler.hasLimitedSchedulingSlots()) {  // 這里調(diào)用的時(shí)GreedyScheduler 的schedule 方法 GreedyScheduler 是在 WoekManager 的createSchedulers 添加的
                    scheduler.schedule(enqueuedWorkSpecsArray);
                }
            }
        }
    }

eligibleWorkSpecsForLimitedSlots 列表是有限制的 或者周期性的worklist沒執(zhí)行的list挨厚。
他會(huì)進(jìn)入到 SystemJobScheduler 里面去執(zhí)行 堡僻。

schedule{
scheduleInternal(workSpec, jobId);
}
 //mJobScheduler  (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE),
scheduleInternal{
int result = mJobScheduler.schedule(jobInfo);
}

到條件后會(huì)調(diào)用到 SystemJobService 的 onStartJob

@Override
    public boolean onStartJob(@NonNull JobParameters params) {
       ···

            // We don't need to worry about the case where JobParams#isOverrideDeadlineExpired()
            // returns true. This is because JobScheduler ensures that for PeriodicWork, constraints
            // are actually met irrespective.

            Logger.get().debug(TAG, String.format("onStartJob for %s", workSpecId));
            mJobParameters.put(workSpecId, params);
        }

        WorkerParameters.RuntimeExtras runtimeExtras = null;
        if (Build.VERSION.SDK_INT >= 24) {
            runtimeExtras = new WorkerParameters.RuntimeExtras();
            if (params.getTriggeredContentUris() != null) {
                runtimeExtras.triggeredContentUris =
                        Arrays.asList(params.getTriggeredContentUris());
            }
            if (params.getTriggeredContentAuthorities() != null) {
                runtimeExtras.triggeredContentAuthorities =
                        Arrays.asList(params.getTriggeredContentAuthorities());
            }
            if (Build.VERSION.SDK_INT >= 28) {
                runtimeExtras.network = params.getNetwork();
            }
        }

        // It is important that we return true, and hang on this onStartJob() request.
        // The call to startWork() may no-op because the WorkRequest could have been picked up
        // by the GreedyScheduler, and was already being executed. GreedyScheduler does not
        // handle retries, and the Processor notifies all Schedulers about an intent to reschedule.
        // In such cases, we rely on SystemJobService to ask for a reschedule by calling
        // jobFinished(params, true) in onExecuted(...);
        // For more information look at b/123211993
        mWorkManagerImpl.startWork(workSpecId, runtimeExtras);
        return true;
    }

GreedyScheduler schedule 方法 沒有延時(shí) 沒有限制條件的直接 startwork 。 帶延時(shí)直接延時(shí)處理疫剃。有限制條件的放到 限制條件的列表里面钉疫,后面條件任務(wù)里面會(huì)說(shuō)明。

 @Override
    public void schedule(@NonNull WorkSpec... workSpecs) {
        if (mInDefaultProcess == null) {
            checkDefaultProcess();
        }

        if (!mInDefaultProcess) {
            Logger.get().info(TAG, "Ignoring schedule request in a secondary process");
            return;
        }

        registerExecutionListenerIfNeeded();

        // Keep track of the list of new WorkSpecs whose constraints need to be tracked.
        // Add them to the known list of constrained WorkSpecs and call replace() on
        // WorkConstraintsTracker. That way we only need to synchronize on the part where we
        // are updating mConstrainedWorkSpecs.
        Set<WorkSpec> constrainedWorkSpecs = new HashSet<>();
        Set<String> constrainedWorkSpecIds = new HashSet<>();

        for (WorkSpec workSpec : workSpecs) {
            long nextRunTime = workSpec.calculateNextRunTime();
            long now = System.currentTimeMillis();
            if (workSpec.state == WorkInfo.State.ENQUEUED) {
                if (now < nextRunTime) {
                    // Future work
                    if (mDelayedWorkTracker != null) {
                        mDelayedWorkTracker.schedule(workSpec);
                    }
                } else if (workSpec.hasConstraints()) {
                    if (SDK_INT >= 23 && workSpec.constraints.requiresDeviceIdle()) {
                        // Ignore requests that have an idle mode constraint.
                        Logger.get().debug(TAG,
                                String.format("Ignoring WorkSpec %s, Requires device idle.",
                                        workSpec));
                    } else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
                        // Ignore requests that have content uri triggers.
                        Logger.get().debug(TAG,
                                String.format("Ignoring WorkSpec %s, Requires ContentUri triggers.",
                                        workSpec));
                    } else {
                        constrainedWorkSpecs.add(workSpec);
                        constrainedWorkSpecIds.add(workSpec.id);
                    }
                } else {
               
                    mWorkManagerImpl.startWork(workSpec.id);
                }
            }
        }

    
        synchronized (mLock) {
            if (!constrainedWorkSpecs.isEmpty()) {
              
                mConstrainedWorkSpecs.addAll(constrainedWorkSpecs);
                mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
            }
        }
    }
條件任務(wù)例如網(wǎng)絡(luò)低電量等條件任務(wù)

workmanager 的依賴?yán)锩?manifest 里面添加的有各種條件的廣播接收receiver巢价。

<receiver
            android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryChargingProxy"
        ···>
            <intent-filter>
                <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
                <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
            </intent-filter>
        </receiver>
        <receiver
            android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy"
           ··· >
            <intent-filter>
                <action android:name="android.intent.action.BATTERY_OKAY" />
                <action android:name="android.intent.action.BATTERY_LOW" />
            </intent-filter>
        </receiver>
        <receiver
            android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$StorageNotLowProxy"
            ··· >
            <intent-filter>
                <action android:name="android.intent.action.DEVICE_STORAGE_LOW" />
                <action android:name="android.intent.action.DEVICE_STORAGE_OK" />
            </intent-filter>
        </receiver>
        <receiver
            android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
            ··· >
            <intent-filter>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
        </receiver>

ConstraintProxy 接收

 @Override
    public void onReceive(Context context, Intent intent) {
        Logger.get().debug(TAG, String.format("onReceive : %s", intent));
        Intent constraintChangedIntent = CommandHandler.createConstraintsChangedIntent(context);
        context.startService(constraintChangedIntent);
    }

··· 
static Intent createConstraintsChangedIntent(@NonNull Context context) {
        Intent intent = new Intent(context, SystemAlarmService.class);
        intent.setAction(ACTION_CONSTRAINTS_CHANGED);
        return intent;
    }

SystemAlarmService 里面

 public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        if (mIsShutdown) {
            Logger.get().info(TAG,
                    "Re-initializing SystemAlarmDispatcher after a request to shut-down.");

            // Destroy the old dispatcher to complete it's lifecycle.
            mDispatcher.onDestroy();
            // Create a new dispatcher to setup a new lifecycle.
            initializeDispatcher(); // 初始化 dispatcher
            // Set mIsShutdown to false, to correctly accept new commands.
            mIsShutdown = false;
        }

        if (intent != null) {
            mDispatcher.add(intent, startId); / / 檢測(cè)
        }

        // If the service were to crash, we want all unacknowledged Intents to get redelivered.
        return Service.START_REDELIVER_INTENT;
    }

SystemAlarmDispatcher 里面


    public boolean add(@NonNull final Intent intent, final int startId) {
    ···
(CommandHandler.ACTION_CONSTRAINTS_CHANGED.equals(action)
                && hasIntentWithAction(CommandHandler.ACTION_CONSTRAINTS_CHANGED)) {
            return false;
        }

        intent.putExtra(KEY_START_ID, startId);
        synchronized (mIntents) {
            boolean hasCommands = !mIntents.isEmpty();
            mIntents.add(intent);
            if (!hasCommands) {
                // Only call processCommand if this is the first command.
                // The call to dequeueAndCheckForCompletion will process the remaining commands
                // in the order that they were added.
                processCommand();   // 檢測(cè)
            }
        }
        return true;
    }


 @MainThread
    @SuppressWarnings("FutureReturnValueIgnored")
    private void processCommand() {
        assertMainThread();
        PowerManager.WakeLock processCommandLock =
                WakeLocks.newWakeLock(mContext, PROCESS_COMMAND_TAG);
        try {
            processCommandLock.acquire();
            // Process commands on the background thread.
            mWorkManager.getWorkTaskExecutor().executeOnBackgroundThread(new Runnable() {
                @Override
                public void run() {
                    synchronized (mIntents) {
                        mCurrentIntent = mIntents.get(0);
                    }

                    if (mCurrentIntent != null) {
                        final String action = mCurrentIntent.getAction();
                        final int startId = mCurrentIntent.getIntExtra(KEY_START_ID,
                                DEFAULT_START_ID);
             ···
                        final PowerManager.WakeLock wakeLock = WakeLocks.newWakeLock(
                                mContext,
                                String.format("%s (%s)", action, startId));
                        try {
                          ···
                            wakeLock.acquire();
                            mCommandHandler.onHandleIntent(mCurrentIntent, startId,
                                    SystemAlarmDispatcher.this);  //  handler 處理message
                        } catch (Throwable throwable) {
                        ···
                        }  finally {
                      ···
                            wakeLock.release();
                        ···
                            postOnMainThread(
                                    new DequeueAndCheckForCompletion(SystemAlarmDispatcher.this));
                        }
                    }
                }
            });
        } finally {
            processCommandLock.release();
        }
    }

CommonHandler onHandleIntent 再調(diào)用到 ConstraintsCommandHandler 的 onHandleIntent

 @WorkerThread
    void onHandleIntent(
            @NonNull Intent intent,
            int startId,
            @NonNull SystemAlarmDispatcher dispatcher) {

        String action = intent.getAction();

        if (ACTION_CONSTRAINTS_CHANGED.equals(action)) { //條件變化  走到這里
            handleConstraintsChanged(intent, startId, dispatcher);
        } else if (ACTION_RESCHEDULE.equals(action)) {
            handleReschedule(intent, startId, dispatcher);
        } else {
            Bundle extras = intent.getExtras();
            if (!hasKeys(extras, KEY_WORKSPEC_ID)) {
                Logger.get().error(TAG,
                        String.format("Invalid request for %s, requires %s.",
                                action,
                                KEY_WORKSPEC_ID));
            } else {
                if (ACTION_SCHEDULE_WORK.equals(action)) {
                    handleScheduleWorkIntent(intent, startId, dispatcher);
                } else if (ACTION_DELAY_MET.equals(action)) {
                    handleDelayMet(intent, startId, dispatcher);
                } else if (ACTION_STOP_WORK.equals(action)) {
                    handleStopWork(intent, dispatcher);
                } else if (ACTION_EXECUTION_COMPLETED.equals(action)) {
                    handleExecutionCompleted(intent, startId);
                } else {
                    Logger.get().warning(TAG, String.format("Ignoring intent %s", intent));
                }
            }
        }
    }

ConstraintsCommandHandler 里面
 @WorkerThread
    void handleConstraintsChanged() {
        List<WorkSpec> candidates = mDispatcher.getWorkManager().getWorkDatabase()
                .workSpecDao()
                .getScheduledWork();
···
        ConstraintProxy.updateAll(mContext, candidates);

        // This needs to be done to populate matching WorkSpec ids in every constraint controller.
        mWorkConstraintsTracker.replace(candidates);

        List<WorkSpec> eligibleWorkSpecs = new ArrayList<>(candidates.size());
        // Filter candidates should have already been scheduled.
        long now = System.currentTimeMillis();
        for (WorkSpec workSpec : candidates) {
            String workSpecId = workSpec.id;
            long triggerAt = workSpec.calculateNextRunTime();
            if (now >= triggerAt && (!workSpec.hasConstraints()
                    || mWorkConstraintsTracker.areAllConstraintsMet(workSpecId))) {
                eligibleWorkSpecs.add(workSpec);
            }
        }

        for (WorkSpec workSpec : eligibleWorkSpecs) {
            String workSpecId = workSpec.id;
            Intent intent = CommandHandler.createDelayMetIntent(mContext, workSpecId);  // ACTION_DELAY_MET 
            Logger.get().debug(TAG, String.format(
                    "Creating a delay_met command for workSpec with id (%s)", workSpecId));
            mDispatcher.postOnMainThread(
                    new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId));  //  里面 mDispatcher 牲阁。add
        }

        mWorkConstraintsTracker.reset();
    }
}


//SystemAlarmDispatcher 里面
 AddRunnable(@NonNull SystemAlarmDispatcher dispatcher,
                @NonNull Intent intent,
                int startId) {
            mDispatcher = dispatcher;
            mIntent = intent;
            mStartId = startId;
        }

        @Override
        public void run() {
            mDispatcher.add(mIntent, mStartId);
        }


又回到 dispacher 里面了 mDispatcher.add(mIntent, mStartId);
然后又到 commonHandler 里面的 handleConstraintsChanged

                    handleDelayMet(intent, startId, dispatcher);
}

之后DelayMetCommonHandler 里面處理

@WorkerThread
    void handleProcessWork() {
        mWakeLock = WakeLocks.newWakeLock(
                mContext,
                String.format("%s (%s)", mWorkSpecId, mStartId));
        Logger.get().debug(TAG,
                String.format("Acquiring wakelock %s for WorkSpec %s", mWakeLock, mWorkSpecId));
        mWakeLock.acquire();

        WorkSpec workSpec = mDispatcher.getWorkManager()
                .getWorkDatabase()
                .workSpecDao()
                .getWorkSpec(mWorkSpecId);

        // This should typically never happen. Cancelling work should remove alarms, but if an
        // alarm has already fired, then fire a stop work request to remove the pending delay met
        // command handler.
        if (workSpec == null) {
            stopWork();
            return;
        }

        // Keep track of whether the WorkSpec had constraints. This is useful for updating the
        // state of constraint proxies when onExecuted().
        mHasConstraints = workSpec.hasConstraints();
        if (!mHasConstraints) { 
  onAllConstraintsMet(Collections.singletonList(mWorkSpecId)); // 沒有限制 直接就work
        } else {
            // Allow tracker to report constraint changes
            mWorkConstraintsTracker.replace(Collections.singletonList(workSpec));  //  判斷限制
        }
    }

WorkConstraintsTracker replace 方法

public void replace(@NonNull Iterable<WorkSpec> workSpecs) {
        synchronized (mLock) {
            for (ConstraintController<?> controller : mConstraintControllers) {
                controller.setCallback(null);
            }

            for (ConstraintController<?> controller : mConstraintControllers) {
                controller.replace(workSpecs);
            }

            for (ConstraintController<?> controller : mConstraintControllers) {
                controller.setCallback(this);
            }
        }
    }

ConstaintController


  public void setCallback(@Nullable OnConstraintUpdatedCallback callback) {
        if (mCallback != callback) {
            mCallback = callback;
            updateCallback(mCallback, mCurrentValue);
        }
    }
private void updateCallback(
            @Nullable OnConstraintUpdatedCallback callback,
            @Nullable T currentValue) {

        // We pass copies of references (callback, currentValue) to updateCallback because public
        // APIs on ConstraintController may be called from any thread, and onConstraintChanged() is
        // called from the main thread.
        if (mMatchingWorkSpecIds.isEmpty() || callback == null) {
            return;
        }
        if (currentValue == null || isConstrained(currentValue)) {
            callback.onConstraintNotMet(mMatchingWorkSpecIds); // 符合條件 callback 回去 
        } else {
            callback.onConstraintMet(mMatchingWorkSpecIds);
        }
    }

滿足條件后 會(huì)掉到 WorkConstraintsTracker 里面 遍歷獲取條件滿足的 work之后 然后在回調(diào)到 DelayMetCommonHandler 里面的 onAllConstraintsMet 里面

 //  WorkConstraintsTracker  
 @Override
    public void onConstraintMet(@NonNull List<String> workSpecIds) {
        synchronized (mLock) {
            List<String> unconstrainedWorkSpecIds = new ArrayList<>();
            for (String workSpecId : workSpecIds) {  // 遍歷尋找所有的滿足條件的work
                if (areAllConstraintsMet(workSpecId)) {
                    Logger.get().debug(TAG, String.format("Constraints met for %s", workSpecId));
                    unconstrainedWorkSpecIds.add(workSpecId);
                }
            }
            if (mCallback != null) {
                mCallback.onAllConstraintsMet(unconstrainedWorkSpecIds); // 鬼掉到commonHandler 里面 
            }
        }
    }

 // DelayMetCommonHandler  里面開啟工作
@Override
    public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
        // WorkConstraintsTracker will call onAllConstraintsMet with list of workSpecs whose
        // constraints are met. Ensure the workSpecId we are interested is part of the list
        // before we call Processor#startWork().
        if (!workSpecIds.contains(mWorkSpecId)) {
            return;
        }

        synchronized (mLock) {
            if (mCurrentState == STATE_INITIAL) {
                mCurrentState = STATE_START_REQUESTED;

                Logger.get().debug(TAG, String.format("onAllConstraintsMet for %s", mWorkSpecId));
                // Constraints met, schedule execution
                // Not using WorkManagerImpl#startWork() here because we need to know if the
                // processor actually enqueued the work here.
                boolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId);

                if (isEnqueued) {
                    // setup timers to enforce quotas on workers that have
                    // been enqueued
                    mDispatcher.getWorkTimer()
                            .startTimer(mWorkSpecId, WORK_PROCESSING_TIME_IN_MS, this);
                } else {
                    // if we did not actually enqueue the work, it was enqueued before
                    // cleanUp and pretend this never happened.
                    cleanUp();
                }
            } else {
                Logger.get().debug(TAG, String.format("Already started work for %s", mWorkSpecId));
            }
        }
    }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末固阁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子城菊,更是在濱河造成了極大的恐慌备燃,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凌唬,死亡現(xiàn)場(chǎng)離奇詭異赚爵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)法瑟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)唁奢,“玉大人霎挟,你說(shuō)我怎么就攤上這事÷榈В” “怎么了酥夭?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)脊奋。 經(jīng)常有香客問我熬北,道長(zhǎng),這世上最難降的妖魔是什么诚隙? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任讶隐,我火速辦了婚禮,結(jié)果婚禮上久又,老公的妹妹穿的比我還像新娘巫延。我一直安慰自己,他們只是感情好地消,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布炉峰。 她就那樣靜靜地躺著,像睡著了一般脉执。 火紅的嫁衣襯著肌膚如雪疼阔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天半夷,我揣著相機(jī)與錄音婆廊,去河邊找鬼。 笑死玻熙,一個(gè)胖子當(dāng)著我的面吹牛否彩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嗦随,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼列荔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼敬尺!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起贴浙,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤砂吞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后崎溃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜻直,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年袁串,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了概而。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡囱修,死狀恐怖赎瑰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情破镰,我是刑警寧澤餐曼,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站鲜漩,受9級(jí)特大地震影響源譬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜孕似,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一踩娘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧喉祭,春花似錦霸饲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至胶惰,卻和暖如春傻工,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背孵滞。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工中捆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坊饶。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓泄伪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親匿级。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蟋滴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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