Full Code of gdutxiaoxu/AnchorTask for AI

master 189b8956a0db cached
78 files
121.2 KB
34.1k tokens
13 symbols
1 requests
Download .txt
Repository: gdutxiaoxu/AnchorTask
Branch: master
Commit: 189b8956a0db
Files: 78
Total size: 121.2 KB

Directory structure:
gitextract_gkjzk3zl/

├── .gitignore
├── README.md
├── anchortasklibrary/
│   ├── .gitignore
│   ├── build.gradle
│   ├── consumer-rules.pro
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── xj/
│       │               └── anchortask/
│       │                   └── library/
│       │                       └── ExampleInstrumentedTest.kt
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── com/
│       │           └── xj/
│       │               └── anchortask/
│       │                   └── library/
│       │                       ├── AnchorConfig.kt
│       │                       ├── AnchorProject.kt
│       │                       ├── AnchorTaskException.kt
│       │                       ├── AnchorTaskRunnable.kt
│       │                       ├── AnchorTaskUtils.kt
│       │                       ├── IAnchorTask.kt
│       │                       ├── IAnchorTaskCreator.kt
│       │                       ├── ProjectBuilder.kt
│       │                       ├── ProjectListener.kt
│       │                       ├── TaskExecutorManager.kt
│       │                       ├── ThreadUtils.kt
│       │                       ├── log/
│       │                       │   └── LogUtils.kt
│       │                       └── monitor/
│       │                           ├── ExecuteMonitor.kt
│       │                           └── OnGetMonitorRecordCallback.kt
│       └── test/
│           └── java/
│               └── com/
│                   └── xj/
│                       └── anchortask/
│                           └── library/
│                               └── ExampleUnitTest.kt
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   ├── src/
│   │   ├── androidTest/
│   │   │   └── java/
│   │   │       └── com/
│   │   │           └── xj/
│   │   │               └── anchortask/
│   │   │                   └── ExampleInstrumentedTest.kt
│   │   ├── main/
│   │   │   ├── AndroidManifest.xml
│   │   │   ├── java/
│   │   │   │   └── com/
│   │   │   │       └── xj/
│   │   │   │           └── anchortask/
│   │   │   │               ├── LogUtils.kt
│   │   │   │               ├── LoggerPrinter.kt
│   │   │   │               ├── MainActivity.kt
│   │   │   │               ├── MyApplication.kt
│   │   │   │               ├── SPUtil.kt
│   │   │   │               ├── anchorTask/
│   │   │   │               │   ├── AnchorTaskTestActivity.kt
│   │   │   │               │   ├── ApplicationAnchorTaskCreator.kt
│   │   │   │               │   └── TestTaskUtils.kt
│   │   │   │               ├── appstartup/
│   │   │   │               │   └── InitializerSample.kt
│   │   │   │               ├── asyncInflate/
│   │   │   │               │   ├── AsyncActivity.kt
│   │   │   │               │   ├── AsyncInflateItem.kt
│   │   │   │               │   ├── AsyncInflateKey.kt
│   │   │   │               │   ├── AsyncInflateManager.kt
│   │   │   │               │   ├── ThreadUtils.java
│   │   │   │               │   └── page/
│   │   │   │               │       ├── AsyncFragment.kt
│   │   │   │               │       └── AsyncUtils.kt
│   │   │   │               ├── flowlayout/
│   │   │   │               │   ├── FlowLayoutDemo.kt
│   │   │   │               │   └── TagGroup.java
│   │   │   │               └── viewStub/
│   │   │   │                   ├── MyViewStub.kt
│   │   │   │                   ├── ViewStubDemoActivity.kt
│   │   │   │                   ├── ViewStubTask.kt
│   │   │   │                   └── ViewStubTaskManager.kt
│   │   │   └── res/
│   │   │       ├── drawable/
│   │   │       │   └── ic_launcher_background.xml
│   │   │       ├── drawable-v24/
│   │   │       │   └── ic_launcher_foreground.xml
│   │   │       ├── layout/
│   │   │       │   ├── activity_anchortask_test.xml
│   │   │       │   ├── activity_async.xml
│   │   │       │   ├── activity_flow_layout_demo.xml
│   │   │       │   ├── activity_main.xml
│   │   │       │   ├── activity_view_stub_demo.xml
│   │   │       │   ├── fragment_asny.xml
│   │   │       │   ├── layout_bottom.xml
│   │   │       │   ├── layout_content.xml
│   │   │       │   └── layout_title.xml
│   │   │       ├── mipmap-anydpi-v26/
│   │   │       │   ├── ic_launcher.xml
│   │   │       │   └── ic_launcher_round.xml
│   │   │       └── values/
│   │   │           ├── colors.xml
│   │   │           ├── strings.xml
│   │   │           └── styles.xml
│   │   └── test/
│   │       └── java/
│   │           └── com/
│   │               └── xj/
│   │                   └── anchortask/
│   │                       └── ExampleUnitTest.kt
│   └── test.gradle
├── build.gradle
├── config.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── localconfig.gradle
├── publish-mavencentral.gradle
└── settings.gradle

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx


================================================
FILE: README.md
================================================
> 我的 CSDN 博客:https://blog.csdn.net/gdutxiaoxu <br>
> 我的掘金:https://juejin.im/user/2207475076966584  <br>
> github: https://github.com/gdutxiaoxu/  <br>
> 微信公众号:程序员徐公  <br>



#  AnchorTask

锚点任务,可以用来解决多线程加载任务依赖的问题。实现原理是使用有向无环图,常见的,比如 Android 启动优化,通常会进行多线程异步加载。





# 基本使用

第一步:在 moulde build.gradle 配置远程依赖


```
implementation 'io.github.gdutxiaoxu:anchortask:1.1.0'
```

最新的版本号可以看这里 [lastedt version](https://github.com/gdutxiaoxu/AnchorTask/tags)

# 具体使用文档



## 0.1.0 版本

0.1.0 版本使用说明见这里 [AnchorTask 0.1.0 版本使用说明](https://github.com/gdutxiaoxu/AnchorTask/wiki/AnchorTask-0.1.0-%E7%89%88%E6%9C%AC%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E), 

0.1.0 版本实现借鉴了 [android-startup](https://github.com/idisfkj/android-startup),[AppStartFaster](https://github.com/NoEndToLF/AppStartFaster),[AnchorTask 0.1.0 原理
](https://github.com/gdutxiaoxu/AnchorTask/wiki/AnchorTask-0.1.0-%E5%8E%9F%E7%90%86)

##  1.0.0 版本

[AnchorTask 1.0.0 版本使用说明](https://github.com/gdutxiaoxu/AnchorTask/wiki/AnchorTask-1.0.0-%E7%89%88%E6%9C%AC%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E),参考了阿里 [alpha](https://github.com/alibaba/alpha)

[AnchorTask-1.0.0-原理说明](https://github.com/gdutxiaoxu/AnchorTask/wiki/AnchorTask-1.0.0-%E5%8E%9F%E7%90%86%E8%AF%B4%E6%98%8E)

## 两个版本之间区别


1. 之前的 0.1.0 版本 配置前置依赖任务,是通过 `AnchorTask getDependsTaskList` 的方式,这种方式不太直观,1.0.0 放弃了这种方式,参考阿里 `Alpha` 的方式,通过 `addTask(TASK_NAME_THREE).afterTask(TASK_NAME_ZERO, TASK_NAME_ONE)`
2. 1.0.0 版本新增了 Project 类,并增加 `OnProjectExecuteListener` 监听
3. 1.0.0 版本新增 `OnGetMonitorRecordCallback` 监听,方便统计各个任务的耗时


# 实现原理

AnchorTask 的原理不复杂,本质是有向无环图与多线程知识的结合。

1. 根据 BFS 构建出有向无环图,并得到它的拓扑排序
2.  在多线程执行过程中,我们是通过任务的子任务关系和 CounDownLatch 确保先后执行关系的
    1. 前置任务没有执行完毕的话,等待,执行完毕的话,往下走
    2. 执行任务
    3.  通知子任务,当前任务执行完毕了,相应的计数器(入度数)要减一。
    

[Android 启动优化(一) - 有向无环图
](https://juejin.cn/post/6926794003794903048)

[Android 启动优化(二) - 拓扑排序的原理以及解题思路](https://juejin.cn/post/6930805971673415694)



# 特别鸣谢

在实现这个开源框架的时候,借鉴了以下开源框架的思想。AppStartFaster 主要是通过 ClassName 找到相应的 Task,而阿里 alpha 是通过 taskName 找到相应的 Task,并且需要指定 ITaskCreator。两种方式各有优缺点,没有优劣之说,具体看使用场景。

[android-startup](https://github.com/idisfkj/android-startup)

[alpha](https://github.com/alibaba/alpha)

[AppStartFaster](https://github.com/NoEndToLF/AppStartFaster)

# 系列文章

这几篇文章从 0 到 1,讲解 DAG 有向无环图是怎么实现的,以及在 Android 启动优化的应用。

**推荐理由:现在挺多文章一谈到启动优化,动不动就聊拓扑结构,这篇文章从数据结构到算法、到设计都给大家说清楚了,开源项目也有非常强的借鉴意义。**

[Android 启动优化(一) - 有向无环图]( https://mp.weixin.qq.com/s/xWYe-uxgXTPuitYcLgXYNg)

[Android 启动优化(二) - 拓扑排序的原理以及解题思路]( https://mp.weixin.qq.com/s/ShfxD_Z7M_NuWYNodn-vqA)

[Android 启动优化(三)- AnchorTask 开源了]( https://mp.weixin.qq.com/s/YRUpf9jKEwIHV0A4FqltXg)

[Android 启动优化(四)- AnchorTask 是怎么实现的](https://mp.weixin.qq.com/s/6RKco9JTm6ZrFyw99k9Rlg)

[Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了]( https://mp.weixin.qq.com/s/0MsJa0ZepWkPUs-ymnVb-w)

[Android 启动优化(六)- 深入理解布局优化](https://mp.weixin.qq.com/s/7_dQd2wGZYKWf9kHNlv2fg)

**如果觉得对你有所帮助的,可以关注我的微信公众号,程序员徐公。主要更新 Android 技术,算法,职场相关的。**

![](https://raw.githubusercontent.com/gdutxiaoxu/blog_pic/master/21/0120210409172003.png)


================================================
FILE: anchortasklibrary/.gitignore
================================================
/build

================================================
FILE: anchortasklibrary/build.gradle
================================================
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'maven'
//apply plugin: 'com.novoda.bintray-release'
//apply plugin: 'com.github.panpf.bintray-publish'


// 发布到本地仓库配置
uploadArchives {
    repositories.mavenDeployer {
        // 配置本地仓库路径,项目根目录下的repository目录中
        repository(url: uri('../repository'))
        pom.groupId = "com.xj.android"// 唯一标识(通常为模块包名,也可以任意)
        pom.artifactId = "anchortask" // 项目名称(通常为类库模块名称,也可以任意)
        pom.version = "${anchorTaskPublicVersion}-local" // 版本号
    }
}

//
//publish {
//    userOrg = 'xujun94'
//    groupId = 'com.xj.android'
//    artifactId = 'anchortask'
//    publishVersion = "${anchorTaskPublicVersion}"
//    desc = 'anchortask'
//    website = 'https://github.com/gdutxiaoxu/anchortask'
//}


android {
    compileSdkVersion 30
    buildToolsVersion "29.0.3"

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        kotlinOptions.freeCompilerArgs += ['-module-name', "com.xj.android.anchortask"]
    }

}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

}


ext {
    PUBLISH_GROUP_ID = "com.xj.android"        //项目包名
    PUBLISH_ARTIFACT_ID = 'anchortask'            //项目名
    PUBLISH_VERSION = "${anchorTaskPublicVersion}"            //版本号
}
apply from: "${rootProject.projectDir}/publish-mavencentral.gradle"




================================================
FILE: anchortasklibrary/consumer-rules.pro
================================================


================================================
FILE: anchortasklibrary/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

================================================
FILE: anchortasklibrary/src/androidTest/java/com/xj/anchortask/library/ExampleInstrumentedTest.kt
================================================
package com.xj.anchortask.library

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
 * Instrumented test, which will execute on an Android device.
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.xj.anchortask.library.test", appContext.packageName)
    }
}

================================================
FILE: anchortasklibrary/src/main/AndroidManifest.xml
================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xj.anchortask.library">

    /
</manifest>

================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorConfig.kt
================================================
package com.xj.anchortask.library

import com.xj.anchortask.library.log.LogUtils

/**
 * Created by jun xu on 2/9/21.
 */
class AnchorConfig(
    logLevel: LogUtils.LogLevel = LogUtils.LogLevel.NONE,
    waringTime: Long = Long.MAX_VALUE,
    showToastToAlarm: Boolean = false
) {

}

================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorProject.kt
================================================
package com.xj.anchortask.library

import android.content.Context
import com.xj.anchortask.library.log.LogUtils
import com.xj.anchortask.library.monitor.ExecuteMonitor
import com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.CountDownLatch
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger

/**
 * Created by jun xu on 2/1/21.
 */
class AnchorProject private constructor(val builder: Builder) {

    init {
        LogUtils.init("AnchorTaskLibrary")
    }

    private val threadPoolExecutor: ThreadPoolExecutor =
        builder.threadPoolExecutor ?: TaskExecutorManager.instance.cpuThreadPoolExecutor

    // 存储所有的任务,key 是 taskName,value 是 AnchorTask
    private val taskMap: MutableMap<String, AnchorTask> = builder.taskMap

    // 储存当前任务的子任务, key 是当前任务的 taskName,value 是 AnchorTask 的 list
    private val taskChildMap: MutableMap<String, ArrayList<AnchorTask>?> = builder.taskChildMap

    //需要等待的任务总数,用于阻塞
    private val countDownLatch: CountDownLatch = builder.countDownLatch

    // 拓扑排序之后的主线程任务
    private val mainList = builder.mainList

    // 拓扑排序之后的子线程任务
    private val threadList = builder.threadList

    private val totalTaskSize = builder.list.size
    private val finishTask = AtomicInteger(0)

    private val listeners: CopyOnWriteArrayList<OnProjectExecuteListener> =
        CopyOnWriteArrayList()

    private var iAnchorTaskCreator: IAnchorTaskCreator? = null
    private var cacheTask: AnchorTask? = null
    private val executeMonitor: ExecuteMonitor =
        ExecuteMonitor()


    var onGetMonitorRecordCallback: OnGetMonitorRecordCallback? = null

    fun addListener(onProjectExecuteListener: OnProjectExecuteListener) {
        listeners.add(onProjectExecuteListener)
    }

    fun removeListener(onProjectExecuteListener: OnProjectExecuteListener) {
        listeners.remove(onProjectExecuteListener)
    }

    fun record(taskName: String, executeTime: Long) {
        executeMonitor.record(taskName, executeTime)
    }


    /**
     *  通知 child countdown,当前的阻塞任务书也需要 countdown
     */
    fun setNotifyChildren(anchorTask: AnchorTask) {
        taskChildMap[anchorTask.getTaskName()]?.forEach {
            taskMap[it.getTaskName()]?.countdown()
        }
        if (anchorTask.needWait()) {
            countDownLatch.countDown()
        }
        listeners.forEach {
            it.onTaskFinish(anchorTask.getTaskName())
        }
        finishTask.incrementAndGet()

        if (finishTask.get() == totalTaskSize) {
            executeMonitor.recordProjectFinish()
            ThreadUtils.runOnUiThread(Runnable {
                onGetMonitorRecordCallback?.onGetProjectExecuteTime(executeMonitor.projectCostTime)
                onGetMonitorRecordCallback?.onGetTaskExecuteRecord(executeMonitor.executeTimeMap)
            })

            listeners.forEach {
                it.onProjectFinish()
            }
        }
    }

    fun start(): AnchorProject {
        executeMonitor.recordProjectStart()
        this.listeners.forEach {
            it.onProjectStart()
        }

        this.threadList.forEach {
            threadPoolExecutor.execute(AnchorTaskRunnable(this, anchorTask = it))
        }

        this.mainList.forEach {
            AnchorTaskRunnable(this, anchorTask = it).run()
        }
        return this
    }

    fun await(timeOutMillion: Long = -1) {
        if (timeOutMillion > 0) {
            countDownLatch.await(timeOutMillion, TimeUnit.MILLISECONDS)
        } else {
            countDownLatch.await()
        }
    }


    class Builder constructor() {


        companion object {
            private const val TAG = "AnchorTaskDispatcher"
        }


        var threadPoolExecutor: ThreadPoolExecutor? = null
            private set

        private lateinit var context: Context
        private var logLevel: LogUtils.LogLevel = LogUtils.logLevel
        private var timeOutMillion: Long = -1

        // 存储所有的任务
        val list: MutableList<AnchorTask> = ArrayList()

        // 存储所有的任务,key 是 taskName,value 是 AnchorTask
        val taskMap: MutableMap<String, AnchorTask> = HashMap()

        // 储存当前任务的子任务, key 是当前任务的 taskName,value 是 AnchorTask 的 list
        val taskChildMap: MutableMap<String, ArrayList<AnchorTask>?> = HashMap()

        // 拓扑排序之后的主线程任务
        val mainList: MutableList<AnchorTask> = ArrayList()

        // 拓扑排序之后的子线程任务
        val threadList: MutableList<AnchorTask> = ArrayList()

        //需要等待的任务总数,用于阻塞
        lateinit var countDownLatch: CountDownLatch

        //需要等待的任务总数,用于CountDownLatch
        private val needWaitCount: AtomicInteger = AtomicInteger()

        private var startTime = -1L

        private val listeners: CopyOnWriteArrayList<OnProjectExecuteListener> =
            CopyOnWriteArrayList()

        private var iAnchorTaskCreator: IAnchorTaskCreator? = null
        private val anchorTaskWrapper: TaskCreatorWrap = TaskCreatorWrap(iAnchorTaskCreator)

        private var cacheTask: AnchorTask? = null

        fun setAnchorTaskCreator(iAnchorTaskCreator: IAnchorTaskCreator): Builder {
            this.iAnchorTaskCreator = iAnchorTaskCreator
            anchorTaskWrapper.iAnchorTaskCreator = iAnchorTaskCreator
            return this
        }

        fun setContext(context: Context): Builder {
            this.context = context
            return this
        }

        fun setLogLevel(logLevel: LogUtils.LogLevel): Builder {
            this.logLevel = logLevel
            LogUtils.logLevel = logLevel
            return this
        }

        fun setThreadPoolExecutor(threadPoolExecutor: ThreadPoolExecutor?): Builder {
            this.threadPoolExecutor = threadPoolExecutor
            return this
        }

        fun setTimeOutMillion(timeOutMillion: Long): Builder {
            this.timeOutMillion = timeOutMillion
            return this
        }

        fun addTask(taskName: String): Builder {
            val createTask = anchorTaskWrapper.createTask(taskName)
            createTask ?: let {
                throw AnchorTaskException("could not find anchorTask, taskName is $taskName")
            }
            return addTask(anchorTask = createTask)
        }

        fun afterTask(vararg taskNames: String): Builder {
            cacheTask ?: let {
                throw AnchorTaskException("should be call addTask first")
            }

            taskNames.forEach { taskName ->
                val createTask = anchorTaskWrapper.createTask(taskName)
                createTask ?: let {
                    throw AnchorTaskException("could not find anchorTask, taskName is $taskName")
                }

                cacheTask?.afterTask(taskName)
            }

            return this

        }

        fun afterTask(vararg anchorTasks: AnchorTask): Builder {
            cacheTask ?: let {
                throw AnchorTaskException("should be call addTask first")
            }
            anchorTasks.forEach {
                cacheTask?.afterTask(it.getTaskName())
            }

            return this
        }

        fun addTask(anchorTask: AnchorTask): Builder {
            cacheTask = anchorTask
            list.add(anchorTask)
            anchorTask.onAdd()
            if (anchorTask.needWait()) {
                needWaitCount.incrementAndGet()
            }
            return this
        }


        fun build(): AnchorProject {
            val sortResult = AnchorTaskUtils.getSortResult(list, taskMap, taskChildMap)
            LogUtils.d(TAG, "start: sortResult is $sortResult")
            sortResult.forEach {
                if (it.isRunOnMainThread()) {
                    mainList.add(it)
                } else {
                    threadList.add(it)
                }
            }

            countDownLatch = CountDownLatch(needWaitCount.get())
            return AnchorProject(this)
        }


    }
}




================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskException.kt
================================================
package com.xj.anchortask.library

import java.lang.RuntimeException

/**
 * Created by jun xu on 2/1/21.
 */
class AnchorTaskException(message: String?) : RuntimeException(message) {
}

================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskRunnable.kt
================================================
package com.xj.anchortask.library

import android.os.Process
import android.os.SystemClock

/**
 * Created by jun xu on 2/2/21.
 *
 */
class AnchorTaskRunnable(
    private val anchorProject: AnchorProject,
    private val anchorTask: AnchorTask
) : Runnable {

    override fun run() {
        Process.setThreadPriority(anchorTask.priority())
        //  前置任务没有执行完毕的话,等待,执行完毕的话,往下走
        anchorTask.await()
        anchorTask.onStart()
        // 执行任务
        val startTime = SystemClock.elapsedRealtime()
        anchorTask.run()
        val executeTime = SystemClock.elapsedRealtime() - startTime
        anchorProject.record(anchorTask.getTaskName(), executeTime)
        anchorTask.onFinish()
        // 通知子任务,当前任务执行完毕了,相应的计数器要减一。
        anchorProject.setNotifyChildren(anchorTask)
    }
}

================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskUtils.kt
================================================
package com.xj.anchortask.library


import com.xj.anchortask.library.log.LogUtils
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.collections.set

/**
 * Created by jun xu on 2/1/21.
 */
object AnchorTaskUtils {

    @JvmStatic
    fun getSortResult(
        list: MutableList<AnchorTask>, taskMap: MutableMap<String, AnchorTask>,
        taskChildMap: MutableMap<String, ArrayList<AnchorTask>?>
    ): MutableList<AnchorTask> {
        val result = ArrayList<AnchorTask>()
        // 入度为 0 的队列
        val queue = ArrayDeque<AnchorTask>()
        val taskIntegerHashMap = HashMap<String, Int>()

        // 建立每个 task 的入度关系
        list.forEach { anchorTask: AnchorTask ->
            val taskName = anchorTask.getTaskName()
            if (taskIntegerHashMap.containsKey(taskName)) {
                throw AnchorTaskException("anchorTask is repeat, anchorTask is $anchorTask, list is $list")
            }

            val size = anchorTask.getDependsTaskList()?.size ?: 0
            taskIntegerHashMap[taskName] = size
            taskMap[taskName] = anchorTask
            if (size == 0) {
                queue.offer(anchorTask)
            }
        }

        // 建立每个 task 的 children 关系
        list.forEach { anchorTask: AnchorTask ->
            anchorTask.getDependsTaskList()?.forEach { taskName: String ->
                var list = taskChildMap[taskName]
                if (list == null) {
                    list = ArrayList<AnchorTask>()
                }
                list.add(anchorTask)
                taskChildMap[taskName] = list
            }
        }

        taskChildMap.entries.iterator().forEach {
            LogUtils.d("TAG","key is ${it.key}, value is ${it.value}")
        }


        // 使用 BFS 方法获得有向无环图的拓扑排序
        while (!queue.isEmpty()) {
            val anchorTask = queue.pop()
            result.add(anchorTask)
            val taskName = anchorTask.getTaskName()
            taskChildMap[taskName]?.forEach { // 遍历所有依赖这个顶点的顶点,移除该顶点之后,如果入度为 0,加入到改队列当中
                val key = it.getTaskName()
                var result = taskIntegerHashMap[key] ?: 0
                result--
                if (result == 0) {
                    queue.offer(it)
                }
                taskIntegerHashMap[key] = result
            }
        }

        // size 不相等,证明有环
        if (list.size != result.size) {
            throw AnchorTaskException("Ring appeared,Please check.list is $list, result is $result")
        }

        return result

    }
}

================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/IAnchorTask.kt
================================================
package com.xj.anchortask.library

import android.os.Process
import androidx.annotation.CallSuper
import androidx.annotation.IntRange
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.CountDownLatch

/**
 * Created by jun xu on 2/1/21.
 */
interface IAnchorTask : IAnchorCallBack {

    /**
     * 获取任务昵称
     */
    fun getTaskName(): String

    /**
     * 是否在主线程执行
     */
    fun isRunOnMainThread(): Boolean

    /**
     * 任务优先级别
     */
    @IntRange(
        from = Process.THREAD_PRIORITY_FOREGROUND.toLong(),
        to = Process.THREAD_PRIORITY_LOWEST.toLong()
    )
    fun priority(): Int

    /**
     * 调用 await 方法,是否需要等待改任务执行完成
     * true 不需要
     * false 需要
     */
    fun needWait(): Boolean

    /**
     * 任务被执行的时候回调
     */
    fun run()

}

interface IAnchorCallBack {
    fun onAdd()
    fun onStart()
    fun onFinish()
}

class SimpleAnchorCallBack : IAnchorCallBack {
    override fun onAdd() {

    }


    override fun onStart() {

    }

    override fun onFinish() {

    }

}

abstract class AnchorTask(private val name: String) : IAnchorTask {

    companion object {
        const val TAG = "AnchorTask"
    }

    private lateinit var countDownLatch: CountDownLatch
    private val copyOnWriteArrayList: CopyOnWriteArrayList<IAnchorCallBack> by lazy {
        CopyOnWriteArrayList<IAnchorCallBack>()
    }

    val dependList: MutableList<String> = ArrayList()


    private fun getListSize() = getDependsTaskList()?.size ?: 0

    override fun getTaskName(): String {
        return name
    }

    override fun priority(): Int {
        return Process.THREAD_PRIORITY_FOREGROUND
    }

    override fun needWait(): Boolean {
        return true
    }

    fun afterTask(taskName: String) {
        dependList.add(taskName)
    }

    /**
     * self call,await
     */
    fun await() {
        tryToInitCountDown()
        countDownLatch.await()
    }

    @Synchronized
    private fun tryToInitCountDown() {
        if (!this::countDownLatch.isInitialized) {
            countDownLatch = CountDownLatch(dependList.size)
        }
    }

    /**
     * parent call, countDown
     */
    fun countdown() {
        tryToInitCountDown()
        countDownLatch.countDown()
    }

    override fun isRunOnMainThread(): Boolean {
        return false
    }

    fun getDependsTaskList(): List<String>? {
        return dependList
    }

    @CallSuper
    override fun onAdd() {
        copyOnWriteArrayList.forEach {
            it.onAdd()
        }
    }

    @CallSuper
    override fun onStart() {
        copyOnWriteArrayList.forEach {
            it.onStart()
        }
    }

    @CallSuper
    override fun onFinish() {
        copyOnWriteArrayList.forEach {
            it.onFinish()
        }
    }

    fun addCallback(iAnchorCallBack: IAnchorCallBack?) {
        iAnchorCallBack ?: return
        copyOnWriteArrayList.add(iAnchorCallBack)
    }

    fun removeCallback(iAnchorCallBack: IAnchorCallBack?) {
        iAnchorCallBack ?: return
        copyOnWriteArrayList.remove(iAnchorCallBack)
    }

    override fun toString(): String {
        return "AnchorTask(name='$name',dependList is $dependList)"
    }


}

================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/IAnchorTaskCreator.kt
================================================
package com.xj.anchortask.library

/**
 * Created by jun xu on 2/9/21.
 */
open interface IAnchorTaskCreator {
    /**
     * 根据Task名称,创建Task实例。这个接口需要使用者自己实现。创建后的实例会被缓存起来。
     * @param taskName Task名称
     * @return  Task实例
     */
    fun createTask(taskName: String): AnchorTask?
}


open class TaskCreatorWrap(var iAnchorTaskCreator: IAnchorTaskCreator?) : IAnchorTaskCreator {

    private val map: MutableMap<String, AnchorTask?> = HashMap()

    override fun createTask(taskName: String): AnchorTask? {
        val anchorTask = map[taskName]
        anchorTask?.let {
            return it
        }
        return iAnchorTaskCreator?.createTask(taskName)
    }

    fun checkTaskIsExits(taskName: String): Boolean {
        return map.containsKey(taskName)
    }

}


================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/ProjectBuilder.kt
================================================
package com.xj.anchortask.library

import android.content.Context
import com.xj.anchortask.library.log.LogUtils
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.CountDownLatch
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.atomic.AtomicInteger

/**
 * Created by jun xu on 2/1/21.
 */





================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/ProjectListener.kt
================================================
package com.xj.anchortask.library

/**
 * Created by jun xu on 2/8/21.
 */
interface OnProjectExecuteListener {
    /**
     * 当`Project`开始执行时,调用该函数。<br></br>
     * **注意:**该回调函数在`Task`所在线程中回调,注意线程安全。
     */
    fun onProjectStart()

    /**
     * 当`Project`其中一个`Task`执行结束时,调用该函数。<br></br>
     * **注意:**该回调函数在`Task`所在线程中回调,注意线程安全。
     *
     * @param taskName 当前结束的`Task`名称
     */
    fun onTaskFinish(taskName: String)

    /**
     * 当`Project`执行结束时,调用该函数。<br></br>
     * **注意:**该回调函数在`Task`所在线程中回调,注意线程安全。
     */
    fun onProjectFinish()
}



================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/TaskExecutorManager.kt
================================================
package com.xj.anchortask.library

import java.util.concurrent.*

class TaskExecutorManager private constructor() {
    //获得cpu密集型线程池,因为占据CPU的时间片过多的话会影响性能,所以这里控制了最大并发,防止主线程的时间片减少
    //CPU 密集型任务的线程池
    val cpuThreadPoolExecutor: ThreadPoolExecutor

    //获得io密集型线程池,有好多任务其实占用的CPU time非常少,所以使用缓存线程池,基本上来着不拒
    // IO 密集型任务的线程池
    val ioThreadPoolExecutor: ExecutorService

    //线程池队列
    private val mPoolWorkQueue: BlockingQueue<Runnable> =
        LinkedBlockingQueue()

    // 这个是为了保障任务超出BlockingQueue的最大值,且线程池中的线程数已经达到MAXIMUM_POOL_SIZE时候,还有任务到来会采取任务拒绝策略,这里定义的策略就是
    //再开一个缓存线程池去执行。当然BlockingQueue默认的最大值是int_max,所以理论上这里是用不到的
    private val mHandler =
        RejectedExecutionHandler { r, executor -> Executors.newCachedThreadPool().execute(r) }

    companion object {
        //CPU 核数
        private val CPU_COUNT = Runtime.getRuntime().availableProcessors()

        //线程池线程数
        private val CORE_POOL_SIZE = Math.max(
            2,
            Math.min(CPU_COUNT - 1, 5)
        )

        //线程池线程数的最大值
        private val MAXIMUM_POOL_SIZE = CORE_POOL_SIZE

        //线程空置回收时间
        private const val KEEP_ALIVE_SECONDS = 5

        @JvmStatic
        val instance: TaskExecutorManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            TaskExecutorManager()
        }
    }

    //初始化线程池
    init {
        cpuThreadPoolExecutor = ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE,
            KEEP_ALIVE_SECONDS.toLong(),
            TimeUnit.SECONDS,
            mPoolWorkQueue,
            Executors.defaultThreadFactory(),
            mHandler
        ).apply {
            allowCoreThreadTimeOut(true)
        }
        ioThreadPoolExecutor = Executors.newCachedThreadPool(Executors.defaultThreadFactory())
    }
}

================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/ThreadUtils.kt
================================================
package com.xj.anchortask.library

import android.os.Handler
import android.os.Looper

object ThreadUtils {

    fun runOnUiThread(r: Runnable) {
        if (isMainThread) {
            r.run()
        } else {
            LazyHolder.sUiThreadHandler.post(r)
        }
    }

    fun runOnUiThreadAtFront(r: Runnable) {
        if (isMainThread) {
            r.run()
        } else {
            LazyHolder.sUiThreadHandler.postAtFrontOfQueue(r)
        }
    }

    fun runOnUiThread(r: Runnable?, delay: Long) {
        LazyHolder.sUiThreadHandler.postDelayed(r!!, delay)
    }

    fun removeCallbacks(r: Runnable?) {
        LazyHolder.sUiThreadHandler.removeCallbacks(r!!)
    }

    val isMainThread: Boolean
        get() = Looper.getMainLooper() == Looper.myLooper()

    fun checkAtMainThread() {
        val e = RuntimeException("not main thread")
        LazyHolder.sUiThreadHandler.postDelayed({ throw e }, 500)
    }

    private object LazyHolder {
        val sUiThreadHandler =
            Handler(Looper.getMainLooper())
    }
}

================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/log/LogUtils.kt
================================================
package com.xj.anchortask.library.log

import android.util.Log

/**
 * Created by jun xu on 2/2/21.
 */
object LogUtils {

    enum class LogLevel {
        NONE {
            override val value: Int
                get() = -1
        },

        ERROR {
            override val value: Int
                get() = 0
        },
        WARN {
            override val value: Int
                get() = 1
        },
        INFO {
            override val value: Int
                get() = 2
        },
        DEBUG {
            override val value: Int
                get() = 3
        };


        abstract val value: Int
    }

    private var TAG = "SAF_L"

    var logLevel = LogLevel.INFO // 日志的等级,可以进行配置,最好在Application中进行全局的配置

    @JvmStatic
    fun init(clazz: Class<*>) {
        TAG = clazz.simpleName
    }

    /**
     * 支持用户自己传tag,可扩展性更好
     * @param tag
     */
    @JvmStatic
    fun init(tag: String) {
        TAG = tag
    }

    @JvmStatic
    fun e(tag: String, msg: String) {
        if (LogLevel.ERROR.value <= logLevel.value) {
            if (msg.isNotBlank()) {
                Log.e("$TAG$tag", msg)
            }
        }
    }

    @JvmStatic
    fun w(tag: String, msg: String) {
        if (LogLevel.WARN.value <= logLevel.value) {
            if (msg.isNotBlank()) {
                Log.e("$TAG$tag", msg)
            }
        }
    }

    @JvmStatic
    fun i(tag: String, msg: String) {
        if (LogLevel.INFO.value <= logLevel.value) {
            if (msg.isNotBlank()) {
                Log.i("$TAG$tag", msg)
            }

        }
    }

    @JvmStatic
    fun d(tag: String, msg: String) {
        if (LogLevel.DEBUG.value <= logLevel.value) {
            if (msg.isNotBlank()) {
                Log.d("$TAG$tag", msg)
            }
        }
    }


}

================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/monitor/ExecuteMonitor.kt
================================================
/*
 * Copyright 2018 Alibaba Group.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.xj.anchortask.library.monitor

import android.os.Handler
import android.os.Looper
import java.util.concurrent.ConcurrentHashMap

/**
 *
 * 监控`Project`执行性能的类。会记录每一个`Task`执行时间,以及整个`Project`执行时间。
 *
 */
internal class ExecuteMonitor {
    private val mExecuteTimeMap: MutableMap<String?, Long?> = ConcurrentHashMap()
    private var mStartTime: Long = 0

    /**
     * @return `Project`执行时间。
     */
    var projectCostTime: Long = 0
        private set
    private var mHandler: Handler? = null

    /**
     * 记录`task`执行时间。
     *
     * @param taskName    `task`的名称
     * @param executeTime 执行的时间
     */
    fun record(taskName: String, executeTime: Long) {
//        AlphaLog.d(AlphaLog.GLOBAL_TAG, "AlphaTask-->Startup task %s cost time: %s ms, in thread: %s", taskName, executeTime, Thread.currentThread().getName());
//        if (executeTime >= AlphaConfig.getWarmingTime()) {
//            toastToWarn("AlphaTask %s run too long, cost time: %s", taskName, executeTime);
//        }
        mExecuteTimeMap[taskName] = executeTime
    }

    /**
     * @return 已执行完的每个task的执行时间。
     */
    val executeTimeMap: Map<String?, Long?>
        get() = mExecuteTimeMap

    /**
     * 在`Project`开始执行时打点,记录开始时间。
     */
    fun recordProjectStart() {
        mStartTime = System.currentTimeMillis()
    }

    /**
     * 在`Project`结束时打点,记录耗时。
     */
    fun recordProjectFinish() {
        projectCostTime = System.currentTimeMillis() - mStartTime
        //        AlphaLog.d("==ALPHA==", "tm start up cost time: %s ms", mProjectCostTime);
    }

    /**
     * 通过弹出`toast`来告警。
     *
     * @param msg  告警内容
     * @param args format参数
     */
    private fun toastToWarn(msg: String, vararg args: Any) {
        handler.post {
            val formattedMsg: String
            formattedMsg = if (args == null) {
                msg
            } else {
                String.format(msg, *args)
            }

//                    Toast.makeText(AlphaConfig.getContext(), formattedMsg, Toast.LENGTH_SHORT).show();
        }
    }

    private val handler: Handler
        private get() {
            if (mHandler == null) {
                mHandler = Handler(Looper.getMainLooper())
            }
            return mHandler!!
        }
}

================================================
FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/monitor/OnGetMonitorRecordCallback.kt
================================================
/*
 * Copyright 2018 Alibaba Group.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.xj.anchortask.library.monitor

/**
 *
 * 获取`Project`执行性能记录的回调
 *
 */
interface OnGetMonitorRecordCallback {
    /**
     * 获取`task`执行的耗时。
     * @param result `task`执行的耗时。`key`是`task`名称,`value`是`task`执行耗时,单位是毫秒。
     */
    fun onGetTaskExecuteRecord(result: Map<String?, Long?>?)

    /**
     * 获取整个`Project`执行耗时。
     * @param costTime 整个`Project`执行耗时。
     */
    fun onGetProjectExecuteTime(costTime: Long)
}

================================================
FILE: anchortasklibrary/src/test/java/com/xj/anchortask/library/ExampleUnitTest.kt
================================================
package com.xj.anchortask.library

import org.junit.Test

import org.junit.Assert.*

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
class ExampleUnitTest {
    @Test
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}

================================================
FILE: app/.gitignore
================================================
/build

================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply from: '../localconfig.gradle'

android {
    compileSdkVersion 30
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.xj.anchortask"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

Properties properties = loadProperties("local.properties")
String mode = null
if (properties != null) {
    mode = getValue(properties, "mode", "local")
}
println("mode is $mode")
print("anchorTaskVersion is ${anchorTaskVersion}")

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'

    implementation 'com.google.android:flexbox:2.0.1'


    implementation "androidx.startup:startup-runtime:1.0.0"
    implementation 'androidx.work:work-runtime:2.3.4'
    implementation 'com.google.android.material:material:1.4.0'


    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    if (mode == "local") {
        implementation "com.xj.android:anchortask:${anchorTaskVersion}-local"
    } else if (mode == "remote") {
        implementation "com.xj.android:anchortask:${anchorTaskVersion}"
    } else {
        implementation project(path: ':anchortasklibrary')
    }


}

================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

================================================
FILE: app/src/androidTest/java/com/xj/anchortask/ExampleInstrumentedTest.kt
================================================
package com.xj.anchortask

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4

import org.junit.Test
import org.junit.runner.RunWith

import org.junit.Assert.*

/**
 * Instrumented test, which will execute on an Android device.
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.xj.anchortask", appContext.packageName)
    }
}

================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.xj.anchortask">

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".asyncInflate.AsyncActivity"
            android:exported="true" />
        <activity android:name=".flowlayout.FlowLayoutDemo" />

        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge" />

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".viewStub.ViewStubDemoActivity" />
        <activity android:name=".anchorTask.AnchorTaskTestActivity" />
    </application>

</manifest>

================================================
FILE: app/src/main/java/com/xj/anchortask/LogUtils.kt
================================================
package com.xj.anchortask

import android.util.Log
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject

/**
 * Created by jun xu on 2/2/21.
 */
object LogUtils {

    enum class LogLevel {
        ERROR {
            override val value: Int
                get() = 0
        },
        WARN {
            override val value: Int
                get() = 1
        },
        INFO {
            override val value: Int
                get() = 2
        },
        DEBUG {
            override val value: Int
                get() = 3
        };

        abstract val value: Int
    }

    private var TAG = "AnchorTask"

    var logLevel = LogLevel.DEBUG // 日志的等级,可以进行配置,最好在Application中进行全局的配置

    var printMsgStack = false

    @JvmStatic
    fun init(clazz: Class<*>) {
        TAG = clazz.simpleName
    }

    /**
     * 支持用户自己传tag,可扩展性更好
     * @param tag
     */
    @JvmStatic
    fun init(tag: String) {
        TAG = tag
    }

    @JvmStatic
    fun e(tag: String, msg: String) {
        if (LogLevel.ERROR.value <= logLevel.value) {
            if (msg.isNotBlank()) {
                val s = getMethodNames()
                Log.e("${TAG}_${tag}", String.format(s, msg))
            }
        }
    }

    @JvmStatic
    fun w(tag: String, msg: String) {
        if (LogLevel.WARN.value <= logLevel.value) {
            if (msg.isNotBlank()) {
                val s = getMethodNames()
                Log.e("${TAG}_${tag}", String.format(s, msg))
            }
        }
    }

    @JvmStatic
    fun i(tag: String, msg: String) {
        if (LogLevel.INFO.value <= logLevel.value) {
            if (msg.isNotBlank()) {
                val s = getMethodNames()
                Log.i("${TAG}_${tag}", String.format(s, msg))
            }

        }
    }

    @JvmStatic
    fun d(tag: String, msg: String) {
        if (LogLevel.DEBUG.value <= logLevel.value) {
            if (msg.isNotBlank()) {
                val s = getMethodNames()
                Log.d("${TAG}_${tag}", String.format(s, msg))
            }
        }
    }

    @JvmStatic
    fun json(tag: String, json: String) {
        var json = json
        if (json.isBlank()) {
            d(tag, "Empty/Null json content")
            return
        }

        try {
            json = json.trim { it <= ' ' }
            if (json.startsWith("{")) {
                val jsonObject = JSONObject(json)
                var message = jsonObject.toString(LoggerPrinter.JSON_INDENT)
                message = message.replace("\n".toRegex(), "\n║ ")
                val s = getMethodNames()
                println(String.format(s, message))
                return
            }
            if (json.startsWith("[")) {
                val jsonArray = JSONArray(json)
                var message = jsonArray.toString(LoggerPrinter.JSON_INDENT)
                message = message.replace("\n".toRegex(), "\n║ ")
                val s = getMethodNames()
                println(String.format(s, message))
                return
            }
            e(tag, "Invalid Json")
        } catch (e: JSONException) {
            e(tag, "Invalid Json")
        }

    }

    private fun getMethodNames(): String {
        if (!printMsgStack) {
            return "%s"
        }
        val sElements = Thread.currentThread().stackTrace

        var stackOffset = LoggerPrinter.getStackOffset(sElements)

        stackOffset++
        val builder = StringBuilder()
        builder.append(LoggerPrinter.TOP_BORDER).append("\r\n")
            // 添加当前线程名
            .append("║ " + "Thread: " + Thread.currentThread().name).append("\r\n")
            .append(LoggerPrinter.MIDDLE_BORDER).append("\r\n")
            // 添加类名、方法名、行数
            .append("║ ")
            .append(sElements[stackOffset].className)
            .append(".")
            .append(sElements[stackOffset].methodName)
            .append(" ")
            .append(" (")
            .append(sElements[stackOffset].fileName)
            .append(":")
            .append(sElements[stackOffset].lineNumber)
            .append(")")
            .append("\r\n")
            .append(LoggerPrinter.MIDDLE_BORDER).append("\r\n")
            // 添加打印的日志信息
            .append("║ ").append("%s").append("\r\n")
            .append(LoggerPrinter.BOTTOM_BORDER).append("\r\n")
        return builder.toString()
    }

}

================================================
FILE: app/src/main/java/com/xj/anchortask/LoggerPrinter.kt
================================================
package com.xj.anchortask

/**
 * Created by jun xu on 2/2/21.
 */
object LoggerPrinter {

    private val MIN_STACK_OFFSET = 3

    /**
     * Drawing toolbox
     */
    private val TOP_LEFT_CORNER = '╔'
    private val BOTTOM_LEFT_CORNER = '╚'
    private val MIDDLE_CORNER = '╟'
    private val HORIZONTAL_DOUBLE_LINE = '║'
    private val DOUBLE_DIVIDER = "════════════════════════════════════════════"
    private val SINGLE_DIVIDER = "────────────────────────────────────────────"
    val TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER
    val BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER
    val MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER

    /**
     * It is used for json pretty print
     */
    val JSON_INDENT = 2

    fun getStackOffset(trace: Array<StackTraceElement>): Int {
        var i = MIN_STACK_OFFSET
        while (i < trace.size) {
            val e = trace[i]
            val name = e.className
            if (name != LoggerPrinter::class.java.name && name != LogUtils::class.java.name) {
                return --i
            }
            i++
        }
        return -1
    }
}

================================================
FILE: app/src/main/java/com/xj/anchortask/MainActivity.kt
================================================
package com.xj.anchortask

import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.xj.anchortask.anchorTask.AnchorTaskTestActivity
import com.xj.anchortask.asyncInflate.AsyncActivity
import com.xj.anchortask.asyncInflate.page.AsyncUtils
import com.xj.anchortask.asyncInflate.page.AsyncUtils.isHomeFragmentOpen
import com.xj.anchortask.flowlayout.FlowLayoutDemo
import com.xj.anchortask.viewStub.ViewStubDemoActivity

import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn_view_stub.setOnClickListener {
            startActivity(Intent(this@MainActivity, ViewStubDemoActivity::class.java))
        }

        btn_anchortask.setOnClickListener {
            startActivity(Intent(this@MainActivity, AnchorTaskTestActivity::class.java))
        }

        btn_flow_layout_demo.setOnClickListener {
            startActivity(Intent(this@MainActivity, FlowLayoutDemo::class.java))
        }

        btn_async.setOnClickListener {
            startActivity(Intent(this@MainActivity, AsyncActivity::class.java))
        }


        val isOpen = AsyncUtils.isHomeFragmentOpen()
        updateText(isOpen)
        switch_async.isChecked = isOpen

        ll_switch.setOnClickListener {
            val b = !switch_async.isChecked
            updateChecked(b)
        }

        switch_async.setOnCheckedChangeListener { buttonView, isChecked ->
            updateChecked(isChecked)
        }
    }

    private fun updateChecked(b: Boolean) {
        updateText(b)
        switch_async.isChecked = b
        getSP("async_config").spApplyBoolean("home_fragment_switch", b)
    }


    private fun updateText(b: Boolean) {
        if (b) {
            tv_asnyc_text.setText("首页 Fragment 开启异步加载")
        } else {
            tv_asnyc_text.setText("首页 Fragment 关闭异步加载")
        }
    }
}

================================================
FILE: app/src/main/java/com/xj/anchortask/MyApplication.kt
================================================
package com.xj.anchortask

import android.app.Application
import android.content.Context
import android.util.Log
import com.xj.anchortask.anchorTask.TestTaskUtils
import com.xj.anchortask.asyncInflate.page.AsyncUtils
import com.xj.anchortask.library.OnProjectExecuteListener
import com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback

/**
 * Created by jun xu on 2/1/21.
 */
class MyApplication : Application() {


    companion object {
        const val TAG = "AnchorTaskApplication"

        lateinit var myApplication: MyApplication
            private set

        @JvmStatic
        fun getInstance(): Application {
            return myApplication
        }
    }

    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        Log.i(TAG, "attachBaseContext: ")
    }

    override fun onCreate() {
        super.onCreate()
        Log.i(TAG, "onCreate: ")
        myApplication = this

        TestTaskUtils.executeTask(this, projectExecuteListener = object : OnProjectExecuteListener {
            override fun onProjectFinish() {

            }

            override fun onProjectStart() {

            }

            override fun onTaskFinish(taskName: String) {

            }

        }, onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback {
            override fun onGetProjectExecuteTime(costTime: Long) {
                Log.i(TAG, "onGetProjectExecuteTime: costTime is $costTime")

            }

            override fun onGetTaskExecuteRecord(result: Map<String?, Long?>?) {
                Log.i(TAG, "onGetTaskExecuteRecord: result is $result")
            }

        })

    }


}

================================================
FILE: app/src/main/java/com/xj/anchortask/SPUtil.kt
================================================
package com.xj.anchortask

import android.content.Context
import android.content.SharedPreferences


fun getSP(name: String) = MyApplication.getInstance().getSharedPreferences(name, Context.MODE_PRIVATE)!!

fun SharedPreferences.getSPEditor() = this.edit()!!

fun SharedPreferences.spApplyClear() = getSPEditor().clear().apply()

fun SharedPreferences.spCommitClear() = getSPEditor().clear().commit()

fun SharedPreferences.spApplyInt(key: String, value: Int) = getSPEditor().putInt(key, value).apply()

fun SharedPreferences.spCommitInt(key: String, value: Int) =
    getSPEditor().putInt(key, value).commit()

fun SharedPreferences.spGetInt(key: String, defValue: Int = 0): Int = this.getInt(key, defValue)

fun SharedPreferences.spApplyLong(key: String, value: Long) =
    getSPEditor().putLong(key, value).apply()

fun SharedPreferences.spCommitLong(key: String, value: Long) =
    getSPEditor().putLong(key, value).commit()

fun SharedPreferences.spGetLong(key: String, defValue: Long = 0): Long = this.getLong(key, defValue)

fun SharedPreferences.spApplyFloat(key: String, value: Float) =
    getSPEditor().putFloat(key, value).apply()

fun SharedPreferences.spCommitFloat(key: String, value: Float) =
    getSPEditor().putFloat(key, value).commit()

fun SharedPreferences.spGetFloat(key: String, defValue: Float = 0F): Float =
    this.getFloat(key, defValue)

fun SharedPreferences.spApplyBoolean(key: String, value: Boolean) =
    getSPEditor().putBoolean(key, value).apply()

fun SharedPreferences.spCommitBoolean(key: String, value: Boolean) =
    getSPEditor().putBoolean(key, value).commit()

fun SharedPreferences.spGetBoolean(key: String, defValue: Boolean = false): Boolean =
    this.getBoolean(key, defValue)

fun SharedPreferences.spApplyString(key: String, value: String) =
    getSPEditor().putString(key, value).apply()

fun SharedPreferences.spCommitString(key: String, value: String) =
    getSPEditor().putString(key, value).commit()

fun SharedPreferences.spGetString(key: String, defValue: String = ""): String? =
    this.getString(key, defValue)

fun SharedPreferences.spApplyStringSet(key: String, value: Set<String>) =
    getSPEditor().putStringSet(key, value).apply()

fun SharedPreferences.spCommitStringSet(key: String, value: Set<String>) =
    getSPEditor().putStringSet(key, value).commit()

fun SharedPreferences.spGetStringSet(key: String, defValue: Set<String>? = null): Set<String>? =
    this.getStringSet(key, defValue)

fun SharedPreferences.spCommitRemove(key: String) = getSPEditor().remove(key).commit()

fun SharedPreferences.spApplyRemove(key: String) = getSPEditor().remove(key).commit()

================================================
FILE: app/src/main/java/com/xj/anchortask/anchorTask/AnchorTaskTestActivity.kt
================================================
package com.xj.anchortask.anchorTask

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.xj.anchortask.R
import com.xj.anchortask.library.OnProjectExecuteListener
import com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback
import kotlinx.android.synthetic.main.activity_anchortask_test.*

class AnchorTaskTestActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_anchortask_test)
        initAnchorTask()
    }

    private fun initAnchorTask() {
        val sb2 = StringBuilder()
        text2.text = "正在执行中"
        val projectExecuteListener = object : OnProjectExecuteListener {
            val sb = StringBuffer()
            override fun onProjectStart() {
                if (sb.length > 0) {
                    sb.delete(0, sb.length)
                }

            }

            override fun onTaskFinish(taskName: String) {
                sb.append("task $taskName execute finish \n")
            }

            override fun onProjectFinish() {
                text.post {
                    text.setText(sb.toString())
                }
            }

        }
        val onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback {


            override fun onGetTaskExecuteRecord(result: Map<String?, Long?>?) {
                result?.entries?.iterator()?.forEach {
                    sb2.append(it.key).append("执行耗时").append(it.value).append("毫秒\n")
                }
                text2.text = sb2.toString()
            }

            override fun onGetProjectExecuteTime(costTime: Long) {
                sb2.append("总共执行耗时").append(costTime).append("毫秒\n")
            }

        }
        btn_execute.setOnClickListener {
            sb2.clear()
            text2.text = "正在执行中"
            try {
                TestTaskUtils.executeTask(
                    this, projectExecuteListener, onGetMonitorRecordCallback
                )
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }

        btn_execute2.setOnClickListener {
            sb2.clear()
            text2.text = "正在执行中2"
            try {
                TestTaskUtils.executeTask(
                    this, projectExecuteListener, onGetMonitorRecordCallback
                )
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
    }
}

================================================
FILE: app/src/main/java/com/xj/anchortask/anchorTask/ApplicationAnchorTaskCreator.kt
================================================
package com.xj.anchortask.anchorTask

import com.xj.anchortask.LogUtils
import com.xj.anchortask.library.AnchorTask
import com.xj.anchortask.library.IAnchorTaskCreator

/**
 * Created by jun xu on 2/1/21.
 */

const val TASK_NAME_ZERO = "zero"
const val TASK_NAME_ONE = "one"
const val TASK_NAME_TWO = "two"
const val TASK_NAME_THREE = "three"
const val TASK_NAME_FOUR = "four"
const val TASK_NAME_FIVE = "five"

class ApplicationAnchorTaskCreator : IAnchorTaskCreator {
    override fun createTask(taskName: String): AnchorTask? {
        when (taskName) {
            TASK_NAME_ZERO -> {
                return AnchorTaskZero()
            }

            TASK_NAME_ONE -> {
                return AnchorTaskOne()
            }
            TASK_NAME_TWO -> {
                return AnchorTaskTwo()
            }
            TASK_NAME_THREE -> {
                return AnchorTaskThree()
            }
            TASK_NAME_FOUR -> {
                return AnchorTaskFour()
            }
            TASK_NAME_FIVE -> {
                return AnchorTaskFive()
            }
        }
        return null
    }

}

class AnchorTaskZero() : AnchorTask(TASK_NAME_ZERO) {
    override fun isRunOnMainThread(): Boolean {
        return false
    }

    override fun run() {
        val start = System.currentTimeMillis()
        try {
            Thread.sleep(300)
        } catch (e: Exception) {
        }
        LogUtils.i(
            TAG, "AnchorTaskOne: " + (System.currentTimeMillis() - start)
        )
    }
}

class AnchorTaskOne : AnchorTask(TASK_NAME_ONE) {
    override fun isRunOnMainThread(): Boolean {
        return false
    }

    override fun run() {
        val start = System.currentTimeMillis()
        try {
            Thread.sleep(300)
        } catch (e: Exception) {
        }
        LogUtils.i(
            TAG, "AnchorTaskOne: " + (System.currentTimeMillis() - start)
        )
    }

}

class AnchorTaskTwo : AnchorTask(TASK_NAME_TWO) {
    override fun isRunOnMainThread(): Boolean {
        return false
    }

    override fun run() {
        val start = System.currentTimeMillis()
        try {
            Thread.sleep(300)
        } catch (e: Exception) {
        }
        LogUtils.i(
            TAG, "AnchorTaskTwo执行耗时: " + (System.currentTimeMillis() - start)
        )
    }

}

class AnchorTaskThree : AnchorTask(TASK_NAME_THREE) {
    override fun isRunOnMainThread(): Boolean {
        return false
    }

    override fun run() {
        val start = System.currentTimeMillis()
        try {
            Thread.sleep(300)
        } catch (e: Exception) {
        }
        LogUtils.i(
            TAG, "AnchorTaskThree执行耗时: " + (System.currentTimeMillis() - start)
        )
    }

}


class AnchorTaskFour : AnchorTask(TASK_NAME_FOUR) {
    override fun isRunOnMainThread(): Boolean {
        return false
    }

    override fun run() {
        val start = System.currentTimeMillis()
        try {
            Thread.sleep(300)
        } catch (e: Exception) {
        }
        LogUtils.i(
            TAG, "AnchorTaskFour执行耗时: " + (System.currentTimeMillis() - start)
        )
    }

}

class AnchorTaskFive : AnchorTask(TASK_NAME_FIVE) {
    override fun isRunOnMainThread(): Boolean {
        return false
    }

    override fun run() {
        val start = System.currentTimeMillis()
        try {
            Thread.sleep(300)
        } catch (e: Exception) {
        }
        LogUtils.i(
            TAG, "AnchorTaskFive执行耗时: " + (System.currentTimeMillis() - start)
        )
    }


}





================================================
FILE: app/src/main/java/com/xj/anchortask/anchorTask/TestTaskUtils.kt
================================================
package com.xj.anchortask.anchorTask

import android.content.Context
import com.xj.anchortask.MyApplication
import com.xj.anchortask.library.AnchorProject
import com.xj.anchortask.library.OnProjectExecuteListener
import com.xj.anchortask.library.TaskExecutorManager
import com.xj.anchortask.library.log.LogUtils
import com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback

/**
 * Created by jun xu on 2/9/21.
 */
object TestTaskUtils {

    fun executeTask(
        context: Context,
        projectExecuteListener: OnProjectExecuteListener? = null,
        onGetMonitorRecordCallback: OnGetMonitorRecordCallback? = null
    ) {
        val project =
            AnchorProject.Builder().setContext(context).setLogLevel(LogUtils.LogLevel.DEBUG)
                .setAnchorTaskCreator(ApplicationAnchorTaskCreator())
                .addTask(TASK_NAME_ZERO)
                .addTask(TASK_NAME_ONE)
                .addTask(TASK_NAME_TWO)
                .addTask(TASK_NAME_THREE).afterTask(
                    TASK_NAME_ZERO,
                    TASK_NAME_ONE
                )
                .addTask(TASK_NAME_FOUR).afterTask(
                    TASK_NAME_ONE,
                    TASK_NAME_TWO
                )
                .addTask(TASK_NAME_FIVE).afterTask(
                    TASK_NAME_THREE,
                    TASK_NAME_FOUR
                )
                .setThreadPoolExecutor(TaskExecutorManager.instance.cpuThreadPoolExecutor)
                .build()
        project.addListener(object : OnProjectExecuteListener {
            override fun onProjectStart() {
                com.xj.anchortask.LogUtils.i(
                    MyApplication.TAG,
                    "onProjectStart "
                )
            }

            override fun onTaskFinish(taskName: String) {
                com.xj.anchortask.LogUtils.i(
                    MyApplication.TAG,
                    "onTaskFinish, taskName is $taskName"
                )
            }

            override fun onProjectFinish() {
                com.xj.anchortask.LogUtils.i(
                    MyApplication.TAG,
                    "onProjectFinish "
                )
            }

        })
        projectExecuteListener?.let {
            project.addListener(it)
        }
        project.onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback {
            override fun onGetTaskExecuteRecord(result: Map<String?, Long?>?) {
                onGetMonitorRecordCallback?.onGetTaskExecuteRecord(result)
            }

            override fun onGetProjectExecuteTime(costTime: Long) {
                onGetMonitorRecordCallback?.onGetProjectExecuteTime(costTime)
            }

        }

        project.start().await(1000)
    }

    fun executeTask2(
        context: Context,
        projectExecuteListener: OnProjectExecuteListener? = null,
        onGetMonitorRecordCallback: OnGetMonitorRecordCallback? = null
    ) {

        val anchorTaskZero = AnchorTaskZero()
        val anchorTaskOne = AnchorTaskOne()
        val anchorTaskTwo = AnchorTaskTwo()
        val anchorTaskThree = AnchorTaskThree()
        val anchorTaskFour = AnchorTaskFour()
        val anchorTaskFive = AnchorTaskFive()
        val project =
            AnchorProject.Builder().setContext(context).setLogLevel(LogUtils.LogLevel.DEBUG)
                .setAnchorTaskCreator(ApplicationAnchorTaskCreator())
                .addTask(anchorTaskZero)
                .addTask(anchorTaskOne)
                .addTask(anchorTaskTwo)
                .addTask(anchorTaskThree).afterTask(
                    anchorTaskZero,
                    anchorTaskOne
                )
                .addTask(TASK_NAME_FOUR).afterTask(
                    anchorTaskOne,
                    anchorTaskTwo
                )
                .addTask(TASK_NAME_FIVE).afterTask(
                    anchorTaskThree,
                    anchorTaskFive
                )
                .setThreadPoolExecutor(TaskExecutorManager.instance.cpuThreadPoolExecutor)
                .build()
        project.addListener(object : OnProjectExecuteListener {
            override fun onProjectStart() {
                com.xj.anchortask.LogUtils.i(
                    MyApplication.TAG,
                    "onProjectStart "
                )
            }

            override fun onTaskFinish(taskName: String) {
                com.xj.anchortask.LogUtils.i(
                    MyApplication.TAG,
                    "onTaskFinish, taskName is $taskName"
                )
            }

            override fun onProjectFinish() {
                com.xj.anchortask.LogUtils.i(
                    MyApplication.TAG,
                    "onProjectFinish "
                )
            }

        })
        projectExecuteListener?.let {
            project.addListener(it)
        }
        project.onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback {
            override fun onGetTaskExecuteRecord(result: Map<String?, Long?>?) {
                onGetMonitorRecordCallback?.onGetTaskExecuteRecord(result)
            }

            override fun onGetProjectExecuteTime(costTime: Long) {
                onGetMonitorRecordCallback?.onGetProjectExecuteTime(costTime)
            }

        }

        project.start().await(1000)
    }
}

================================================
FILE: app/src/main/java/com/xj/anchortask/appstartup/InitializerSample.kt
================================================
package com.xj.anchortask.appstartup

import android.content.Context
import android.util.Log
import androidx.startup.AppInitializer
import androidx.startup.Initializer
import androidx.work.WorkManager


/**
 * Created by jun xu on 4/17/21.
 */

const val TAG = "AnchorTaskApplication"

class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager {
        Log.i(TAG, "create: WorkManagerInitializer init")
        AppInitializer.getInstance(context)
            .initializeComponent(ExampleLoggerInitializer::class.java)
        val let: Int = context.let {
            123
        }
        return WorkManager.getInstance(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // No dependencies on other libraries.

        return emptyList()
    }
}

// Initializes ExampleLogger.
class ExampleLoggerInitializer : Initializer<ExampleLogger> {
    override fun create(context: Context): ExampleLogger {
        Log.i(TAG, "create: ExampleLoggerInitializer init")
        val apply: Context = context.apply { }
        return ExampleLogger(WorkManager.getInstance(context))
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // Defines a dependency on WorkManagerInitializer so it can be
        // initialized after WorkManager is initialized.
        return listOf(WorkManagerInitializer::class.java)
    }
}

class ExampleLogger(val workManager: WorkManager) {

}

fun <T, R> T.let2(block: (T) -> R): R {
    return block(this)
}

fun <T> T.apply2(block: T.() -> Unit): T {
    block()
    return this
}


================================================
FILE: app/src/main/java/com/xj/anchortask/asyncInflate/AsyncActivity.kt
================================================
package com.xj.anchortask.asyncInflate

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.xj.anchortask.R

class AsyncActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val asyncInflateItem =
            AsyncInflateItem(
                LaunchInflateKey.LAUNCH_ACTIVITY,
                R.layout.activity_async,
                null,
                null
            )
        AsyncInflateManager.instance.asyncInflate(this, asyncInflateItem)
        Thread.sleep(2)
        val inflatedView = AsyncInflateManager.instance.getInflatedView(
            this,
            R.layout.activity_async,
            null,
            LaunchInflateKey.LAUNCH_ACTIVITY,
            layoutInflater
        )
        setContentView(inflatedView)
    }

    override fun onDestroy() {
        super.onDestroy()
        AsyncInflateManager.instance.remove(LaunchInflateKey.LAUNCH_ACTIVITY)
    }
}

================================================
FILE: app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateItem.kt
================================================
package com.xj.anchortask.asyncInflate

import android.view.View
import android.view.ViewGroup

/**
 * Created by jun xu on 4/1/21.
 */
class AsyncInflateItem constructor(var inflateKey: String, var layoutResId: Int, var parent: ViewGroup? = null, var callback: OnInflateFinishedCallback? = null) {
    var inflatedView: View? = null
    private var cancelled = false
    private var inflating = false

    fun isCancelled(): Boolean {
        synchronized(this) { return cancelled }
    }

    fun setCancelled(cancelled: Boolean) {
        synchronized(this) { this.cancelled = cancelled }
    }

    fun isInflating(): Boolean {
        synchronized(this) { return inflating }
    }

    fun setInflating(inflating: Boolean) {
        synchronized(this) { this.inflating = inflating }
    }

    override fun toString(): String {
        return "AsyncInflateItem(inflateKey='$inflateKey', layoutResId=$layoutResId, parent=$parent, callback=$callback, inflatedView=$inflatedView, cancelled=$cancelled, inflating=$inflating)"
    }


    interface OnInflateFinishedCallback {
        fun onInflateFinished(item: AsyncInflateItem)
    }
}

================================================
FILE: app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateKey.kt
================================================
package com.xj.anchortask.asyncInflate


/**
 *   @author  pjt
 */
object LaunchInflateKey {

    /**
     * 首页Fragment
     * [R.layout.fragment_tabs]
     */
    const val LAUNCH_FRAGMENT_MAIN = "launch_fragment_main"
    const val LAUNCH_ACTIVITY = "async_activity"

}

================================================
FILE: app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateManager.kt
================================================
package com.xj.anchortask.asyncInflate

import android.content.Context
import android.content.MutableContextWrapper
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import java.util.concurrent.*

/**
 * Created by jun xu on 4/1/21
 *
 *
 * 用来提供子线程inflate view的功能,避免某个view层级太深太复杂,主线程inflate会耗时很长,
 * 实就是对 AsyncLayoutInflater进行了抽取和封装
 */
class AsyncInflateManager private constructor() {

    private val mInflateMap //保存inflateKey以及InflateItem,里面包含所有要进行inflate的任务
            : ConcurrentHashMap<String, AsyncInflateItem?> = ConcurrentHashMap()
    private val mInflateLatchMap: ConcurrentHashMap<String, CountDownLatch> = ConcurrentHashMap()

    companion object {
        private const val TAG = "AsyncInflateManager"
        private val cpuCount = Runtime.getRuntime().availableProcessors()
        val threadPool = ThreadPoolExecutor(
            cpuCount,
            cpuCount * 2,
            5,
            TimeUnit.SECONDS,
            LinkedBlockingDeque()
        ).apply {
            allowCoreThreadTimeOut(true)
        }

        @JvmStatic
        val instance by lazy {
            AsyncInflateManager()
        }

        /**
         * 空方法,为了可以提前加载 AsyncInflateManager,并初始化 mThreadPool
         */
        fun init() {

        }
    }

    /**
     * 用来获得异步inflate出来的view
     *
     * @param context
     * @param layoutResId 需要拿的layoutId
     * @param parent      container
     * @param inflateKey  每一个View会对应一个inflateKey,因为可能许多地方用的同一个 layout,但是需要inflate多个,用InflateKey进行区分
     * @param inflater    外部传进来的inflater,外面如果有inflater,传进来,用来进行可能的SyncInflate,
     * @return 最后inflate出来的view
     */
    @UiThread
    fun getInflatedView(
        context: Context?,
        layoutResId: Int,
        parent: ViewGroup?,
        inflateKey: String?,
        inflater: LayoutInflater
    ): View {
        if (!TextUtils.isEmpty(inflateKey) && mInflateMap.containsKey(inflateKey)) {
            val item = mInflateMap[inflateKey]
            val latch = mInflateLatchMap[inflateKey]
            if (item != null) {
                val resultView = item.inflatedView
                if (resultView != null) {
                    //拿到了view直接返回
                    removeInflateKey(item)
                    replaceContextForView(resultView, context)
                    Log.i(TAG, "getInflatedView from cache: inflateKey is $inflateKey")
                    return resultView
                }

                if (item.isInflating() && latch != null) {
                    //没拿到view,但是在inflate中,等待返回
                    try {
                        latch.await()
                    } catch (e: InterruptedException) {
                        Log.e(TAG, e.message, e)
                    }

                    val inflatedView = item.inflatedView
                    if (inflatedView != null) {
                        removeInflateKey(item)
                        Log.i(TAG, "getInflatedView from OtherThread: inflateKey is $inflateKey")
                        replaceContextForView(inflatedView, context)
                        return inflatedView
                    }

                }

                //如果还没开始inflate,则设置为false,UI线程进行inflate
                item.setCancelled(true)
            }
        }
        Log.i(TAG, "getInflatedView from UI: inflateKey is $inflateKey")
        //拿异步inflate的View失败,UI线程inflate
        return inflater.inflate(layoutResId, parent, false)
    }

    /**
     * 如果  inflater初始化时是传进来的application,inflate出来的 view 的 context 没法用来 startActivity,
     * 因此用 MutableContextWrapper 进行包装,后续进行替换
     */
    private fun replaceContextForView(
        inflatedView: View?,
        context: Context?
    ) {
        if (inflatedView == null || context == null) {
            return
        }
        val cxt = inflatedView.context
        if (cxt is MutableContextWrapper) {
            cxt.baseContext = context
        }
    }


    fun asyncInflate(
        context: Context,
        vararg items: AsyncInflateItem?
    ) {
        items.forEach { item ->
            if (item == null || item.layoutResId == 0 || mInflateMap.containsKey(item.inflateKey) || item.isCancelled() || item.isInflating()) {
                return
            }
            mInflateMap[item.inflateKey] = item
            onAsyncInflateReady(item)
            inflateWithThreadPool(context, item)
        }

    }

    fun cancel() {

    }

    fun remove(inflateKey: String) {
        mInflateMap.remove(inflateKey)
        mInflateLatchMap.remove(inflateKey)
    }

    private fun onAsyncInflateReady(item: AsyncInflateItem) {}

    private fun onAsyncInflateStart(item: AsyncInflateItem) {}

    private fun onAsyncInflateEnd(item: AsyncInflateItem, success: Boolean) {
        item.setInflating(false)
        val latch = mInflateLatchMap[item.inflateKey]
        latch?.countDown()
        if (success && item.callback != null) {
            removeInflateKey(item)

            if (item.isCancelled()) { // 已经取消了,不再回调
                return
            }

            ThreadUtils.runOnUiThread { item.callback?.onInflateFinished(item) }
        }
    }

    private fun removeInflateKey(item: AsyncInflateItem) {

    }

    private fun inflateWithThreadPool(
        context: Context,
        item: AsyncInflateItem
    ) {
        threadPool.execute {
            if (!item.isInflating() && !item.isCancelled()) {
                try {
                    onAsyncInflateStart(item)
                    item.setInflating(true)
                    mInflateLatchMap[item.inflateKey] = CountDownLatch(1)
                    val currentTimeMillis = System.currentTimeMillis()
                    item.inflatedView =
                        BasicInflater(context).inflate(item.layoutResId, item.parent, false)
                    onAsyncInflateEnd(item, true)
                    val l = System.currentTimeMillis() - currentTimeMillis
                    Log.i(TAG, "inflateWithThreadPool: inflateKey is ${item.inflateKey}, time is ${l}")
                } catch (e: RuntimeException) {
                    Log.e(
                        TAG,
                        "Failed to inflate resource in the background! Retrying on the UI thread",
                        e
                    )
                    onAsyncInflateEnd(item, false)
                }
            }
        }
    }

    /**
     * copy from AsyncLayoutInflater - actual inflater
     */
    private class BasicInflater(context: Context?) :
        LayoutInflater(context) {
        override fun cloneInContext(newContext: Context): LayoutInflater {
            return BasicInflater(newContext)
        }

        @Throws(ClassNotFoundException::class)
        override fun onCreateView(
            name: String,
            attrs: AttributeSet
        ): View {
            for (prefix in sClassPrefixList) {
                try {
                    val view = this.createView(name, prefix, attrs)
                    if (view != null) {
                        return view
                    }
                } catch (ignored: ClassNotFoundException) {
                }
            }
            return super.onCreateView(name, attrs)
        }

        companion object {
            private val sClassPrefixList =
                arrayOf("android.widget.", "android.webkit.", "android.app.")
        }
    }
}

================================================
FILE: app/src/main/java/com/xj/anchortask/asyncInflate/ThreadUtils.java
================================================
package com.xj.anchortask.asyncInflate;

import android.os.Handler;
import android.os.Looper;

/**
 * Created by jun xu on 4/1/21.
 */
public class ThreadUtils {

    public static void runOnUiThread(Runnable r) {
        if (isMainThread()) {
            r.run();
        } else {
            LazyHolder.sUiThreadHandler.post(r);
        }
    }

    public static void runOnUiThreadAtFront(Runnable r) {
        if (isMainThread()) {
            r.run();
        } else {
            LazyHolder.sUiThreadHandler.postAtFrontOfQueue(r);
        }
    }

    public static void runOnUiThread(Runnable r, long delay) {
        LazyHolder.sUiThreadHandler.postDelayed(r, delay);
    }

    public static void postOnUiThread(Runnable r) {
        LazyHolder.sUiThreadHandler.post(r);
    }

    public static void removeCallbacks(Runnable r) {
        LazyHolder.sUiThreadHandler.removeCallbacks(r);
    }

    public static boolean isMainThread() {
        return Looper.getMainLooper() == Looper.myLooper();
    }

    private static class LazyHolder {
        private static Handler sUiThreadHandler = new Handler(Looper.getMainLooper());
    }
}


================================================
FILE: app/src/main/java/com/xj/anchortask/asyncInflate/page/AsyncFragment.kt
================================================
package com.xj.anchortask.asyncInflate.page

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.xj.anchortask.R
import com.xj.anchortask.asyncInflate.AsyncInflateManager
import com.xj.anchortask.asyncInflate.LaunchInflateKey.LAUNCH_FRAGMENT_MAIN

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
private const val TAG = "AsyncFragment"


/**
 * A simple [Fragment] subclass.
 * Use the [AsyncFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class AsyncFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }


    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val startTime = System.currentTimeMillis()
        val homeFragmentOpen = AsyncUtils.isHomeFragmentOpen()
        val inflatedView: View

        val isOpen = AsyncUtils.isHomeFragmentOpen()
        if (isOpen){
            AsyncUtils.asyncInflate(context)
        }

//        inflatedView = AsyncInflateManager.instance.getInflatedView(
//            context,
//            R.layout.fragment_asny,
//            container,
//            LAUNCH_FRAGMENT_MAIN,
//            inflater
//        )
//
//        Log.i(
//            TAG,
//            "onCreateView: homeFragmentOpen is $homeFragmentOpen, timeInstance is ${System.currentTimeMillis() - startTime}, ${inflatedView.context}"
//        )
//        return inflatedView
        return inflater.inflate(R.layout.fragment_asny, container, false)
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment AsnyFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
            AsyncFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putString(ARG_PARAM2, param2)
                }
            }
    }
}

================================================
FILE: app/src/main/java/com/xj/anchortask/asyncInflate/page/AsyncUtils.kt
================================================
package com.xj.anchortask.asyncInflate.page

import android.content.Context
import com.xj.anchortask.R
import com.xj.anchortask.asyncInflate.AsyncInflateItem
import com.xj.anchortask.asyncInflate.AsyncInflateManager
import com.xj.anchortask.asyncInflate.LaunchInflateKey.LAUNCH_FRAGMENT_MAIN
import com.xj.anchortask.getSP

/**
 * Created by jun xu on 4/1/21.
 */
object AsyncUtils {

    fun asyncInflate(context: Context?) {
        context ?: return
        val asyncInflateItem =
            AsyncInflateItem(
                LAUNCH_FRAGMENT_MAIN,
                R.layout.fragment_asny,
                null,
                null
            )
        AsyncInflateManager.instance.asyncInflate(context, asyncInflateItem)
    }

    fun isHomeFragmentOpen() =
        getSP("async_config").getBoolean("home_fragment_switch", true)
}

================================================
FILE: app/src/main/java/com/xj/anchortask/flowlayout/FlowLayoutDemo.kt
================================================
package com.xj.anchortask.flowlayout

import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.xj.anchortask.R
import java.util.*


class FlowLayoutDemo : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_flow_layout_demo)

        val tagGroup = findViewById<TagGroup>(R.id.tag_group)
        val texts: List<String> = Arrays.asList(
            "zhang",
            "phil",
            "csdn",
            "android",
            "zhang",
            "phil",
            "csdn",
            "android",
            "zhang",
            "phil",
            "csdn",
            "android"
        )
        val colors: List<Int> = Arrays.asList(
            Color.RED,
            Color.DKGRAY,
            Color.BLUE,
            Color.RED,
            Color.DKGRAY,
            Color.BLUE,
            Color.RED,
            Color.DKGRAY,
            Color.BLUE,
            Color.RED,
            Color.DKGRAY,
            Color.BLUE
        )
        tagGroup.setTagView(texts, colors)
    }
}

================================================
FILE: app/src/main/java/com/xj/anchortask/flowlayout/TagGroup.java
================================================
package com.xj.anchortask.flowlayout;

/**
 * Created by jun xu on 4/19/21.
 */

import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.google.android.flexbox.FlexDirection;
import com.google.android.flexbox.FlexWrap;
import com.google.android.flexbox.FlexboxLayout;
import com.google.android.flexbox.JustifyContent;

import java.util.ArrayList;
import java.util.List;

//https://blog.csdn.net/qq_36699930/article/details/112249170
public class TagGroup extends FlexboxLayout {
    private List<String> mTexts;
    private List<Integer> mColors;
    private Context mContext;
    private int TAG_VIEW_COUNT = 0;

    public TagGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
    }

    private void init() {
        mTexts = new ArrayList<>();
        mColors = new ArrayList<>();

        this.setFlexDirection(FlexDirection.ROW);
        this.setJustifyContent(JustifyContent.FLEX_START);
        this.setFlexWrap(FlexWrap.WRAP);
    }

    public void setTagView(@NonNull List<String> texts, @Nullable List<Integer> colors) {
        if (texts == null || texts.size() == 0) {
            throw new RuntimeException("tag view文本字段不能为空");
        }

        this.mTexts = texts;
        TAG_VIEW_COUNT = texts.size();

        if (colors == null || colors.size() == 0) {
            for (int i = 0; i < TAG_VIEW_COUNT; i++) {
                mColors.clear();
                mColors.add(Color.WHITE);
            }
        } else {
            this.mColors = colors;
        }

        this.removeAllViews();

        for (int i = 0; i < TAG_VIEW_COUNT; i++) {
            TextView textView = makeTextView(mTexts.get(i), mColors.get(i));
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            //设置每一个子View在整体布局中与其他子View的上下左右的margin
            layoutParams.setMargins(0, 1, 5, 1);

            this.addView(textView, layoutParams);
        }

        this.invalidate();
    }

    //绘制圆角描边的TextView
    private TextView makeTextView(String s, Integer c) {
        TextView textView = new TextView(mContext);
        textView.setText(s);
        textView.setPadding(10, 5, 10, 5);

        int strokeWidth = 5; // 5px
        int roundRadius = 15; // 15px
        int strokeColor = Color.GRAY;
        int fillColor = c;

        GradientDrawable gd = new GradientDrawable();
        gd.setColor(fillColor);
        gd.setCornerRadius(roundRadius);
        gd.setStroke(strokeWidth, strokeColor);

        textView.setBackground(gd);

        return textView;
    }
}


================================================
FILE: app/src/main/java/com/xj/anchortask/viewStub/MyViewStub.kt
================================================
package com.xj.anchortask.viewStub

import ViewStubTask
import android.util.Log
import android.view.View
import com.xj.anchortask.R

/**
 * Created by jun xu on 4/1/21.
 */
class ViewStubTaskTitle(decorView: View) : ViewStubTask(decorView) {

    override fun getViewStubId(): Int {
        return R.id.vs_title
    }

    override fun onInflateFinish() {
        super.onInflateFinish()
        Log.i(TAG, "onInflateFinish: ViewStubTaskTitle")
    }


}

class ViewStubTaskContent(decorView: View) : ViewStubTask(decorView) {

    override fun getViewStubId(): Int {
        return R.id.vs_content
    }

    override fun onInflateFinish() {
        super.onInflateFinish()
        Log.i(TAG, "onInflateFinish: ViewStubTaskContent")
    }


}

class ViewStubTaskBottom(decorView: View) : ViewStubTask(decorView) {

    override fun getViewStubId(): Int {
        return R.id.vs_bottom
    }

    override fun onInflateFinish() {
        super.onInflateFinish()
        Log.i(TAG, "onInflateFinish: ViewStubTaskBottom")
    }


}

================================================
FILE: app/src/main/java/com/xj/anchortask/viewStub/ViewStubDemoActivity.kt
================================================
package com.xj.anchortask.viewStub

import ViewStubTaskManager
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.xj.anchortask.R

class ViewStubDemoActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_stub_demo)
        val decorView = this.window.decorView
        ViewStubTaskManager.instance(decorView)
            .addTask(ViewStubTaskContent(decorView))
            .addTask(ViewStubTaskTitle(decorView))
            .addTask(ViewStubTaskBottom(decorView))
            .start()
    }


}

================================================
FILE: app/src/main/java/com/xj/anchortask/viewStub/ViewStubTask.kt
================================================
import android.view.View
import android.view.ViewStub
import androidx.annotation.CallSuper
import androidx.annotation.IdRes


abstract class ViewStubTask(val decorView: View) {

    companion object {
        const val TAG = "ViewStubTask"
    }

    // ViewStub 转化的 View
    var root: View? = null
        private set

    // 数据是否准备好
    var dataIsReady: Boolean = false

    // ViewStub 是否转化完成
    var onInflateListener: ViewStub.OnInflateListener? = null

    // 当前的 viewStub
    var viewStub: ViewStub? = null
        private set

    // 当前 ViewStub 是否转化完成
    val isInflated: Boolean get() = this.root != null

    abstract fun getViewStubId(): Int


    private fun setUpViewStub(viewStub: ViewStub) {
        this.viewStub = viewStub
        val proxyListener: ViewStub.OnInflateListener = ViewStub.OnInflateListener { stub, inflated ->
                this@ViewStubTask.root = inflated
                this@ViewStubTask.viewStub = null
                onInflateListener?.onInflate(stub, inflated)
                onInflateFinish()
            }
        viewStub.setOnInflateListener(proxyListener)
    }

    /**
     * 调用这个方法,会开始转化 ViewStub
     */
    fun inflate(): View? {
        if (this.root != null) {
            return this.root!!
        }
        val viewStub: ViewStub? = decorView.findViewById(getViewStubId())
        viewStub?.let {
            setUpViewStub(viewStub)
            return viewStub.inflate()
        } ?: kotlin.run {
            return null
        }
    }

    /**
     * ViewStub 转化完成回调
     */
    open fun onInflateFinish() {

    }

    @CallSuper
    open fun onDetach() {

    }


    @CallSuper
    open fun onDataReady() {
        dataIsReady = true
    }

    // 使用 ViewStub ,建议使用 findViewById,如果直接调用 Activity 或者 根布局的 findViewById,当我们的 view 还没有 add 进入的时候,这时候会为 null
    fun <T : View?> findViewById(@IdRes id: Int): T? {
        if (!isInflated) {
            return null
        }
        return if (id == View.NO_ID) {
            null
        } else {
            root?.findViewById<T>(id)
        }

    }


}

================================================
FILE: app/src/main/java/com/xj/anchortask/viewStub/ViewStubTaskManager.kt
================================================
import android.view.View
import androidx.core.view.ViewCompat
import java.util.concurrent.CopyOnWriteArrayList

/**
 * Created by jun xu on 3/31/21
 */
class ViewStubTaskManager private constructor(val decorView: View) : Runnable {

    private var iViewStubTask: IViewStubTask? = null
    
    companion object {

        const val TAG = "ViewStubTaskManager"

        @JvmStatic
        fun instance(decorView: View): ViewStubTaskManager {
            return ViewStubTaskManager(decorView)
        }
    }

    private val queue: MutableList<ViewStubTask> = CopyOnWriteArrayList()
    private val list: MutableList<ViewStubTask> = CopyOnWriteArrayList()


    fun setCallBack(iViewStubTask: IViewStubTask?): ViewStubTaskManager {
        this.iViewStubTask = iViewStubTask
        return this
    }

    fun addTask(viewStubTasks: List<ViewStubTask>): ViewStubTaskManager {
        queue.addAll(viewStubTasks)
        list.addAll(viewStubTasks)
        return this
    }

    fun addTask(viewStubTask: ViewStubTask): ViewStubTaskManager {
        queue.add(viewStubTask)
        list.add(viewStubTask)
        return this
    }


    fun start() {
        if (isEmpty()) {
            return
        }
        iViewStubTask?.beforeTaskExecute()
        // 指定 decorView 绘制下一帧的时候会回调里面的 runnable
        ViewCompat.postOnAnimation(decorView, this)
    }

    fun stop() {
        queue.clear()
        list.clear()
        decorView.removeCallbacks(null)
    }

    private fun isEmpty() = queue.isEmpty() || queue.size == 0

    override fun run() {
        if (!isEmpty()) {
            // 当队列不为空的时候,先加载当前 viewStubTask
            val viewStubTask = queue.removeAt(0)
            viewStubTask.inflate()
            iViewStubTask?.onTaskExecute(viewStubTask)
            // 加载完成之后,再 postOnAnimation 加载下一个
            ViewCompat.postOnAnimation(decorView, this)
        } else {
            iViewStubTask?.afterTaskExecute()
        }

    }

    fun notifyOnDetach() {
        list.forEach {
            it.onDetach()
        }
        list.clear()
    }

    fun notifyOnDataReady() {
        list.forEach {
            it.onDataReady()
        }
    }

}

interface IViewStubTask {

    fun beforeTaskExecute()

    fun onTaskExecute(viewStubTask: ViewStubTask)

    fun afterTaskExecute()


}

================================================
FILE: app/src/main/res/drawable/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
    <path
        android:fillColor="#3DDC84"
        android:pathData="M0,0h108v108h-108z" />
    <path
        android:fillColor="#00000000"
        android:pathData="M9,0L9,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,0L19,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M29,0L29,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M39,0L39,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M49,0L49,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M59,0L59,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M69,0L69,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M79,0L79,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M89,0L89,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M99,0L99,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,9L108,9"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,19L108,19"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,29L108,29"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,39L108,39"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,49L108,49"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,59L108,59"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,69L108,69"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,79L108,79"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,89L108,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,99L108,99"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,29L89,29"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,39L89,39"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,49L89,49"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,59L89,59"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,69L89,69"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,79L89,79"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M29,19L29,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M39,19L39,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M49,19L49,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M59,19L59,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M69,19L69,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M79,19L79,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
</vector>


================================================
FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
        <aapt:attr name="android:fillColor">
            <gradient
                android:endX="85.84757"
                android:endY="92.4963"
                android:startX="42.9492"
                android:startY="49.59793"
                android:type="linear">
                <item
                    android:color="#44000000"
                    android:offset="0.0" />
                <item
                    android:color="#00000000"
                    android:offset="1.0" />
            </gradient>
        </aapt:attr>
    </path>
    <path
        android:fillColor="#FFFFFF"
        android:fillType="nonZero"
        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
        android:strokeWidth="1"
        android:strokeColor="#00000000" />
</vector>

================================================
FILE: app/src/main/res/layout/activity_anchortask_test.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".anchorTask.AnchorTaskTestActivity">


    <Button
        android:id="@+id/btn_execute"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开始执行"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_execute2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开始执行(1.1)"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_execute" />

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_execute2" />

    <TextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="15dp"
        android:text="Hello World!"
        android:textSize="14sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/text" />

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/activity_async.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".asyncInflate.page.AsyncFragment">


    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_light" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_dark" />

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello_blank_fragment" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_light" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_dark" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_bright" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/black" />
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:background="@android:color/holo_red_dark" />
    </RelativeLayout>

</LinearLayout>

================================================
FILE: app/src/main/res/layout/activity_flow_layout_demo.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".flowlayout.FlowLayoutDemo">

    <com.xj.anchortask.flowlayout.TagGroup
        android:id="@+id/tag_group"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </com.xj.anchortask.flowlayout.TagGroup>

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_view_stub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳转 ViewStub"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_anchortask"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳转 AnchorTask"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_view_stub" />


    <Button
        android:id="@+id/btn_async"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="测试异步加载"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_view_stub" />

    <Button
        android:id="@+id/btn_flow_layout_demo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳转 Flow Layout Demo"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_view_stub" />

    <LinearLayout
        android:id="@+id/ll_switch"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_asnyc_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="首页 Fragment 开启预加载" />

        <androidx.appcompat.widget.SwitchCompat
            android:id="@+id/switch_async"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

    <fragment
        android:id="@+id/async_fragment"
        android:name="com.xj.anchortask.asyncInflate.page.AsyncFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </fragment>


</LinearLayout>

================================================
FILE: app/src/main/res/layout/activity_view_stub_demo.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".viewStub.ViewStubDemoActivity">

    <ViewStub
        android:id="@+id/vs_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/layout_title" />

    <ViewStub
        android:id="@+id/vs_content"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout="@layout/layout_content" />

    <ViewStub
        android:id="@+id/vs_bottom"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/layout_bottom" />

</LinearLayout>

================================================
FILE: app/src/main/res/layout/fragment_asny.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".asyncInflate.page.AsyncFragment">


    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_light" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_dark" />

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello_blank_fragment" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_light" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_dark" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/holo_blue_bright" />

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@android:color/black" />
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:background="@android:color/holo_red_dark" />
    </RelativeLayout>

</LinearLayout>

================================================
FILE: app/src/main/res/layout/layout_bottom.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="50dp">


    <TextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="15dp"
        android:text="我是 Bottom"
        android:textColor="@android:color/black"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/layout_content.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <TextView
        android:background="@android:color/holo_blue_light"
        android:id="@+id/text2"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="15dp"
        android:text="我是 Content"
        android:textSize="30sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/layout/layout_title.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="50dp">


    <TextView
        android:id="@+id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="15dp"
        android:text="ViewStubTitle"
        android:textSize="18sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background" />
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background" />
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#6200EE</color>
    <color name="colorPrimaryDark">#3700B3</color>
    <color name="colorAccent">#03DAC5</color>
</resources>

================================================
FILE: app/src/main/res/values/strings.xml
================================================
<resources>
    <string name="app_name">AnchorTask</string>
    <!-- TODO: Remove or change this placeholder text -->
    <string name="hello_blank_fragment">Hello blank fragment</string>
</resources>

================================================
FILE: app/src/main/res/values/styles.xml
================================================
<resources>
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

================================================
FILE: app/src/test/java/com/xj/anchortask/ExampleUnitTest.kt
================================================
package com.xj.anchortask

import org.junit.Test

import org.junit.Assert.*

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
class ExampleUnitTest {
    @Test
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}

================================================
FILE: app/test.gradle
================================================
task myTask1 {
    println "configure task1"
}

================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: "config.gradle"

buildscript {
    ext.kotlin_version = "1.3.72"
    repositories {
        google()
        jcenter()
        maven {
            url "https://dl.bintray.com/xujun94/maven/"
        }
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.0.2"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
//        classpath 'com.novoda:bintray-release:0.9.2'
        classpath 'com.github.panpf.bintray-publish:bintray-publish:1.0.0'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files

    }
}

allprojects {
    repositories {
        google()
        jcenter()
//        maven {
//            url 'file://Users/junxu/Desktop/gitProject/my-github/AnchorTask/repository'
//        }
        maven {
            url uri('../repository')
        }
        maven {
            url "https://dl.bintray.com/xujun94/maven/"
        }
    }


}

task clean(type: Delete) {
    delete rootProject.buildDir
}

================================================
FILE: config.gradle
================================================
ext {
    // Sdk and tools
    anchorTaskPublicVersion = '1.1.0'
    anchorTaskVersion = anchorTaskPublicVersion


    dep = [
            androidPlugin: 'com.android.tools.build:gradle:2.1.3',


    ]

    isCi = "true".equals(System.getenv('CI'))
}


================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Mon Feb 01 16:20:03 CST 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip


================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official



================================================
FILE: gradlew
================================================
#!/usr/bin/env sh

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn () {
    echo "$*"
}

die () {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
  NONSTOP* )
    nonstop=true
    ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=$((i+1))
    done
    case $i in
        (0) set -- ;;
        (1) set -- "$args0" ;;
        (2) set -- "$args0" "$args1" ;;
        (3) set -- "$args0" "$args1" "$args2" ;;
        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Escape application args
save () {
    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
    echo " "
}
APP_ARGS=$(save "$@")

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"

# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
  cd "$(dirname "$0")"
fi

exec "$JAVACMD" "$@"


================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windows variants

if not "%OS%" == "Windows_NT" goto win9xME_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: localconfig.gradle
================================================
task test22{
    parseLocalProperties()
}

Properties loadProperties(String fileName) {
    File file = rootProject.file(fileName)
    println("parseLocalProperties start")
    if (file.exists()) {
        InputStream inputStream = rootProject.file(fileName).newDataInputStream();
        Properties properties = new Properties()
        properties.load(inputStream)
        return properties
    }
    return null
}

def parseLocalProperties() {
    File file = rootProject.file('local.properties')
    println("parseLocalProperties start")
    if (file.exists()) {
        InputStream inputStream = rootProject.file('local.properties').newDataInputStream();
        Properties properties = new Properties()
        properties.load(inputStream)

        def containsKey = properties.containsKey("packagename")
        println("parseLocalProperties containsKey is ${containsKey}")
        if (containsKey) {
            println 'packageName:' + properties.getProperty("packagename")
            ext.packagename = properties.getProperty("packagename")
            println 'packageName:' + project["packagename"]
        }
    }
}

// 设置属性
def static getValue(Properties properties, String name, Object defValue) {
    if (properties == null) {
        return defValue
    }

    if (properties.containsKey(name)) {
        String value = properties.getProperty(name)
        return value
    }
    return defValue
}

// 方法复用
ext {
    parseLocalProperties = this.&parseLocalProperties
    loadProperties = this.&loadProperties
    getValue = this.&getValue
}

================================================
FILE: publish-mavencentral.gradle
================================================
apply plugin: 'maven-publish'
apply plugin: 'signing'


task androidSourcesJar(type: Jar) {
    classifier = 'sources'
    from android.sourceSets.main.java.source
}

ext["signing.keyId"] = ''
ext["signing.password"] = ''
ext["signing.secretKeyRingFile"] = ''
ext["ossrhUsername"] = ''
ext["ossrhPassword"] = ''

File secretPropsFile = project.rootProject.file('local.properties')
if (secretPropsFile.exists()) {
    println "Found secret props file, loading props"
    Properties p = new Properties()
    p.load(new FileInputStream(secretPropsFile))
    p.each { name, value ->
        ext[name] = value
        println("name is " + name + ";value is " + value)
    }
} else {
    println "No props file, loading env vars"
}
publishing {
    publications {
        release(MavenPublication) {
            // The coordinates of the library, being set from variables that
            // we'll set up in a moment
            groupId PUBLISH_GROUP_ID
            artifactId PUBLISH_ARTIFACT_ID
            version PUBLISH_VERSION

            // Two artifacts, the `aar` and the sources
            artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
            artifact androidSourcesJar

            // Self-explanatory metadata for the most part
            pom {
                name = PUBLISH_ARTIFACT_ID
                description = 'AnchorTask'
                // If your project has a dedicated site, use its URL here
                url = 'https://github.com/gdutxiaoxu/'
                licenses {
                    license {
                        //协议类型,一般默认Apache License2.0的话不用改:
                        name = 'The Apache License, Version 2.0'
                        url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
                    }
                }
                developers {
                    developer {
                        name = 'xujun'
                        email = 'gdutxiaoxu@163.com'
                    }
                }
                // Version control info, if you're using GitHub, follow the format as seen here
                scm {
                    //修改成你的Git地址:
                    connection = 'scm:git:github.com/gdutxiaoxu/AnchorTask.git'
                    developerConnection = 'scm:git:ssh://github.com/gdutxiaoxu/AnchorTask.git'
                    //分支地址:
                    url = 'https://github.com/gdutxiaoxu/AnchorTask/tree/master'
                }
                // A slightly hacky fix so that your POM will include any transitive dependencies
                // that your library builds upon
                withXml {
                    def dependenciesNode = asNode().appendNode('dependencies')

                    project.configurations.implementation.allDependencies.each {
                        println(it)
                        if (it.name == "unspecified") {
                            // 忽略无法识别的
                            return
                        }

                        def dependencyNode = dependenciesNode.appendNode('dependency')
                        dependencyNode.appendNode('groupId', it.group)
                        dependencyNode.appendNode('artifactId', it.name)
                        dependencyNode.appendNode('version', it.version)
                    }
                }
            }
        }
    }
    repositories {
        // The repository to publish to, Sonatype/MavenCentral
        maven {
            // This is an arbitrary name, you may also use "mavencentral" or
            // any other name that's descriptive for you

            def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
            def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
            // You only need this if you want to publish snapshots, otherwise just set the URL
            // to the release repo directly
            url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl

            // The username and password we've fetched earlier
            credentials {
                username ossrhUsername
                password ossrhPassword
            }
        }
    }
}
signing {
    sign publishing.publications
}


================================================
FILE: settings.gradle
================================================
include ':anchortasklibrary'
include ':app'
rootProject.name = "AnchorTask"
Download .txt
gitextract_gkjzk3zl/

├── .gitignore
├── README.md
├── anchortasklibrary/
│   ├── .gitignore
│   ├── build.gradle
│   ├── consumer-rules.pro
│   ├── proguard-rules.pro
│   └── src/
│       ├── androidTest/
│       │   └── java/
│       │       └── com/
│       │           └── xj/
│       │               └── anchortask/
│       │                   └── library/
│       │                       └── ExampleInstrumentedTest.kt
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── com/
│       │           └── xj/
│       │               └── anchortask/
│       │                   └── library/
│       │                       ├── AnchorConfig.kt
│       │                       ├── AnchorProject.kt
│       │                       ├── AnchorTaskException.kt
│       │                       ├── AnchorTaskRunnable.kt
│       │                       ├── AnchorTaskUtils.kt
│       │                       ├── IAnchorTask.kt
│       │                       ├── IAnchorTaskCreator.kt
│       │                       ├── ProjectBuilder.kt
│       │                       ├── ProjectListener.kt
│       │                       ├── TaskExecutorManager.kt
│       │                       ├── ThreadUtils.kt
│       │                       ├── log/
│       │                       │   └── LogUtils.kt
│       │                       └── monitor/
│       │                           ├── ExecuteMonitor.kt
│       │                           └── OnGetMonitorRecordCallback.kt
│       └── test/
│           └── java/
│               └── com/
│                   └── xj/
│                       └── anchortask/
│                           └── library/
│                               └── ExampleUnitTest.kt
├── app/
│   ├── .gitignore
│   ├── build.gradle
│   ├── proguard-rules.pro
│   ├── src/
│   │   ├── androidTest/
│   │   │   └── java/
│   │   │       └── com/
│   │   │           └── xj/
│   │   │               └── anchortask/
│   │   │                   └── ExampleInstrumentedTest.kt
│   │   ├── main/
│   │   │   ├── AndroidManifest.xml
│   │   │   ├── java/
│   │   │   │   └── com/
│   │   │   │       └── xj/
│   │   │   │           └── anchortask/
│   │   │   │               ├── LogUtils.kt
│   │   │   │               ├── LoggerPrinter.kt
│   │   │   │               ├── MainActivity.kt
│   │   │   │               ├── MyApplication.kt
│   │   │   │               ├── SPUtil.kt
│   │   │   │               ├── anchorTask/
│   │   │   │               │   ├── AnchorTaskTestActivity.kt
│   │   │   │               │   ├── ApplicationAnchorTaskCreator.kt
│   │   │   │               │   └── TestTaskUtils.kt
│   │   │   │               ├── appstartup/
│   │   │   │               │   └── InitializerSample.kt
│   │   │   │               ├── asyncInflate/
│   │   │   │               │   ├── AsyncActivity.kt
│   │   │   │               │   ├── AsyncInflateItem.kt
│   │   │   │               │   ├── AsyncInflateKey.kt
│   │   │   │               │   ├── AsyncInflateManager.kt
│   │   │   │               │   ├── ThreadUtils.java
│   │   │   │               │   └── page/
│   │   │   │               │       ├── AsyncFragment.kt
│   │   │   │               │       └── AsyncUtils.kt
│   │   │   │               ├── flowlayout/
│   │   │   │               │   ├── FlowLayoutDemo.kt
│   │   │   │               │   └── TagGroup.java
│   │   │   │               └── viewStub/
│   │   │   │                   ├── MyViewStub.kt
│   │   │   │                   ├── ViewStubDemoActivity.kt
│   │   │   │                   ├── ViewStubTask.kt
│   │   │   │                   └── ViewStubTaskManager.kt
│   │   │   └── res/
│   │   │       ├── drawable/
│   │   │       │   └── ic_launcher_background.xml
│   │   │       ├── drawable-v24/
│   │   │       │   └── ic_launcher_foreground.xml
│   │   │       ├── layout/
│   │   │       │   ├── activity_anchortask_test.xml
│   │   │       │   ├── activity_async.xml
│   │   │       │   ├── activity_flow_layout_demo.xml
│   │   │       │   ├── activity_main.xml
│   │   │       │   ├── activity_view_stub_demo.xml
│   │   │       │   ├── fragment_asny.xml
│   │   │       │   ├── layout_bottom.xml
│   │   │       │   ├── layout_content.xml
│   │   │       │   └── layout_title.xml
│   │   │       ├── mipmap-anydpi-v26/
│   │   │       │   ├── ic_launcher.xml
│   │   │       │   └── ic_launcher_round.xml
│   │   │       └── values/
│   │   │           ├── colors.xml
│   │   │           ├── strings.xml
│   │   │           └── styles.xml
│   │   └── test/
│   │       └── java/
│   │           └── com/
│   │               └── xj/
│   │                   └── anchortask/
│   │                       └── ExampleUnitTest.kt
│   └── test.gradle
├── build.gradle
├── config.gradle
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── localconfig.gradle
├── publish-mavencentral.gradle
└── settings.gradle
Download .txt
SYMBOL INDEX (13 symbols across 2 files)

FILE: app/src/main/java/com/xj/anchortask/asyncInflate/ThreadUtils.java
  class ThreadUtils (line 9) | public class ThreadUtils {
    method runOnUiThread (line 11) | public static void runOnUiThread(Runnable r) {
    method runOnUiThreadAtFront (line 19) | public static void runOnUiThreadAtFront(Runnable r) {
    method runOnUiThread (line 27) | public static void runOnUiThread(Runnable r, long delay) {
    method postOnUiThread (line 31) | public static void postOnUiThread(Runnable r) {
    method removeCallbacks (line 35) | public static void removeCallbacks(Runnable r) {
    method isMainThread (line 39) | public static boolean isMainThread() {
    class LazyHolder (line 43) | private static class LazyHolder {

FILE: app/src/main/java/com/xj/anchortask/flowlayout/TagGroup.java
  class TagGroup (line 26) | public class TagGroup extends FlexboxLayout {
    method TagGroup (line 32) | public TagGroup(Context context, AttributeSet attrs) {
    method init (line 38) | private void init() {
    method setTagView (line 47) | public void setTagView(@NonNull List<String> texts, @Nullable List<Int...
    method makeTextView (line 79) | private TextView makeTextView(String s, Integer c) {
Condensed preview — 78 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (141K chars).
[
  {
    "path": ".gitignore",
    "chars": 208,
    "preview": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor."
  },
  {
    "path": "README.md",
    "chars": 3033,
    "preview": "> 我的 CSDN 博客:https://blog.csdn.net/gdutxiaoxu <br>\n> 我的掘金:https://juejin.im/user/2207475076966584  <br>\n> github: https:"
  },
  {
    "path": "anchortasklibrary/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "anchortasklibrary/build.gradle",
    "chars": 2148,
    "preview": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-android-extensions'\napply plugi"
  },
  {
    "path": "anchortasklibrary/consumer-rules.pro",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "anchortasklibrary/proguard-rules.pro",
    "chars": 750,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "anchortasklibrary/src/androidTest/java/com/xj/anchortask/library/ExampleInstrumentedTest.kt",
    "chars": 682,
    "preview": "package com.xj.anchortask.library\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.ju"
  },
  {
    "path": "anchortasklibrary/src/main/AndroidManifest.xml",
    "chars": 128,
    "preview": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.xj.anchortask.library\">\n\n    /\n</m"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorConfig.kt",
    "chars": 283,
    "preview": "package com.xj.anchortask.library\n\nimport com.xj.anchortask.library.log.LogUtils\n\n/**\n * Created by jun xu on 2/9/21.\n *"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorProject.kt",
    "chars": 7995,
    "preview": "package com.xj.anchortask.library\n\nimport android.content.Context\nimport com.xj.anchortask.library.log.LogUtils\nimport c"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskException.kt",
    "chars": 185,
    "preview": "package com.xj.anchortask.library\n\nimport java.lang.RuntimeException\n\n/**\n * Created by jun xu on 2/1/21.\n */\nclass Anch"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskRunnable.kt",
    "chars": 797,
    "preview": "package com.xj.anchortask.library\n\nimport android.os.Process\nimport android.os.SystemClock\n\n/**\n * Created by jun xu on "
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskUtils.kt",
    "chars": 2549,
    "preview": "package com.xj.anchortask.library\n\n\nimport com.xj.anchortask.library.log.LogUtils\nimport java.util.*\nimport kotlin.colle"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/IAnchorTask.kt",
    "chars": 3189,
    "preview": "package com.xj.anchortask.library\n\nimport android.os.Process\nimport androidx.annotation.CallSuper\nimport androidx.annota"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/IAnchorTaskCreator.kt",
    "chars": 774,
    "preview": "package com.xj.anchortask.library\n\n/**\n * Created by jun xu on 2/9/21.\n */\nopen interface IAnchorTaskCreator {\n    /**\n "
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/ProjectBuilder.kt",
    "chars": 344,
    "preview": "package com.xj.anchortask.library\n\nimport android.content.Context\nimport com.xj.anchortask.library.log.LogUtils\nimport j"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/ProjectListener.kt",
    "chars": 552,
    "preview": "package com.xj.anchortask.library\n\n/**\n * Created by jun xu on 2/8/21.\n */\ninterface OnProjectExecuteListener {\n    /**\n"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/TaskExecutorManager.kt",
    "chars": 1782,
    "preview": "package com.xj.anchortask.library\n\nimport java.util.concurrent.*\n\nclass TaskExecutorManager private constructor() {\n    "
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/ThreadUtils.kt",
    "chars": 1046,
    "preview": "package com.xj.anchortask.library\n\nimport android.os.Handler\nimport android.os.Looper\n\nobject ThreadUtils {\n\n    fun run"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/log/LogUtils.kt",
    "chars": 1803,
    "preview": "package com.xj.anchortask.library.log\n\nimport android.util.Log\n\n/**\n * Created by jun xu on 2/2/21.\n */\nobject LogUtils "
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/monitor/ExecuteMonitor.kt",
    "chars": 2853,
    "preview": "/*\n * Copyright 2018 Alibaba Group.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/monitor/OnGetMonitorRecordCallback.kt",
    "chars": 1023,
    "preview": "/*\n * Copyright 2018 Alibaba Group.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not"
  },
  {
    "path": "anchortasklibrary/src/test/java/com/xj/anchortask/library/ExampleUnitTest.kt",
    "chars": 349,
    "preview": "package com.xj.anchortask.library\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, whi"
  },
  {
    "path": "app/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "app/build.gradle",
    "chars": 1904,
    "preview": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-android-extensions'\napply f"
  },
  {
    "path": "app/proguard-rules.pro",
    "chars": 750,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "app/src/androidTest/java/com/xj/anchortask/ExampleInstrumentedTest.kt",
    "chars": 661,
    "preview": "package com.xj.anchortask\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runn"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "chars": 1339,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:to"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/LogUtils.kt",
    "chars": 4365,
    "preview": "package com.xj.anchortask\n\nimport android.util.Log\nimport org.json.JSONArray\nimport org.json.JSONException\nimport org.js"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/LoggerPrinter.kt",
    "chars": 1170,
    "preview": "package com.xj.anchortask\n\n/**\n * Created by jun xu on 2/2/21.\n */\nobject LoggerPrinter {\n\n    private val MIN_STACK_OFF"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/MainActivity.kt",
    "chars": 2038,
    "preview": "package com.xj.anchortask\n\nimport android.content.Intent\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompa"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/MyApplication.kt",
    "chars": 1661,
    "preview": "package com.xj.anchortask\n\nimport android.app.Application\nimport android.content.Context\nimport android.util.Log\nimport "
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/SPUtil.kt",
    "chars": 2639,
    "preview": "package com.xj.anchortask\n\nimport android.content.Context\nimport android.content.SharedPreferences\n\n\nfun getSP(name: Str"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/anchorTask/AnchorTaskTestActivity.kt",
    "chars": 2492,
    "preview": "package com.xj.anchortask.anchorTask\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport co"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/anchorTask/ApplicationAnchorTaskCreator.kt",
    "chars": 3542,
    "preview": "package com.xj.anchortask.anchorTask\n\nimport com.xj.anchortask.LogUtils\nimport com.xj.anchortask.library.AnchorTask\nimpo"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/anchorTask/TestTaskUtils.kt",
    "chars": 5317,
    "preview": "package com.xj.anchortask.anchorTask\n\nimport android.content.Context\nimport com.xj.anchortask.MyApplication\nimport com.x"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/appstartup/InitializerSample.kt",
    "chars": 1628,
    "preview": "package com.xj.anchortask.appstartup\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.startup.App"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/AsyncActivity.kt",
    "chars": 1007,
    "preview": "package com.xj.anchortask.asyncInflate\n\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimport "
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateItem.kt",
    "chars": 1138,
    "preview": "package com.xj.anchortask.asyncInflate\n\nimport android.view.View\nimport android.view.ViewGroup\n\n/**\n * Created by jun xu"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateKey.kt",
    "chars": 271,
    "preview": "package com.xj.anchortask.asyncInflate\n\n\n/**\n *   @author  pjt\n */\nobject LaunchInflateKey {\n\n    /**\n     * 首页Fragment\n"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateManager.kt",
    "chars": 7486,
    "preview": "package com.xj.anchortask.asyncInflate\n\nimport android.content.Context\nimport android.content.MutableContextWrapper\nimpo"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/ThreadUtils.java",
    "chars": 1146,
    "preview": "package com.xj.anchortask.asyncInflate;\n\nimport android.os.Handler;\nimport android.os.Looper;\n\n/**\n * Created by jun xu "
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/page/AsyncFragment.kt",
    "chars": 2851,
    "preview": "package com.xj.anchortask.asyncInflate.page\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.Layout"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/page/AsyncUtils.kt",
    "chars": 836,
    "preview": "package com.xj.anchortask.asyncInflate.page\n\nimport android.content.Context\nimport com.xj.anchortask.R\nimport com.xj.anc"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/flowlayout/FlowLayoutDemo.kt",
    "chars": 1164,
    "preview": "package com.xj.anchortask.flowlayout\n\nimport android.graphics.Color\nimport android.os.Bundle\nimport androidx.appcompat.a"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/flowlayout/TagGroup.java",
    "chars": 2907,
    "preview": "package com.xj.anchortask.flowlayout;\n\n/**\n * Created by jun xu on 4/19/21.\n */\n\nimport android.content.Context;\nimport "
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/viewStub/MyViewStub.kt",
    "chars": 1029,
    "preview": "package com.xj.anchortask.viewStub\n\nimport ViewStubTask\nimport android.util.Log\nimport android.view.View\nimport com.xj.a"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/viewStub/ViewStubDemoActivity.kt",
    "chars": 653,
    "preview": "package com.xj.anchortask.viewStub\n\nimport ViewStubTaskManager\nimport android.os.Bundle\nimport androidx.appcompat.app.Ap"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/viewStub/ViewStubTask.kt",
    "chars": 2063,
    "preview": "import android.view.View\nimport android.view.ViewStub\nimport androidx.annotation.CallSuper\nimport androidx.annotation.Id"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/viewStub/ViewStubTaskManager.kt",
    "chars": 2295,
    "preview": "import android.view.View\nimport androidx.core.view.ViewCompat\nimport java.util.concurrent.CopyOnWriteArrayList\n\n/**\n * C"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "chars": 5606,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:wi"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "chars": 1702,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    "
  },
  {
    "path": "app/src/main/res/layout/activity_anchortask_test.xml",
    "chars": 1771,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/activity_async.xml",
    "chars": 1819,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "app/src/main/res/layout/activity_flow_layout_demo.xml",
    "chars": 758,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "chars": 2451,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "app/src/main/res/layout/activity_view_stub_demo.xml",
    "chars": 942,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "app/src/main/res/layout/fragment_asny.xml",
    "chars": 1819,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "app/src/main/res/layout/layout_bottom.xml",
    "chars": 841,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/layout_content.xml",
    "chars": 894,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/layout/layout_title.xml",
    "chars": 796,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "chars": 272,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "chars": 272,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "chars": 207,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#6200EE</color>\n    <color name=\"color"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "chars": 200,
    "preview": "<resources>\n    <string name=\"app_name\">AnchorTask</string>\n    <!-- TODO: Remove or change this placeholder text -->\n  "
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "chars": 381,
    "preview": "<resources>\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\""
  },
  {
    "path": "app/src/test/java/com/xj/anchortask/ExampleUnitTest.kt",
    "chars": 341,
    "preview": "package com.xj.anchortask\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will "
  },
  {
    "path": "app/test.gradle",
    "chars": 46,
    "preview": "task myTask1 {\n    println \"configure task1\"\n}"
  },
  {
    "path": "build.gradle",
    "chars": 1141,
    "preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\napply from: \"config."
  },
  {
    "path": "config.gradle",
    "chars": 251,
    "preview": "ext {\n    // Sdk and tools\n    anchorTaskPublicVersion = '1.1.0'\n    anchorTaskVersion = anchorTaskPublicVersion\n\n\n    d"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 232,
    "preview": "#Mon Feb 01 16:20:03 CST 2021\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
  },
  {
    "path": "gradle.properties",
    "chars": 1164,
    "preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
  },
  {
    "path": "gradlew",
    "chars": 5296,
    "preview": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up"
  },
  {
    "path": "gradlew.bat",
    "chars": 2176,
    "preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
  },
  {
    "path": "localconfig.gradle",
    "chars": 1557,
    "preview": "task test22{\n    parseLocalProperties()\n}\n\nProperties loadProperties(String fileName) {\n    File file = rootProject.file"
  },
  {
    "path": "publish-mavencentral.gradle",
    "chars": 4244,
    "preview": "apply plugin: 'maven-publish'\napply plugin: 'signing'\n\n\ntask androidSourcesJar(type: Jar) {\n    classifier = 'sources'\n "
  },
  {
    "path": "settings.gradle",
    "chars": 75,
    "preview": "include ':anchortasklibrary'\ninclude ':app'\nrootProject.name = \"AnchorTask\""
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the gdutxiaoxu/AnchorTask GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 78 files (121.2 KB), approximately 34.1k tokens, and a symbol index with 13 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!