[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\n"
  },
  {
    "path": "README.md",
    "content": "> 我的 CSDN 博客:https://blog.csdn.net/gdutxiaoxu <br>\n> 我的掘金：https://juejin.im/user/2207475076966584  <br>\n> github: https://github.com/gdutxiaoxu/  <br>\n> 微信公众号：程序员徐公  <br>\n\n\n\n#  AnchorTask\n\n锚点任务，可以用来解决多线程加载任务依赖的问题。实现原理是使用有向无环图，常见的，比如 Android 启动优化，通常会进行多线程异步加载。\n\n\n\n\n\n# 基本使用\n\n第一步：在 moulde build.gradle 配置远程依赖\n\n\n```\nimplementation 'io.github.gdutxiaoxu:anchortask:1.1.0'\n```\n\n最新的版本号可以看这里 [lastedt version](https://github.com/gdutxiaoxu/AnchorTask/tags)\n\n# 具体使用文档\n\n\n\n## 0.1.0 版本\n\n0.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)， \n\n0.1.0 版本实现借鉴了 [android-startup](https://github.com/idisfkj/android-startup)，[AppStartFaster](https://github.com/NoEndToLF/AppStartFaster)，[AnchorTask 0.1.0 原理\n](https://github.com/gdutxiaoxu/AnchorTask/wiki/AnchorTask-0.1.0-%E5%8E%9F%E7%90%86)\n\n##  1.0.0 版本\n\n[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)\n\n[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)\n\n## 两个版本之间区别\n\n\n1. 之前的 0.1.0 版本 配置前置依赖任务，是通过 `AnchorTask getDependsTaskList` 的方式，这种方式不太直观，1.0.0 放弃了这种方式，参考阿里 `Alpha` 的方式，通过 `addTask(TASK_NAME_THREE).afterTask(TASK_NAME_ZERO, TASK_NAME_ONE)`\n2. 1.0.0 版本新增了 Project 类，并增加 `OnProjectExecuteListener` 监听\n3. 1.0.0 版本新增 `OnGetMonitorRecordCallback` 监听，方便统计各个任务的耗时\n\n\n# 实现原理\n\nAnchorTask 的原理不复杂，本质是有向无环图与多线程知识的结合。\n\n1. 根据 BFS 构建出有向无环图，并得到它的拓扑排序\n2.  在多线程执行过程中，我们是通过任务的子任务关系和 CounDownLatch 确保先后执行关系的\n    1. 前置任务没有执行完毕的话，等待，执行完毕的话，往下走\n    2. 执行任务\n    3.  通知子任务，当前任务执行完毕了，相应的计数器（入度数）要减一。\n    \n\n[Android 启动优化（一） - 有向无环图\n](https://juejin.cn/post/6926794003794903048)\n\n[Android 启动优化（二） - 拓扑排序的原理以及解题思路](https://juejin.cn/post/6930805971673415694)\n\n\n\n# 特别鸣谢\n\n在实现这个开源框架的时候，借鉴了以下开源框架的思想。AppStartFaster 主要是通过 ClassName 找到相应的 Task，而阿里 alpha 是通过 taskName 找到相应的 Task，并且需要指定 ITaskCreator。两种方式各有优缺点，没有优劣之说，具体看使用场景。\n\n[android-startup](https://github.com/idisfkj/android-startup)\n\n[alpha](https://github.com/alibaba/alpha)\n\n[AppStartFaster](https://github.com/NoEndToLF/AppStartFaster)\n\n# 系列文章\n\n这几篇文章从 0 到 1，讲解 DAG 有向无环图是怎么实现的，以及在 Android 启动优化的应用。\n\n**推荐理由：现在挺多文章一谈到启动优化，动不动就聊拓扑结构，这篇文章从数据结构到算法、到设计都给大家说清楚了，开源项目也有非常强的借鉴意义。**\n\n[Android 启动优化（一） - 有向无环图]( https://mp.weixin.qq.com/s/xWYe-uxgXTPuitYcLgXYNg)\n\n[Android 启动优化（二） - 拓扑排序的原理以及解题思路]( https://mp.weixin.qq.com/s/ShfxD_Z7M_NuWYNodn-vqA)\n\n[Android 启动优化（三）- AnchorTask 开源了]( https://mp.weixin.qq.com/s/YRUpf9jKEwIHV0A4FqltXg)\n\n[Android 启动优化（四）- AnchorTask 是怎么实现的](https://mp.weixin.qq.com/s/6RKco9JTm6ZrFyw99k9Rlg)\n\n[Android 启动优化（五）- AnchorTask 1.0.0 版本正式发布了]( https://mp.weixin.qq.com/s/0MsJa0ZepWkPUs-ymnVb-w)\n\n[Android 启动优化（六）- 深入理解布局优化](https://mp.weixin.qq.com/s/7_dQd2wGZYKWf9kHNlv2fg)\n\n**如果觉得对你有所帮助的，可以关注我的微信公众号，程序员徐公。主要更新 Android 技术，算法，职场相关的。**\n\n![](https://raw.githubusercontent.com/gdutxiaoxu/blog_pic/master/21/0120210409172003.png)\n"
  },
  {
    "path": "anchortasklibrary/.gitignore",
    "content": "/build"
  },
  {
    "path": "anchortasklibrary/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-android-extensions'\napply plugin: 'maven'\n//apply plugin: 'com.novoda.bintray-release'\n//apply plugin: 'com.github.panpf.bintray-publish'\n\n\n// 发布到本地仓库配置\nuploadArchives {\n    repositories.mavenDeployer {\n        // 配置本地仓库路径，项目根目录下的repository目录中\n        repository(url: uri('../repository'))\n        pom.groupId = \"com.xj.android\"// 唯一标识（通常为模块包名，也可以任意）\n        pom.artifactId = \"anchortask\" // 项目名称（通常为类库模块名称，也可以任意）\n        pom.version = \"${anchorTaskPublicVersion}-local\" // 版本号\n    }\n}\n\n//\n//publish {\n//    userOrg = 'xujun94'\n//    groupId = 'com.xj.android'\n//    artifactId = 'anchortask'\n//    publishVersion = \"${anchorTaskPublicVersion}\"\n//    desc = 'anchortask'\n//    website = 'https://github.com/gdutxiaoxu/anchortask'\n//}\n\n\nandroid {\n    compileSdkVersion 30\n    buildToolsVersion \"29.0.3\"\n\n    defaultConfig {\n        minSdkVersion 16\n        targetSdkVersion 30\n        versionCode 1\n        versionName \"1.0\"\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles \"consumer-rules.pro\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n    compileOptions {\n        kotlinOptions.freeCompilerArgs += ['-module-name', \"com.xj.android.anchortask\"]\n    }\n\n}\n\ndependencies {\n    implementation fileTree(dir: \"libs\", include: [\"*.jar\"])\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version\"\n    implementation 'androidx.core:core-ktx:1.3.2'\n    implementation 'androidx.appcompat:appcompat:1.2.0'\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'androidx.test.ext:junit:1.1.2'\n    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'\n\n}\n\n\next {\n    PUBLISH_GROUP_ID = \"com.xj.android\"        //项目包名\n    PUBLISH_ARTIFACT_ID = 'anchortask'            //项目名\n    PUBLISH_VERSION = \"${anchorTaskPublicVersion}\"            //版本号\n}\napply from: \"${rootProject.projectDir}/publish-mavencentral.gradle\"\n\n\n"
  },
  {
    "path": "anchortasklibrary/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "anchortasklibrary/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "anchortasklibrary/src/androidTest/java/com/xj/anchortask/library/ExampleInstrumentedTest.kt",
    "content": "package com.xj.anchortask.library\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runners.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"com.xj.anchortask.library.test\", appContext.packageName)\n    }\n}"
  },
  {
    "path": "anchortasklibrary/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.xj.anchortask.library\">\n\n    /\n</manifest>"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorConfig.kt",
    "content": "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 */\nclass AnchorConfig(\n    logLevel: LogUtils.LogLevel = LogUtils.LogLevel.NONE,\n    waringTime: Long = Long.MAX_VALUE,\n    showToastToAlarm: Boolean = false\n) {\n\n}"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorProject.kt",
    "content": "package com.xj.anchortask.library\n\nimport android.content.Context\nimport com.xj.anchortask.library.log.LogUtils\nimport com.xj.anchortask.library.monitor.ExecuteMonitor\nimport com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback\nimport java.util.concurrent.CopyOnWriteArrayList\nimport java.util.concurrent.CountDownLatch\nimport java.util.concurrent.ThreadPoolExecutor\nimport java.util.concurrent.TimeUnit\nimport java.util.concurrent.atomic.AtomicInteger\n\n/**\n * Created by jun xu on 2/1/21.\n */\nclass AnchorProject private constructor(val builder: Builder) {\n\n    init {\n        LogUtils.init(\"AnchorTaskLibrary\")\n    }\n\n    private val threadPoolExecutor: ThreadPoolExecutor =\n        builder.threadPoolExecutor ?: TaskExecutorManager.instance.cpuThreadPoolExecutor\n\n    // 存储所有的任务,key 是 taskName，value 是 AnchorTask\n    private val taskMap: MutableMap<String, AnchorTask> = builder.taskMap\n\n    // 储存当前任务的子任务， key 是当前任务的 taskName，value 是 AnchorTask 的 list\n    private val taskChildMap: MutableMap<String, ArrayList<AnchorTask>?> = builder.taskChildMap\n\n    //需要等待的任务总数，用于阻塞\n    private val countDownLatch: CountDownLatch = builder.countDownLatch\n\n    // 拓扑排序之后的主线程任务\n    private val mainList = builder.mainList\n\n    // 拓扑排序之后的子线程任务\n    private val threadList = builder.threadList\n\n    private val totalTaskSize = builder.list.size\n    private val finishTask = AtomicInteger(0)\n\n    private val listeners: CopyOnWriteArrayList<OnProjectExecuteListener> =\n        CopyOnWriteArrayList()\n\n    private var iAnchorTaskCreator: IAnchorTaskCreator? = null\n    private var cacheTask: AnchorTask? = null\n    private val executeMonitor: ExecuteMonitor =\n        ExecuteMonitor()\n\n\n    var onGetMonitorRecordCallback: OnGetMonitorRecordCallback? = null\n\n    fun addListener(onProjectExecuteListener: OnProjectExecuteListener) {\n        listeners.add(onProjectExecuteListener)\n    }\n\n    fun removeListener(onProjectExecuteListener: OnProjectExecuteListener) {\n        listeners.remove(onProjectExecuteListener)\n    }\n\n    fun record(taskName: String, executeTime: Long) {\n        executeMonitor.record(taskName, executeTime)\n    }\n\n\n    /**\n     *  通知 child countdown,当前的阻塞任务书也需要 countdown\n     */\n    fun setNotifyChildren(anchorTask: AnchorTask) {\n        taskChildMap[anchorTask.getTaskName()]?.forEach {\n            taskMap[it.getTaskName()]?.countdown()\n        }\n        if (anchorTask.needWait()) {\n            countDownLatch.countDown()\n        }\n        listeners.forEach {\n            it.onTaskFinish(anchorTask.getTaskName())\n        }\n        finishTask.incrementAndGet()\n\n        if (finishTask.get() == totalTaskSize) {\n            executeMonitor.recordProjectFinish()\n            ThreadUtils.runOnUiThread(Runnable {\n                onGetMonitorRecordCallback?.onGetProjectExecuteTime(executeMonitor.projectCostTime)\n                onGetMonitorRecordCallback?.onGetTaskExecuteRecord(executeMonitor.executeTimeMap)\n            })\n\n            listeners.forEach {\n                it.onProjectFinish()\n            }\n        }\n    }\n\n    fun start(): AnchorProject {\n        executeMonitor.recordProjectStart()\n        this.listeners.forEach {\n            it.onProjectStart()\n        }\n\n        this.threadList.forEach {\n            threadPoolExecutor.execute(AnchorTaskRunnable(this, anchorTask = it))\n        }\n\n        this.mainList.forEach {\n            AnchorTaskRunnable(this, anchorTask = it).run()\n        }\n        return this\n    }\n\n    fun await(timeOutMillion: Long = -1) {\n        if (timeOutMillion > 0) {\n            countDownLatch.await(timeOutMillion, TimeUnit.MILLISECONDS)\n        } else {\n            countDownLatch.await()\n        }\n    }\n\n\n    class Builder constructor() {\n\n\n        companion object {\n            private const val TAG = \"AnchorTaskDispatcher\"\n        }\n\n\n        var threadPoolExecutor: ThreadPoolExecutor? = null\n            private set\n\n        private lateinit var context: Context\n        private var logLevel: LogUtils.LogLevel = LogUtils.logLevel\n        private var timeOutMillion: Long = -1\n\n        // 存储所有的任务\n        val list: MutableList<AnchorTask> = ArrayList()\n\n        // 存储所有的任务,key 是 taskName，value 是 AnchorTask\n        val taskMap: MutableMap<String, AnchorTask> = HashMap()\n\n        // 储存当前任务的子任务， key 是当前任务的 taskName，value 是 AnchorTask 的 list\n        val taskChildMap: MutableMap<String, ArrayList<AnchorTask>?> = HashMap()\n\n        // 拓扑排序之后的主线程任务\n        val mainList: MutableList<AnchorTask> = ArrayList()\n\n        // 拓扑排序之后的子线程任务\n        val threadList: MutableList<AnchorTask> = ArrayList()\n\n        //需要等待的任务总数，用于阻塞\n        lateinit var countDownLatch: CountDownLatch\n\n        //需要等待的任务总数，用于CountDownLatch\n        private val needWaitCount: AtomicInteger = AtomicInteger()\n\n        private var startTime = -1L\n\n        private val listeners: CopyOnWriteArrayList<OnProjectExecuteListener> =\n            CopyOnWriteArrayList()\n\n        private var iAnchorTaskCreator: IAnchorTaskCreator? = null\n        private val anchorTaskWrapper: TaskCreatorWrap = TaskCreatorWrap(iAnchorTaskCreator)\n\n        private var cacheTask: AnchorTask? = null\n\n        fun setAnchorTaskCreator(iAnchorTaskCreator: IAnchorTaskCreator): Builder {\n            this.iAnchorTaskCreator = iAnchorTaskCreator\n            anchorTaskWrapper.iAnchorTaskCreator = iAnchorTaskCreator\n            return this\n        }\n\n        fun setContext(context: Context): Builder {\n            this.context = context\n            return this\n        }\n\n        fun setLogLevel(logLevel: LogUtils.LogLevel): Builder {\n            this.logLevel = logLevel\n            LogUtils.logLevel = logLevel\n            return this\n        }\n\n        fun setThreadPoolExecutor(threadPoolExecutor: ThreadPoolExecutor?): Builder {\n            this.threadPoolExecutor = threadPoolExecutor\n            return this\n        }\n\n        fun setTimeOutMillion(timeOutMillion: Long): Builder {\n            this.timeOutMillion = timeOutMillion\n            return this\n        }\n\n        fun addTask(taskName: String): Builder {\n            val createTask = anchorTaskWrapper.createTask(taskName)\n            createTask ?: let {\n                throw AnchorTaskException(\"could not find anchorTask, taskName is $taskName\")\n            }\n            return addTask(anchorTask = createTask)\n        }\n\n        fun afterTask(vararg taskNames: String): Builder {\n            cacheTask ?: let {\n                throw AnchorTaskException(\"should be call addTask first\")\n            }\n\n            taskNames.forEach { taskName ->\n                val createTask = anchorTaskWrapper.createTask(taskName)\n                createTask ?: let {\n                    throw AnchorTaskException(\"could not find anchorTask, taskName is $taskName\")\n                }\n\n                cacheTask?.afterTask(taskName)\n            }\n\n            return this\n\n        }\n\n        fun afterTask(vararg anchorTasks: AnchorTask): Builder {\n            cacheTask ?: let {\n                throw AnchorTaskException(\"should be call addTask first\")\n            }\n            anchorTasks.forEach {\n                cacheTask?.afterTask(it.getTaskName())\n            }\n\n            return this\n        }\n\n        fun addTask(anchorTask: AnchorTask): Builder {\n            cacheTask = anchorTask\n            list.add(anchorTask)\n            anchorTask.onAdd()\n            if (anchorTask.needWait()) {\n                needWaitCount.incrementAndGet()\n            }\n            return this\n        }\n\n\n        fun build(): AnchorProject {\n            val sortResult = AnchorTaskUtils.getSortResult(list, taskMap, taskChildMap)\n            LogUtils.d(TAG, \"start: sortResult is $sortResult\")\n            sortResult.forEach {\n                if (it.isRunOnMainThread()) {\n                    mainList.add(it)\n                } else {\n                    threadList.add(it)\n                }\n            }\n\n            countDownLatch = CountDownLatch(needWaitCount.get())\n            return AnchorProject(this)\n        }\n\n\n    }\n}\n\n\n"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskException.kt",
    "content": "package com.xj.anchortask.library\n\nimport java.lang.RuntimeException\n\n/**\n * Created by jun xu on 2/1/21.\n */\nclass AnchorTaskException(message: String?) : RuntimeException(message) {\n}"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskRunnable.kt",
    "content": "package com.xj.anchortask.library\n\nimport android.os.Process\nimport android.os.SystemClock\n\n/**\n * Created by jun xu on 2/2/21.\n *\n */\nclass AnchorTaskRunnable(\n    private val anchorProject: AnchorProject,\n    private val anchorTask: AnchorTask\n) : Runnable {\n\n    override fun run() {\n        Process.setThreadPriority(anchorTask.priority())\n        //  前置任务没有执行完毕的话，等待，执行完毕的话，往下走\n        anchorTask.await()\n        anchorTask.onStart()\n        // 执行任务\n        val startTime = SystemClock.elapsedRealtime()\n        anchorTask.run()\n        val executeTime = SystemClock.elapsedRealtime() - startTime\n        anchorProject.record(anchorTask.getTaskName(), executeTime)\n        anchorTask.onFinish()\n        // 通知子任务，当前任务执行完毕了，相应的计数器要减一。\n        anchorProject.setNotifyChildren(anchorTask)\n    }\n}"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskUtils.kt",
    "content": "package com.xj.anchortask.library\n\n\nimport com.xj.anchortask.library.log.LogUtils\nimport java.util.*\nimport kotlin.collections.ArrayList\nimport kotlin.collections.HashMap\nimport kotlin.collections.set\n\n/**\n * Created by jun xu on 2/1/21.\n */\nobject AnchorTaskUtils {\n\n    @JvmStatic\n    fun getSortResult(\n        list: MutableList<AnchorTask>, taskMap: MutableMap<String, AnchorTask>,\n        taskChildMap: MutableMap<String, ArrayList<AnchorTask>?>\n    ): MutableList<AnchorTask> {\n        val result = ArrayList<AnchorTask>()\n        // 入度为 0 的队列\n        val queue = ArrayDeque<AnchorTask>()\n        val taskIntegerHashMap = HashMap<String, Int>()\n\n        // 建立每个 task 的入度关系\n        list.forEach { anchorTask: AnchorTask ->\n            val taskName = anchorTask.getTaskName()\n            if (taskIntegerHashMap.containsKey(taskName)) {\n                throw AnchorTaskException(\"anchorTask is repeat, anchorTask is $anchorTask, list is $list\")\n            }\n\n            val size = anchorTask.getDependsTaskList()?.size ?: 0\n            taskIntegerHashMap[taskName] = size\n            taskMap[taskName] = anchorTask\n            if (size == 0) {\n                queue.offer(anchorTask)\n            }\n        }\n\n        // 建立每个 task 的 children 关系\n        list.forEach { anchorTask: AnchorTask ->\n            anchorTask.getDependsTaskList()?.forEach { taskName: String ->\n                var list = taskChildMap[taskName]\n                if (list == null) {\n                    list = ArrayList<AnchorTask>()\n                }\n                list.add(anchorTask)\n                taskChildMap[taskName] = list\n            }\n        }\n\n        taskChildMap.entries.iterator().forEach {\n            LogUtils.d(\"TAG\",\"key is ${it.key}, value is ${it.value}\")\n        }\n\n\n        // 使用 BFS 方法获得有向无环图的拓扑排序\n        while (!queue.isEmpty()) {\n            val anchorTask = queue.pop()\n            result.add(anchorTask)\n            val taskName = anchorTask.getTaskName()\n            taskChildMap[taskName]?.forEach { // 遍历所有依赖这个顶点的顶点，移除该顶点之后，如果入度为 0，加入到改队列当中\n                val key = it.getTaskName()\n                var result = taskIntegerHashMap[key] ?: 0\n                result--\n                if (result == 0) {\n                    queue.offer(it)\n                }\n                taskIntegerHashMap[key] = result\n            }\n        }\n\n        // size 不相等，证明有环\n        if (list.size != result.size) {\n            throw AnchorTaskException(\"Ring appeared，Please check.list is $list, result is $result\")\n        }\n\n        return result\n\n    }\n}"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/IAnchorTask.kt",
    "content": "package com.xj.anchortask.library\n\nimport android.os.Process\nimport androidx.annotation.CallSuper\nimport androidx.annotation.IntRange\nimport java.util.concurrent.CopyOnWriteArrayList\nimport java.util.concurrent.CountDownLatch\n\n/**\n * Created by jun xu on 2/1/21.\n */\ninterface IAnchorTask : IAnchorCallBack {\n\n    /**\n     * 获取任务昵称\n     */\n    fun getTaskName(): String\n\n    /**\n     * 是否在主线程执行\n     */\n    fun isRunOnMainThread(): Boolean\n\n    /**\n     * 任务优先级别\n     */\n    @IntRange(\n        from = Process.THREAD_PRIORITY_FOREGROUND.toLong(),\n        to = Process.THREAD_PRIORITY_LOWEST.toLong()\n    )\n    fun priority(): Int\n\n    /**\n     * 调用 await 方法，是否需要等待改任务执行完成\n     * true 不需要\n     * false 需要\n     */\n    fun needWait(): Boolean\n\n    /**\n     * 任务被执行的时候回调\n     */\n    fun run()\n\n}\n\ninterface IAnchorCallBack {\n    fun onAdd()\n    fun onStart()\n    fun onFinish()\n}\n\nclass SimpleAnchorCallBack : IAnchorCallBack {\n    override fun onAdd() {\n\n    }\n\n\n    override fun onStart() {\n\n    }\n\n    override fun onFinish() {\n\n    }\n\n}\n\nabstract class AnchorTask(private val name: String) : IAnchorTask {\n\n    companion object {\n        const val TAG = \"AnchorTask\"\n    }\n\n    private lateinit var countDownLatch: CountDownLatch\n    private val copyOnWriteArrayList: CopyOnWriteArrayList<IAnchorCallBack> by lazy {\n        CopyOnWriteArrayList<IAnchorCallBack>()\n    }\n\n    val dependList: MutableList<String> = ArrayList()\n\n\n    private fun getListSize() = getDependsTaskList()?.size ?: 0\n\n    override fun getTaskName(): String {\n        return name\n    }\n\n    override fun priority(): Int {\n        return Process.THREAD_PRIORITY_FOREGROUND\n    }\n\n    override fun needWait(): Boolean {\n        return true\n    }\n\n    fun afterTask(taskName: String) {\n        dependList.add(taskName)\n    }\n\n    /**\n     * self call,await\n     */\n    fun await() {\n        tryToInitCountDown()\n        countDownLatch.await()\n    }\n\n    @Synchronized\n    private fun tryToInitCountDown() {\n        if (!this::countDownLatch.isInitialized) {\n            countDownLatch = CountDownLatch(dependList.size)\n        }\n    }\n\n    /**\n     * parent call, countDown\n     */\n    fun countdown() {\n        tryToInitCountDown()\n        countDownLatch.countDown()\n    }\n\n    override fun isRunOnMainThread(): Boolean {\n        return false\n    }\n\n    fun getDependsTaskList(): List<String>? {\n        return dependList\n    }\n\n    @CallSuper\n    override fun onAdd() {\n        copyOnWriteArrayList.forEach {\n            it.onAdd()\n        }\n    }\n\n    @CallSuper\n    override fun onStart() {\n        copyOnWriteArrayList.forEach {\n            it.onStart()\n        }\n    }\n\n    @CallSuper\n    override fun onFinish() {\n        copyOnWriteArrayList.forEach {\n            it.onFinish()\n        }\n    }\n\n    fun addCallback(iAnchorCallBack: IAnchorCallBack?) {\n        iAnchorCallBack ?: return\n        copyOnWriteArrayList.add(iAnchorCallBack)\n    }\n\n    fun removeCallback(iAnchorCallBack: IAnchorCallBack?) {\n        iAnchorCallBack ?: return\n        copyOnWriteArrayList.remove(iAnchorCallBack)\n    }\n\n    override fun toString(): String {\n        return \"AnchorTask(name='$name',dependList is $dependList)\"\n    }\n\n\n}"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/IAnchorTaskCreator.kt",
    "content": "package com.xj.anchortask.library\n\n/**\n * Created by jun xu on 2/9/21.\n */\nopen interface IAnchorTaskCreator {\n    /**\n     * 根据Task名称，创建Task实例。这个接口需要使用者自己实现。创建后的实例会被缓存起来。\n     * @param taskName Task名称\n     * @return  Task实例\n     */\n    fun createTask(taskName: String): AnchorTask?\n}\n\n\nopen class TaskCreatorWrap(var iAnchorTaskCreator: IAnchorTaskCreator?) : IAnchorTaskCreator {\n\n    private val map: MutableMap<String, AnchorTask?> = HashMap()\n\n    override fun createTask(taskName: String): AnchorTask? {\n        val anchorTask = map[taskName]\n        anchorTask?.let {\n            return it\n        }\n        return iAnchorTaskCreator?.createTask(taskName)\n    }\n\n    fun checkTaskIsExits(taskName: String): Boolean {\n        return map.containsKey(taskName)\n    }\n\n}\n"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/ProjectBuilder.kt",
    "content": "package com.xj.anchortask.library\n\nimport android.content.Context\nimport com.xj.anchortask.library.log.LogUtils\nimport java.util.concurrent.CopyOnWriteArrayList\nimport java.util.concurrent.CountDownLatch\nimport java.util.concurrent.ThreadPoolExecutor\nimport java.util.concurrent.atomic.AtomicInteger\n\n/**\n * Created by jun xu on 2/1/21.\n */\n\n\n\n"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/ProjectListener.kt",
    "content": "package com.xj.anchortask.library\n\n/**\n * Created by jun xu on 2/8/21.\n */\ninterface OnProjectExecuteListener {\n    /**\n     * 当`Project`开始执行时，调用该函数。<br></br>\n     * **注意：**该回调函数在`Task`所在线程中回调，注意线程安全。\n     */\n    fun onProjectStart()\n\n    /**\n     * 当`Project`其中一个`Task`执行结束时，调用该函数。<br></br>\n     * **注意：**该回调函数在`Task`所在线程中回调，注意线程安全。\n     *\n     * @param taskName 当前结束的`Task`名称\n     */\n    fun onTaskFinish(taskName: String)\n\n    /**\n     * 当`Project`执行结束时，调用该函数。<br></br>\n     * **注意：**该回调函数在`Task`所在线程中回调，注意线程安全。\n     */\n    fun onProjectFinish()\n}\n\n"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/TaskExecutorManager.kt",
    "content": "package com.xj.anchortask.library\n\nimport java.util.concurrent.*\n\nclass TaskExecutorManager private constructor() {\n    //获得cpu密集型线程池,因为占据CPU的时间片过多的话会影响性能，所以这里控制了最大并发，防止主线程的时间片减少\n    //CPU 密集型任务的线程池\n    val cpuThreadPoolExecutor: ThreadPoolExecutor\n\n    //获得io密集型线程池，有好多任务其实占用的CPU time非常少，所以使用缓存线程池,基本上来着不拒\n    // IO 密集型任务的线程池\n    val ioThreadPoolExecutor: ExecutorService\n\n    //线程池队列\n    private val mPoolWorkQueue: BlockingQueue<Runnable> =\n        LinkedBlockingQueue()\n\n    // 这个是为了保障任务超出BlockingQueue的最大值，且线程池中的线程数已经达到MAXIMUM_POOL_SIZE时候，还有任务到来会采取任务拒绝策略，这里定义的策略就是\n    //再开一个缓存线程池去执行。当然BlockingQueue默认的最大值是int_max，所以理论上这里是用不到的\n    private val mHandler =\n        RejectedExecutionHandler { r, executor -> Executors.newCachedThreadPool().execute(r) }\n\n    companion object {\n        //CPU 核数\n        private val CPU_COUNT = Runtime.getRuntime().availableProcessors()\n\n        //线程池线程数\n        private val CORE_POOL_SIZE = Math.max(\n            2,\n            Math.min(CPU_COUNT - 1, 5)\n        )\n\n        //线程池线程数的最大值\n        private val MAXIMUM_POOL_SIZE = CORE_POOL_SIZE\n\n        //线程空置回收时间\n        private const val KEEP_ALIVE_SECONDS = 5\n\n        @JvmStatic\n        val instance: TaskExecutorManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {\n            TaskExecutorManager()\n        }\n    }\n\n    //初始化线程池\n    init {\n        cpuThreadPoolExecutor = ThreadPoolExecutor(\n            CORE_POOL_SIZE,\n            MAXIMUM_POOL_SIZE,\n            KEEP_ALIVE_SECONDS.toLong(),\n            TimeUnit.SECONDS,\n            mPoolWorkQueue,\n            Executors.defaultThreadFactory(),\n            mHandler\n        ).apply {\n            allowCoreThreadTimeOut(true)\n        }\n        ioThreadPoolExecutor = Executors.newCachedThreadPool(Executors.defaultThreadFactory())\n    }\n}"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/ThreadUtils.kt",
    "content": "package com.xj.anchortask.library\n\nimport android.os.Handler\nimport android.os.Looper\n\nobject ThreadUtils {\n\n    fun runOnUiThread(r: Runnable) {\n        if (isMainThread) {\n            r.run()\n        } else {\n            LazyHolder.sUiThreadHandler.post(r)\n        }\n    }\n\n    fun runOnUiThreadAtFront(r: Runnable) {\n        if (isMainThread) {\n            r.run()\n        } else {\n            LazyHolder.sUiThreadHandler.postAtFrontOfQueue(r)\n        }\n    }\n\n    fun runOnUiThread(r: Runnable?, delay: Long) {\n        LazyHolder.sUiThreadHandler.postDelayed(r!!, delay)\n    }\n\n    fun removeCallbacks(r: Runnable?) {\n        LazyHolder.sUiThreadHandler.removeCallbacks(r!!)\n    }\n\n    val isMainThread: Boolean\n        get() = Looper.getMainLooper() == Looper.myLooper()\n\n    fun checkAtMainThread() {\n        val e = RuntimeException(\"not main thread\")\n        LazyHolder.sUiThreadHandler.postDelayed({ throw e }, 500)\n    }\n\n    private object LazyHolder {\n        val sUiThreadHandler =\n            Handler(Looper.getMainLooper())\n    }\n}"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/log/LogUtils.kt",
    "content": "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 {\n\n    enum class LogLevel {\n        NONE {\n            override val value: Int\n                get() = -1\n        },\n\n        ERROR {\n            override val value: Int\n                get() = 0\n        },\n        WARN {\n            override val value: Int\n                get() = 1\n        },\n        INFO {\n            override val value: Int\n                get() = 2\n        },\n        DEBUG {\n            override val value: Int\n                get() = 3\n        };\n\n\n        abstract val value: Int\n    }\n\n    private var TAG = \"SAF_L\"\n\n    var logLevel = LogLevel.INFO // 日志的等级，可以进行配置，最好在Application中进行全局的配置\n\n    @JvmStatic\n    fun init(clazz: Class<*>) {\n        TAG = clazz.simpleName\n    }\n\n    /**\n     * 支持用户自己传tag，可扩展性更好\n     * @param tag\n     */\n    @JvmStatic\n    fun init(tag: String) {\n        TAG = tag\n    }\n\n    @JvmStatic\n    fun e(tag: String, msg: String) {\n        if (LogLevel.ERROR.value <= logLevel.value) {\n            if (msg.isNotBlank()) {\n                Log.e(\"$TAG$tag\", msg)\n            }\n        }\n    }\n\n    @JvmStatic\n    fun w(tag: String, msg: String) {\n        if (LogLevel.WARN.value <= logLevel.value) {\n            if (msg.isNotBlank()) {\n                Log.e(\"$TAG$tag\", msg)\n            }\n        }\n    }\n\n    @JvmStatic\n    fun i(tag: String, msg: String) {\n        if (LogLevel.INFO.value <= logLevel.value) {\n            if (msg.isNotBlank()) {\n                Log.i(\"$TAG$tag\", msg)\n            }\n\n        }\n    }\n\n    @JvmStatic\n    fun d(tag: String, msg: String) {\n        if (LogLevel.DEBUG.value <= logLevel.value) {\n            if (msg.isNotBlank()) {\n                Log.d(\"$TAG$tag\", msg)\n            }\n        }\n    }\n\n\n}"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/monitor/ExecuteMonitor.kt",
    "content": "/*\n * Copyright 2018 Alibaba Group.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.xj.anchortask.library.monitor\n\nimport android.os.Handler\nimport android.os.Looper\nimport java.util.concurrent.ConcurrentHashMap\n\n/**\n *\n * 监控`Project`执行性能的类。会记录每一个`Task`执行时间，以及整个`Project`执行时间。\n *\n */\ninternal class ExecuteMonitor {\n    private val mExecuteTimeMap: MutableMap<String?, Long?> = ConcurrentHashMap()\n    private var mStartTime: Long = 0\n\n    /**\n     * @return `Project`执行时间。\n     */\n    var projectCostTime: Long = 0\n        private set\n    private var mHandler: Handler? = null\n\n    /**\n     * 记录`task`执行时间。\n     *\n     * @param taskName    `task`的名称\n     * @param executeTime 执行的时间\n     */\n    fun record(taskName: String, executeTime: Long) {\n//        AlphaLog.d(AlphaLog.GLOBAL_TAG, \"AlphaTask-->Startup task %s cost time: %s ms, in thread: %s\", taskName, executeTime, Thread.currentThread().getName());\n//        if (executeTime >= AlphaConfig.getWarmingTime()) {\n//            toastToWarn(\"AlphaTask %s run too long, cost time: %s\", taskName, executeTime);\n//        }\n        mExecuteTimeMap[taskName] = executeTime\n    }\n\n    /**\n     * @return 已执行完的每个task的执行时间。\n     */\n    val executeTimeMap: Map<String?, Long?>\n        get() = mExecuteTimeMap\n\n    /**\n     * 在`Project`开始执行时打点，记录开始时间。\n     */\n    fun recordProjectStart() {\n        mStartTime = System.currentTimeMillis()\n    }\n\n    /**\n     * 在`Project`结束时打点，记录耗时。\n     */\n    fun recordProjectFinish() {\n        projectCostTime = System.currentTimeMillis() - mStartTime\n        //        AlphaLog.d(\"==ALPHA==\", \"tm start up cost time: %s ms\", mProjectCostTime);\n    }\n\n    /**\n     * 通过弹出`toast`来告警。\n     *\n     * @param msg  告警内容\n     * @param args format参数\n     */\n    private fun toastToWarn(msg: String, vararg args: Any) {\n        handler.post {\n            val formattedMsg: String\n            formattedMsg = if (args == null) {\n                msg\n            } else {\n                String.format(msg, *args)\n            }\n\n//                    Toast.makeText(AlphaConfig.getContext(), formattedMsg, Toast.LENGTH_SHORT).show();\n        }\n    }\n\n    private val handler: Handler\n        private get() {\n            if (mHandler == null) {\n                mHandler = Handler(Looper.getMainLooper())\n            }\n            return mHandler!!\n        }\n}"
  },
  {
    "path": "anchortasklibrary/src/main/java/com/xj/anchortask/library/monitor/OnGetMonitorRecordCallback.kt",
    "content": "/*\n * Copyright 2018 Alibaba Group.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.xj.anchortask.library.monitor\n\n/**\n *\n * 获取`Project`执行性能记录的回调\n *\n */\ninterface OnGetMonitorRecordCallback {\n    /**\n     * 获取`task`执行的耗时。\n     * @param result `task`执行的耗时。`key`是`task`名称，`value`是`task`执行耗时，单位是毫秒。\n     */\n    fun onGetTaskExecuteRecord(result: Map<String?, Long?>?)\n\n    /**\n     * 获取整个`Project`执行耗时。\n     * @param costTime 整个`Project`执行耗时。\n     */\n    fun onGetProjectExecuteTime(costTime: Long)\n}"
  },
  {
    "path": "anchortasklibrary/src/test/java/com/xj/anchortask/library/ExampleUnitTest.kt",
    "content": "package com.xj.anchortask.library\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}"
  },
  {
    "path": "app/.gitignore",
    "content": "/build"
  },
  {
    "path": "app/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply plugin: 'kotlin-android-extensions'\napply from: '../localconfig.gradle'\n\nandroid {\n    compileSdkVersion 30\n    buildToolsVersion \"29.0.3\"\n\n    defaultConfig {\n        applicationId \"com.xj.anchortask\"\n        minSdkVersion 16\n        targetSdkVersion 30\n        versionCode 1\n        versionName \"1.0\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n}\n\nProperties properties = loadProperties(\"local.properties\")\nString mode = null\nif (properties != null) {\n    mode = getValue(properties, \"mode\", \"local\")\n}\nprintln(\"mode is $mode\")\nprint(\"anchorTaskVersion is ${anchorTaskVersion}\")\n\ndependencies {\n    implementation fileTree(dir: \"libs\", include: [\"*.jar\"])\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version\"\n    implementation 'androidx.core:core-ktx:1.3.2'\n    implementation 'androidx.appcompat:appcompat:1.2.0'\n    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'\n    implementation 'androidx.legacy:legacy-support-v4:1.0.0'\n\n    implementation 'com.google.android:flexbox:2.0.1'\n\n\n    implementation \"androidx.startup:startup-runtime:1.0.0\"\n    implementation 'androidx.work:work-runtime:2.3.4'\n    implementation 'com.google.android.material:material:1.4.0'\n\n\n    testImplementation 'junit:junit:4.12'\n    androidTestImplementation 'androidx.test.ext:junit:1.1.2'\n    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'\n\n    if (mode == \"local\") {\n        implementation \"com.xj.android:anchortask:${anchorTaskVersion}-local\"\n    } else if (mode == \"remote\") {\n        implementation \"com.xj.android:anchortask:${anchorTaskVersion}\"\n    } else {\n        implementation project(path: ':anchortasklibrary')\n    }\n\n\n}"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "app/src/androidTest/java/com/xj/anchortask/ExampleInstrumentedTest.kt",
    "content": "package com.xj.anchortask\n\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.ext.junit.runners.AndroidJUnit4\n\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\nimport org.junit.Assert.*\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"com.xj.anchortask\", appContext.packageName)\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.xj.anchortask\">\n\n    <application\n        android:name=\".MyApplication\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity\n            android:name=\".asyncInflate.AsyncActivity\"\n            android:exported=\"true\" />\n        <activity android:name=\".flowlayout.FlowLayoutDemo\" />\n\n        <provider\n            android:name=\"androidx.startup.InitializationProvider\"\n            android:authorities=\"${applicationId}.androidx-startup\"\n            android:exported=\"false\"\n            tools:node=\"merge\" />\n\n        <activity android:name=\".MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity android:name=\".viewStub.ViewStubDemoActivity\" />\n        <activity android:name=\".anchorTask.AnchorTaskTestActivity\" />\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/LogUtils.kt",
    "content": "package com.xj.anchortask\n\nimport android.util.Log\nimport org.json.JSONArray\nimport org.json.JSONException\nimport org.json.JSONObject\n\n/**\n * Created by jun xu on 2/2/21.\n */\nobject LogUtils {\n\n    enum class LogLevel {\n        ERROR {\n            override val value: Int\n                get() = 0\n        },\n        WARN {\n            override val value: Int\n                get() = 1\n        },\n        INFO {\n            override val value: Int\n                get() = 2\n        },\n        DEBUG {\n            override val value: Int\n                get() = 3\n        };\n\n        abstract val value: Int\n    }\n\n    private var TAG = \"AnchorTask\"\n\n    var logLevel = LogLevel.DEBUG // 日志的等级，可以进行配置，最好在Application中进行全局的配置\n\n    var printMsgStack = false\n\n    @JvmStatic\n    fun init(clazz: Class<*>) {\n        TAG = clazz.simpleName\n    }\n\n    /**\n     * 支持用户自己传tag，可扩展性更好\n     * @param tag\n     */\n    @JvmStatic\n    fun init(tag: String) {\n        TAG = tag\n    }\n\n    @JvmStatic\n    fun e(tag: String, msg: String) {\n        if (LogLevel.ERROR.value <= logLevel.value) {\n            if (msg.isNotBlank()) {\n                val s = getMethodNames()\n                Log.e(\"${TAG}_${tag}\", String.format(s, msg))\n            }\n        }\n    }\n\n    @JvmStatic\n    fun w(tag: String, msg: String) {\n        if (LogLevel.WARN.value <= logLevel.value) {\n            if (msg.isNotBlank()) {\n                val s = getMethodNames()\n                Log.e(\"${TAG}_${tag}\", String.format(s, msg))\n            }\n        }\n    }\n\n    @JvmStatic\n    fun i(tag: String, msg: String) {\n        if (LogLevel.INFO.value <= logLevel.value) {\n            if (msg.isNotBlank()) {\n                val s = getMethodNames()\n                Log.i(\"${TAG}_${tag}\", String.format(s, msg))\n            }\n\n        }\n    }\n\n    @JvmStatic\n    fun d(tag: String, msg: String) {\n        if (LogLevel.DEBUG.value <= logLevel.value) {\n            if (msg.isNotBlank()) {\n                val s = getMethodNames()\n                Log.d(\"${TAG}_${tag}\", String.format(s, msg))\n            }\n        }\n    }\n\n    @JvmStatic\n    fun json(tag: String, json: String) {\n        var json = json\n        if (json.isBlank()) {\n            d(tag, \"Empty/Null json content\")\n            return\n        }\n\n        try {\n            json = json.trim { it <= ' ' }\n            if (json.startsWith(\"{\")) {\n                val jsonObject = JSONObject(json)\n                var message = jsonObject.toString(LoggerPrinter.JSON_INDENT)\n                message = message.replace(\"\\n\".toRegex(), \"\\n║ \")\n                val s = getMethodNames()\n                println(String.format(s, message))\n                return\n            }\n            if (json.startsWith(\"[\")) {\n                val jsonArray = JSONArray(json)\n                var message = jsonArray.toString(LoggerPrinter.JSON_INDENT)\n                message = message.replace(\"\\n\".toRegex(), \"\\n║ \")\n                val s = getMethodNames()\n                println(String.format(s, message))\n                return\n            }\n            e(tag, \"Invalid Json\")\n        } catch (e: JSONException) {\n            e(tag, \"Invalid Json\")\n        }\n\n    }\n\n    private fun getMethodNames(): String {\n        if (!printMsgStack) {\n            return \"%s\"\n        }\n        val sElements = Thread.currentThread().stackTrace\n\n        var stackOffset = LoggerPrinter.getStackOffset(sElements)\n\n        stackOffset++\n        val builder = StringBuilder()\n        builder.append(LoggerPrinter.TOP_BORDER).append(\"\\r\\n\")\n            // 添加当前线程名\n            .append(\"║ \" + \"Thread: \" + Thread.currentThread().name).append(\"\\r\\n\")\n            .append(LoggerPrinter.MIDDLE_BORDER).append(\"\\r\\n\")\n            // 添加类名、方法名、行数\n            .append(\"║ \")\n            .append(sElements[stackOffset].className)\n            .append(\".\")\n            .append(sElements[stackOffset].methodName)\n            .append(\" \")\n            .append(\" (\")\n            .append(sElements[stackOffset].fileName)\n            .append(\":\")\n            .append(sElements[stackOffset].lineNumber)\n            .append(\")\")\n            .append(\"\\r\\n\")\n            .append(LoggerPrinter.MIDDLE_BORDER).append(\"\\r\\n\")\n            // 添加打印的日志信息\n            .append(\"║ \").append(\"%s\").append(\"\\r\\n\")\n            .append(LoggerPrinter.BOTTOM_BORDER).append(\"\\r\\n\")\n        return builder.toString()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/LoggerPrinter.kt",
    "content": "package com.xj.anchortask\n\n/**\n * Created by jun xu on 2/2/21.\n */\nobject LoggerPrinter {\n\n    private val MIN_STACK_OFFSET = 3\n\n    /**\n     * Drawing toolbox\n     */\n    private val TOP_LEFT_CORNER = '╔'\n    private val BOTTOM_LEFT_CORNER = '╚'\n    private val MIDDLE_CORNER = '╟'\n    private val HORIZONTAL_DOUBLE_LINE = '║'\n    private val DOUBLE_DIVIDER = \"════════════════════════════════════════════\"\n    private val SINGLE_DIVIDER = \"────────────────────────────────────────────\"\n    val TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER\n    val BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER\n    val MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER\n\n    /**\n     * It is used for json pretty print\n     */\n    val JSON_INDENT = 2\n\n    fun getStackOffset(trace: Array<StackTraceElement>): Int {\n        var i = MIN_STACK_OFFSET\n        while (i < trace.size) {\n            val e = trace[i]\n            val name = e.className\n            if (name != LoggerPrinter::class.java.name && name != LogUtils::class.java.name) {\n                return --i\n            }\n            i++\n        }\n        return -1\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/MainActivity.kt",
    "content": "package com.xj.anchortask\n\nimport android.content.Intent\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport com.xj.anchortask.anchorTask.AnchorTaskTestActivity\nimport com.xj.anchortask.asyncInflate.AsyncActivity\nimport com.xj.anchortask.asyncInflate.page.AsyncUtils\nimport com.xj.anchortask.asyncInflate.page.AsyncUtils.isHomeFragmentOpen\nimport com.xj.anchortask.flowlayout.FlowLayoutDemo\nimport com.xj.anchortask.viewStub.ViewStubDemoActivity\n\nimport kotlinx.android.synthetic.main.activity_main.*\n\nclass MainActivity : AppCompatActivity() {\n\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_main)\n        btn_view_stub.setOnClickListener {\n            startActivity(Intent(this@MainActivity, ViewStubDemoActivity::class.java))\n        }\n\n        btn_anchortask.setOnClickListener {\n            startActivity(Intent(this@MainActivity, AnchorTaskTestActivity::class.java))\n        }\n\n        btn_flow_layout_demo.setOnClickListener {\n            startActivity(Intent(this@MainActivity, FlowLayoutDemo::class.java))\n        }\n\n        btn_async.setOnClickListener {\n            startActivity(Intent(this@MainActivity, AsyncActivity::class.java))\n        }\n\n\n        val isOpen = AsyncUtils.isHomeFragmentOpen()\n        updateText(isOpen)\n        switch_async.isChecked = isOpen\n\n        ll_switch.setOnClickListener {\n            val b = !switch_async.isChecked\n            updateChecked(b)\n        }\n\n        switch_async.setOnCheckedChangeListener { buttonView, isChecked ->\n            updateChecked(isChecked)\n        }\n    }\n\n    private fun updateChecked(b: Boolean) {\n        updateText(b)\n        switch_async.isChecked = b\n        getSP(\"async_config\").spApplyBoolean(\"home_fragment_switch\", b)\n    }\n\n\n    private fun updateText(b: Boolean) {\n        if (b) {\n            tv_asnyc_text.setText(\"首页 Fragment 开启异步加载\")\n        } else {\n            tv_asnyc_text.setText(\"首页 Fragment 关闭异步加载\")\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/MyApplication.kt",
    "content": "package com.xj.anchortask\n\nimport android.app.Application\nimport android.content.Context\nimport android.util.Log\nimport com.xj.anchortask.anchorTask.TestTaskUtils\nimport com.xj.anchortask.asyncInflate.page.AsyncUtils\nimport com.xj.anchortask.library.OnProjectExecuteListener\nimport com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback\n\n/**\n * Created by jun xu on 2/1/21.\n */\nclass MyApplication : Application() {\n\n\n    companion object {\n        const val TAG = \"AnchorTaskApplication\"\n\n        lateinit var myApplication: MyApplication\n            private set\n\n        @JvmStatic\n        fun getInstance(): Application {\n            return myApplication\n        }\n    }\n\n    override fun attachBaseContext(base: Context?) {\n        super.attachBaseContext(base)\n        Log.i(TAG, \"attachBaseContext: \")\n    }\n\n    override fun onCreate() {\n        super.onCreate()\n        Log.i(TAG, \"onCreate: \")\n        myApplication = this\n\n        TestTaskUtils.executeTask(this, projectExecuteListener = object : OnProjectExecuteListener {\n            override fun onProjectFinish() {\n\n            }\n\n            override fun onProjectStart() {\n\n            }\n\n            override fun onTaskFinish(taskName: String) {\n\n            }\n\n        }, onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback {\n            override fun onGetProjectExecuteTime(costTime: Long) {\n                Log.i(TAG, \"onGetProjectExecuteTime: costTime is $costTime\")\n\n            }\n\n            override fun onGetTaskExecuteRecord(result: Map<String?, Long?>?) {\n                Log.i(TAG, \"onGetTaskExecuteRecord: result is $result\")\n            }\n\n        })\n\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/SPUtil.kt",
    "content": "package com.xj.anchortask\n\nimport android.content.Context\nimport android.content.SharedPreferences\n\n\nfun getSP(name: String) = MyApplication.getInstance().getSharedPreferences(name, Context.MODE_PRIVATE)!!\n\nfun SharedPreferences.getSPEditor() = this.edit()!!\n\nfun SharedPreferences.spApplyClear() = getSPEditor().clear().apply()\n\nfun SharedPreferences.spCommitClear() = getSPEditor().clear().commit()\n\nfun SharedPreferences.spApplyInt(key: String, value: Int) = getSPEditor().putInt(key, value).apply()\n\nfun SharedPreferences.spCommitInt(key: String, value: Int) =\n    getSPEditor().putInt(key, value).commit()\n\nfun SharedPreferences.spGetInt(key: String, defValue: Int = 0): Int = this.getInt(key, defValue)\n\nfun SharedPreferences.spApplyLong(key: String, value: Long) =\n    getSPEditor().putLong(key, value).apply()\n\nfun SharedPreferences.spCommitLong(key: String, value: Long) =\n    getSPEditor().putLong(key, value).commit()\n\nfun SharedPreferences.spGetLong(key: String, defValue: Long = 0): Long = this.getLong(key, defValue)\n\nfun SharedPreferences.spApplyFloat(key: String, value: Float) =\n    getSPEditor().putFloat(key, value).apply()\n\nfun SharedPreferences.spCommitFloat(key: String, value: Float) =\n    getSPEditor().putFloat(key, value).commit()\n\nfun SharedPreferences.spGetFloat(key: String, defValue: Float = 0F): Float =\n    this.getFloat(key, defValue)\n\nfun SharedPreferences.spApplyBoolean(key: String, value: Boolean) =\n    getSPEditor().putBoolean(key, value).apply()\n\nfun SharedPreferences.spCommitBoolean(key: String, value: Boolean) =\n    getSPEditor().putBoolean(key, value).commit()\n\nfun SharedPreferences.spGetBoolean(key: String, defValue: Boolean = false): Boolean =\n    this.getBoolean(key, defValue)\n\nfun SharedPreferences.spApplyString(key: String, value: String) =\n    getSPEditor().putString(key, value).apply()\n\nfun SharedPreferences.spCommitString(key: String, value: String) =\n    getSPEditor().putString(key, value).commit()\n\nfun SharedPreferences.spGetString(key: String, defValue: String = \"\"): String? =\n    this.getString(key, defValue)\n\nfun SharedPreferences.spApplyStringSet(key: String, value: Set<String>) =\n    getSPEditor().putStringSet(key, value).apply()\n\nfun SharedPreferences.spCommitStringSet(key: String, value: Set<String>) =\n    getSPEditor().putStringSet(key, value).commit()\n\nfun SharedPreferences.spGetStringSet(key: String, defValue: Set<String>? = null): Set<String>? =\n    this.getStringSet(key, defValue)\n\nfun SharedPreferences.spCommitRemove(key: String) = getSPEditor().remove(key).commit()\n\nfun SharedPreferences.spApplyRemove(key: String) = getSPEditor().remove(key).commit()"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/anchorTask/AnchorTaskTestActivity.kt",
    "content": "package com.xj.anchortask.anchorTask\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport com.xj.anchortask.R\nimport com.xj.anchortask.library.OnProjectExecuteListener\nimport com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback\nimport kotlinx.android.synthetic.main.activity_anchortask_test.*\n\nclass AnchorTaskTestActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_anchortask_test)\n        initAnchorTask()\n    }\n\n    private fun initAnchorTask() {\n        val sb2 = StringBuilder()\n        text2.text = \"正在执行中\"\n        val projectExecuteListener = object : OnProjectExecuteListener {\n            val sb = StringBuffer()\n            override fun onProjectStart() {\n                if (sb.length > 0) {\n                    sb.delete(0, sb.length)\n                }\n\n            }\n\n            override fun onTaskFinish(taskName: String) {\n                sb.append(\"task $taskName execute finish \\n\")\n            }\n\n            override fun onProjectFinish() {\n                text.post {\n                    text.setText(sb.toString())\n                }\n            }\n\n        }\n        val onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback {\n\n\n            override fun onGetTaskExecuteRecord(result: Map<String?, Long?>?) {\n                result?.entries?.iterator()?.forEach {\n                    sb2.append(it.key).append(\"执行耗时\").append(it.value).append(\"毫秒\\n\")\n                }\n                text2.text = sb2.toString()\n            }\n\n            override fun onGetProjectExecuteTime(costTime: Long) {\n                sb2.append(\"总共执行耗时\").append(costTime).append(\"毫秒\\n\")\n            }\n\n        }\n        btn_execute.setOnClickListener {\n            sb2.clear()\n            text2.text = \"正在执行中\"\n            try {\n                TestTaskUtils.executeTask(\n                    this, projectExecuteListener, onGetMonitorRecordCallback\n                )\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n\n        }\n\n        btn_execute2.setOnClickListener {\n            sb2.clear()\n            text2.text = \"正在执行中2\"\n            try {\n                TestTaskUtils.executeTask(\n                    this, projectExecuteListener, onGetMonitorRecordCallback\n                )\n            } catch (e: Exception) {\n                e.printStackTrace()\n            }\n\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/anchorTask/ApplicationAnchorTaskCreator.kt",
    "content": "package com.xj.anchortask.anchorTask\n\nimport com.xj.anchortask.LogUtils\nimport com.xj.anchortask.library.AnchorTask\nimport com.xj.anchortask.library.IAnchorTaskCreator\n\n/**\n * Created by jun xu on 2/1/21.\n */\n\nconst val TASK_NAME_ZERO = \"zero\"\nconst val TASK_NAME_ONE = \"one\"\nconst val TASK_NAME_TWO = \"two\"\nconst val TASK_NAME_THREE = \"three\"\nconst val TASK_NAME_FOUR = \"four\"\nconst val TASK_NAME_FIVE = \"five\"\n\nclass ApplicationAnchorTaskCreator : IAnchorTaskCreator {\n    override fun createTask(taskName: String): AnchorTask? {\n        when (taskName) {\n            TASK_NAME_ZERO -> {\n                return AnchorTaskZero()\n            }\n\n            TASK_NAME_ONE -> {\n                return AnchorTaskOne()\n            }\n            TASK_NAME_TWO -> {\n                return AnchorTaskTwo()\n            }\n            TASK_NAME_THREE -> {\n                return AnchorTaskThree()\n            }\n            TASK_NAME_FOUR -> {\n                return AnchorTaskFour()\n            }\n            TASK_NAME_FIVE -> {\n                return AnchorTaskFive()\n            }\n        }\n        return null\n    }\n\n}\n\nclass AnchorTaskZero() : AnchorTask(TASK_NAME_ZERO) {\n    override fun isRunOnMainThread(): Boolean {\n        return false\n    }\n\n    override fun run() {\n        val start = System.currentTimeMillis()\n        try {\n            Thread.sleep(300)\n        } catch (e: Exception) {\n        }\n        LogUtils.i(\n            TAG, \"AnchorTaskOne: \" + (System.currentTimeMillis() - start)\n        )\n    }\n}\n\nclass AnchorTaskOne : AnchorTask(TASK_NAME_ONE) {\n    override fun isRunOnMainThread(): Boolean {\n        return false\n    }\n\n    override fun run() {\n        val start = System.currentTimeMillis()\n        try {\n            Thread.sleep(300)\n        } catch (e: Exception) {\n        }\n        LogUtils.i(\n            TAG, \"AnchorTaskOne: \" + (System.currentTimeMillis() - start)\n        )\n    }\n\n}\n\nclass AnchorTaskTwo : AnchorTask(TASK_NAME_TWO) {\n    override fun isRunOnMainThread(): Boolean {\n        return false\n    }\n\n    override fun run() {\n        val start = System.currentTimeMillis()\n        try {\n            Thread.sleep(300)\n        } catch (e: Exception) {\n        }\n        LogUtils.i(\n            TAG, \"AnchorTaskTwo执行耗时: \" + (System.currentTimeMillis() - start)\n        )\n    }\n\n}\n\nclass AnchorTaskThree : AnchorTask(TASK_NAME_THREE) {\n    override fun isRunOnMainThread(): Boolean {\n        return false\n    }\n\n    override fun run() {\n        val start = System.currentTimeMillis()\n        try {\n            Thread.sleep(300)\n        } catch (e: Exception) {\n        }\n        LogUtils.i(\n            TAG, \"AnchorTaskThree执行耗时: \" + (System.currentTimeMillis() - start)\n        )\n    }\n\n}\n\n\nclass AnchorTaskFour : AnchorTask(TASK_NAME_FOUR) {\n    override fun isRunOnMainThread(): Boolean {\n        return false\n    }\n\n    override fun run() {\n        val start = System.currentTimeMillis()\n        try {\n            Thread.sleep(300)\n        } catch (e: Exception) {\n        }\n        LogUtils.i(\n            TAG, \"AnchorTaskFour执行耗时: \" + (System.currentTimeMillis() - start)\n        )\n    }\n\n}\n\nclass AnchorTaskFive : AnchorTask(TASK_NAME_FIVE) {\n    override fun isRunOnMainThread(): Boolean {\n        return false\n    }\n\n    override fun run() {\n        val start = System.currentTimeMillis()\n        try {\n            Thread.sleep(300)\n        } catch (e: Exception) {\n        }\n        LogUtils.i(\n            TAG, \"AnchorTaskFive执行耗时: \" + (System.currentTimeMillis() - start)\n        )\n    }\n\n\n}\n\n\n\n"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/anchorTask/TestTaskUtils.kt",
    "content": "package com.xj.anchortask.anchorTask\n\nimport android.content.Context\nimport com.xj.anchortask.MyApplication\nimport com.xj.anchortask.library.AnchorProject\nimport com.xj.anchortask.library.OnProjectExecuteListener\nimport com.xj.anchortask.library.TaskExecutorManager\nimport com.xj.anchortask.library.log.LogUtils\nimport com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback\n\n/**\n * Created by jun xu on 2/9/21.\n */\nobject TestTaskUtils {\n\n    fun executeTask(\n        context: Context,\n        projectExecuteListener: OnProjectExecuteListener? = null,\n        onGetMonitorRecordCallback: OnGetMonitorRecordCallback? = null\n    ) {\n        val project =\n            AnchorProject.Builder().setContext(context).setLogLevel(LogUtils.LogLevel.DEBUG)\n                .setAnchorTaskCreator(ApplicationAnchorTaskCreator())\n                .addTask(TASK_NAME_ZERO)\n                .addTask(TASK_NAME_ONE)\n                .addTask(TASK_NAME_TWO)\n                .addTask(TASK_NAME_THREE).afterTask(\n                    TASK_NAME_ZERO,\n                    TASK_NAME_ONE\n                )\n                .addTask(TASK_NAME_FOUR).afterTask(\n                    TASK_NAME_ONE,\n                    TASK_NAME_TWO\n                )\n                .addTask(TASK_NAME_FIVE).afterTask(\n                    TASK_NAME_THREE,\n                    TASK_NAME_FOUR\n                )\n                .setThreadPoolExecutor(TaskExecutorManager.instance.cpuThreadPoolExecutor)\n                .build()\n        project.addListener(object : OnProjectExecuteListener {\n            override fun onProjectStart() {\n                com.xj.anchortask.LogUtils.i(\n                    MyApplication.TAG,\n                    \"onProjectStart \"\n                )\n            }\n\n            override fun onTaskFinish(taskName: String) {\n                com.xj.anchortask.LogUtils.i(\n                    MyApplication.TAG,\n                    \"onTaskFinish, taskName is $taskName\"\n                )\n            }\n\n            override fun onProjectFinish() {\n                com.xj.anchortask.LogUtils.i(\n                    MyApplication.TAG,\n                    \"onProjectFinish \"\n                )\n            }\n\n        })\n        projectExecuteListener?.let {\n            project.addListener(it)\n        }\n        project.onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback {\n            override fun onGetTaskExecuteRecord(result: Map<String?, Long?>?) {\n                onGetMonitorRecordCallback?.onGetTaskExecuteRecord(result)\n            }\n\n            override fun onGetProjectExecuteTime(costTime: Long) {\n                onGetMonitorRecordCallback?.onGetProjectExecuteTime(costTime)\n            }\n\n        }\n\n        project.start().await(1000)\n    }\n\n    fun executeTask2(\n        context: Context,\n        projectExecuteListener: OnProjectExecuteListener? = null,\n        onGetMonitorRecordCallback: OnGetMonitorRecordCallback? = null\n    ) {\n\n        val anchorTaskZero = AnchorTaskZero()\n        val anchorTaskOne = AnchorTaskOne()\n        val anchorTaskTwo = AnchorTaskTwo()\n        val anchorTaskThree = AnchorTaskThree()\n        val anchorTaskFour = AnchorTaskFour()\n        val anchorTaskFive = AnchorTaskFive()\n        val project =\n            AnchorProject.Builder().setContext(context).setLogLevel(LogUtils.LogLevel.DEBUG)\n                .setAnchorTaskCreator(ApplicationAnchorTaskCreator())\n                .addTask(anchorTaskZero)\n                .addTask(anchorTaskOne)\n                .addTask(anchorTaskTwo)\n                .addTask(anchorTaskThree).afterTask(\n                    anchorTaskZero,\n                    anchorTaskOne\n                )\n                .addTask(TASK_NAME_FOUR).afterTask(\n                    anchorTaskOne,\n                    anchorTaskTwo\n                )\n                .addTask(TASK_NAME_FIVE).afterTask(\n                    anchorTaskThree,\n                    anchorTaskFive\n                )\n                .setThreadPoolExecutor(TaskExecutorManager.instance.cpuThreadPoolExecutor)\n                .build()\n        project.addListener(object : OnProjectExecuteListener {\n            override fun onProjectStart() {\n                com.xj.anchortask.LogUtils.i(\n                    MyApplication.TAG,\n                    \"onProjectStart \"\n                )\n            }\n\n            override fun onTaskFinish(taskName: String) {\n                com.xj.anchortask.LogUtils.i(\n                    MyApplication.TAG,\n                    \"onTaskFinish, taskName is $taskName\"\n                )\n            }\n\n            override fun onProjectFinish() {\n                com.xj.anchortask.LogUtils.i(\n                    MyApplication.TAG,\n                    \"onProjectFinish \"\n                )\n            }\n\n        })\n        projectExecuteListener?.let {\n            project.addListener(it)\n        }\n        project.onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback {\n            override fun onGetTaskExecuteRecord(result: Map<String?, Long?>?) {\n                onGetMonitorRecordCallback?.onGetTaskExecuteRecord(result)\n            }\n\n            override fun onGetProjectExecuteTime(costTime: Long) {\n                onGetMonitorRecordCallback?.onGetProjectExecuteTime(costTime)\n            }\n\n        }\n\n        project.start().await(1000)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/appstartup/InitializerSample.kt",
    "content": "package com.xj.anchortask.appstartup\n\nimport android.content.Context\nimport android.util.Log\nimport androidx.startup.AppInitializer\nimport androidx.startup.Initializer\nimport androidx.work.WorkManager\n\n\n/**\n * Created by jun xu on 4/17/21.\n */\n\nconst val TAG = \"AnchorTaskApplication\"\n\nclass WorkManagerInitializer : Initializer<WorkManager> {\n    override fun create(context: Context): WorkManager {\n        Log.i(TAG, \"create: WorkManagerInitializer init\")\n        AppInitializer.getInstance(context)\n            .initializeComponent(ExampleLoggerInitializer::class.java)\n        val let: Int = context.let {\n            123\n        }\n        return WorkManager.getInstance(context)\n    }\n\n    override fun dependencies(): List<Class<out Initializer<*>>> {\n        // No dependencies on other libraries.\n\n        return emptyList()\n    }\n}\n\n// Initializes ExampleLogger.\nclass ExampleLoggerInitializer : Initializer<ExampleLogger> {\n    override fun create(context: Context): ExampleLogger {\n        Log.i(TAG, \"create: ExampleLoggerInitializer init\")\n        val apply: Context = context.apply { }\n        return ExampleLogger(WorkManager.getInstance(context))\n    }\n\n    override fun dependencies(): List<Class<out Initializer<*>>> {\n        // Defines a dependency on WorkManagerInitializer so it can be\n        // initialized after WorkManager is initialized.\n        return listOf(WorkManagerInitializer::class.java)\n    }\n}\n\nclass ExampleLogger(val workManager: WorkManager) {\n\n}\n\nfun <T, R> T.let2(block: (T) -> R): R {\n    return block(this)\n}\n\nfun <T> T.apply2(block: T.() -> Unit): T {\n    block()\n    return this\n}\n"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/AsyncActivity.kt",
    "content": "package com.xj.anchortask.asyncInflate\n\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimport com.xj.anchortask.R\n\nclass AsyncActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val asyncInflateItem =\n            AsyncInflateItem(\n                LaunchInflateKey.LAUNCH_ACTIVITY,\n                R.layout.activity_async,\n                null,\n                null\n            )\n        AsyncInflateManager.instance.asyncInflate(this, asyncInflateItem)\n        Thread.sleep(2)\n        val inflatedView = AsyncInflateManager.instance.getInflatedView(\n            this,\n            R.layout.activity_async,\n            null,\n            LaunchInflateKey.LAUNCH_ACTIVITY,\n            layoutInflater\n        )\n        setContentView(inflatedView)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        AsyncInflateManager.instance.remove(LaunchInflateKey.LAUNCH_ACTIVITY)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateItem.kt",
    "content": "package com.xj.anchortask.asyncInflate\n\nimport android.view.View\nimport android.view.ViewGroup\n\n/**\n * Created by jun xu on 4/1/21.\n */\nclass AsyncInflateItem constructor(var inflateKey: String, var layoutResId: Int, var parent: ViewGroup? = null, var callback: OnInflateFinishedCallback? = null) {\n    var inflatedView: View? = null\n    private var cancelled = false\n    private var inflating = false\n\n    fun isCancelled(): Boolean {\n        synchronized(this) { return cancelled }\n    }\n\n    fun setCancelled(cancelled: Boolean) {\n        synchronized(this) { this.cancelled = cancelled }\n    }\n\n    fun isInflating(): Boolean {\n        synchronized(this) { return inflating }\n    }\n\n    fun setInflating(inflating: Boolean) {\n        synchronized(this) { this.inflating = inflating }\n    }\n\n    override fun toString(): String {\n        return \"AsyncInflateItem(inflateKey='$inflateKey', layoutResId=$layoutResId, parent=$parent, callback=$callback, inflatedView=$inflatedView, cancelled=$cancelled, inflating=$inflating)\"\n    }\n\n\n    interface OnInflateFinishedCallback {\n        fun onInflateFinished(item: AsyncInflateItem)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateKey.kt",
    "content": "package com.xj.anchortask.asyncInflate\n\n\n/**\n *   @author  pjt\n */\nobject LaunchInflateKey {\n\n    /**\n     * 首页Fragment\n     * [R.layout.fragment_tabs]\n     */\n    const val LAUNCH_FRAGMENT_MAIN = \"launch_fragment_main\"\n    const val LAUNCH_ACTIVITY = \"async_activity\"\n\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateManager.kt",
    "content": "package com.xj.anchortask.asyncInflate\n\nimport android.content.Context\nimport android.content.MutableContextWrapper\nimport android.text.TextUtils\nimport android.util.AttributeSet\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.UiThread\nimport java.util.concurrent.*\n\n/**\n * Created by jun xu on 4/1/21\n *\n *\n * 用来提供子线程inflate view的功能，避免某个view层级太深太复杂，主线程inflate会耗时很长，\n * 实就是对 AsyncLayoutInflater进行了抽取和封装\n */\nclass AsyncInflateManager private constructor() {\n\n    private val mInflateMap //保存inflateKey以及InflateItem，里面包含所有要进行inflate的任务\n            : ConcurrentHashMap<String, AsyncInflateItem?> = ConcurrentHashMap()\n    private val mInflateLatchMap: ConcurrentHashMap<String, CountDownLatch> = ConcurrentHashMap()\n\n    companion object {\n        private const val TAG = \"AsyncInflateManager\"\n        private val cpuCount = Runtime.getRuntime().availableProcessors()\n        val threadPool = ThreadPoolExecutor(\n            cpuCount,\n            cpuCount * 2,\n            5,\n            TimeUnit.SECONDS,\n            LinkedBlockingDeque()\n        ).apply {\n            allowCoreThreadTimeOut(true)\n        }\n\n        @JvmStatic\n        val instance by lazy {\n            AsyncInflateManager()\n        }\n\n        /**\n         * 空方法，为了可以提前加载 AsyncInflateManager，并初始化 mThreadPool\n         */\n        fun init() {\n\n        }\n    }\n\n    /**\n     * 用来获得异步inflate出来的view\n     *\n     * @param context\n     * @param layoutResId 需要拿的layoutId\n     * @param parent      container\n     * @param inflateKey  每一个View会对应一个inflateKey，因为可能许多地方用的同一个 layout，但是需要inflate多个，用InflateKey进行区分\n     * @param inflater    外部传进来的inflater，外面如果有inflater，传进来，用来进行可能的SyncInflate，\n     * @return 最后inflate出来的view\n     */\n    @UiThread\n    fun getInflatedView(\n        context: Context?,\n        layoutResId: Int,\n        parent: ViewGroup?,\n        inflateKey: String?,\n        inflater: LayoutInflater\n    ): View {\n        if (!TextUtils.isEmpty(inflateKey) && mInflateMap.containsKey(inflateKey)) {\n            val item = mInflateMap[inflateKey]\n            val latch = mInflateLatchMap[inflateKey]\n            if (item != null) {\n                val resultView = item.inflatedView\n                if (resultView != null) {\n                    //拿到了view直接返回\n                    removeInflateKey(item)\n                    replaceContextForView(resultView, context)\n                    Log.i(TAG, \"getInflatedView from cache: inflateKey is $inflateKey\")\n                    return resultView\n                }\n\n                if (item.isInflating() && latch != null) {\n                    //没拿到view，但是在inflate中，等待返回\n                    try {\n                        latch.await()\n                    } catch (e: InterruptedException) {\n                        Log.e(TAG, e.message, e)\n                    }\n\n                    val inflatedView = item.inflatedView\n                    if (inflatedView != null) {\n                        removeInflateKey(item)\n                        Log.i(TAG, \"getInflatedView from OtherThread: inflateKey is $inflateKey\")\n                        replaceContextForView(inflatedView, context)\n                        return inflatedView\n                    }\n\n                }\n\n                //如果还没开始inflate，则设置为false，UI线程进行inflate\n                item.setCancelled(true)\n            }\n        }\n        Log.i(TAG, \"getInflatedView from UI: inflateKey is $inflateKey\")\n        //拿异步inflate的View失败，UI线程inflate\n        return inflater.inflate(layoutResId, parent, false)\n    }\n\n    /**\n     * 如果  inflater初始化时是传进来的application，inflate出来的 view 的 context 没法用来 startActivity，\n     * 因此用 MutableContextWrapper 进行包装，后续进行替换\n     */\n    private fun replaceContextForView(\n        inflatedView: View?,\n        context: Context?\n    ) {\n        if (inflatedView == null || context == null) {\n            return\n        }\n        val cxt = inflatedView.context\n        if (cxt is MutableContextWrapper) {\n            cxt.baseContext = context\n        }\n    }\n\n\n    fun asyncInflate(\n        context: Context,\n        vararg items: AsyncInflateItem?\n    ) {\n        items.forEach { item ->\n            if (item == null || item.layoutResId == 0 || mInflateMap.containsKey(item.inflateKey) || item.isCancelled() || item.isInflating()) {\n                return\n            }\n            mInflateMap[item.inflateKey] = item\n            onAsyncInflateReady(item)\n            inflateWithThreadPool(context, item)\n        }\n\n    }\n\n    fun cancel() {\n\n    }\n\n    fun remove(inflateKey: String) {\n        mInflateMap.remove(inflateKey)\n        mInflateLatchMap.remove(inflateKey)\n    }\n\n    private fun onAsyncInflateReady(item: AsyncInflateItem) {}\n\n    private fun onAsyncInflateStart(item: AsyncInflateItem) {}\n\n    private fun onAsyncInflateEnd(item: AsyncInflateItem, success: Boolean) {\n        item.setInflating(false)\n        val latch = mInflateLatchMap[item.inflateKey]\n        latch?.countDown()\n        if (success && item.callback != null) {\n            removeInflateKey(item)\n\n            if (item.isCancelled()) { // 已经取消了，不再回调\n                return\n            }\n\n            ThreadUtils.runOnUiThread { item.callback?.onInflateFinished(item) }\n        }\n    }\n\n    private fun removeInflateKey(item: AsyncInflateItem) {\n\n    }\n\n    private fun inflateWithThreadPool(\n        context: Context,\n        item: AsyncInflateItem\n    ) {\n        threadPool.execute {\n            if (!item.isInflating() && !item.isCancelled()) {\n                try {\n                    onAsyncInflateStart(item)\n                    item.setInflating(true)\n                    mInflateLatchMap[item.inflateKey] = CountDownLatch(1)\n                    val currentTimeMillis = System.currentTimeMillis()\n                    item.inflatedView =\n                        BasicInflater(context).inflate(item.layoutResId, item.parent, false)\n                    onAsyncInflateEnd(item, true)\n                    val l = System.currentTimeMillis() - currentTimeMillis\n                    Log.i(TAG, \"inflateWithThreadPool: inflateKey is ${item.inflateKey}, time is ${l}\")\n                } catch (e: RuntimeException) {\n                    Log.e(\n                        TAG,\n                        \"Failed to inflate resource in the background! Retrying on the UI thread\",\n                        e\n                    )\n                    onAsyncInflateEnd(item, false)\n                }\n            }\n        }\n    }\n\n    /**\n     * copy from AsyncLayoutInflater - actual inflater\n     */\n    private class BasicInflater(context: Context?) :\n        LayoutInflater(context) {\n        override fun cloneInContext(newContext: Context): LayoutInflater {\n            return BasicInflater(newContext)\n        }\n\n        @Throws(ClassNotFoundException::class)\n        override fun onCreateView(\n            name: String,\n            attrs: AttributeSet\n        ): View {\n            for (prefix in sClassPrefixList) {\n                try {\n                    val view = this.createView(name, prefix, attrs)\n                    if (view != null) {\n                        return view\n                    }\n                } catch (ignored: ClassNotFoundException) {\n                }\n            }\n            return super.onCreateView(name, attrs)\n        }\n\n        companion object {\n            private val sClassPrefixList =\n                arrayOf(\"android.widget.\", \"android.webkit.\", \"android.app.\")\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/ThreadUtils.java",
    "content": "package com.xj.anchortask.asyncInflate;\n\nimport android.os.Handler;\nimport android.os.Looper;\n\n/**\n * Created by jun xu on 4/1/21.\n */\npublic class ThreadUtils {\n\n    public static void runOnUiThread(Runnable r) {\n        if (isMainThread()) {\n            r.run();\n        } else {\n            LazyHolder.sUiThreadHandler.post(r);\n        }\n    }\n\n    public static void runOnUiThreadAtFront(Runnable r) {\n        if (isMainThread()) {\n            r.run();\n        } else {\n            LazyHolder.sUiThreadHandler.postAtFrontOfQueue(r);\n        }\n    }\n\n    public static void runOnUiThread(Runnable r, long delay) {\n        LazyHolder.sUiThreadHandler.postDelayed(r, delay);\n    }\n\n    public static void postOnUiThread(Runnable r) {\n        LazyHolder.sUiThreadHandler.post(r);\n    }\n\n    public static void removeCallbacks(Runnable r) {\n        LazyHolder.sUiThreadHandler.removeCallbacks(r);\n    }\n\n    public static boolean isMainThread() {\n        return Looper.getMainLooper() == Looper.myLooper();\n    }\n\n    private static class LazyHolder {\n        private static Handler sUiThreadHandler = new Handler(Looper.getMainLooper());\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/page/AsyncFragment.kt",
    "content": "package com.xj.anchortask.asyncInflate.page\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport com.xj.anchortask.R\nimport com.xj.anchortask.asyncInflate.AsyncInflateManager\nimport com.xj.anchortask.asyncInflate.LaunchInflateKey.LAUNCH_FRAGMENT_MAIN\n\n// TODO: Rename parameter arguments, choose names that match\n// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER\nprivate const val ARG_PARAM1 = \"param1\"\nprivate const val ARG_PARAM2 = \"param2\"\nprivate const val TAG = \"AsyncFragment\"\n\n\n/**\n * A simple [Fragment] subclass.\n * Use the [AsyncFragment.newInstance] factory method to\n * create an instance of this fragment.\n */\nclass AsyncFragment : Fragment() {\n    // TODO: Rename and change types of parameters\n    private var param1: String? = null\n    private var param2: String? = null\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        arguments?.let {\n            param1 = it.getString(ARG_PARAM1)\n            param2 = it.getString(ARG_PARAM2)\n        }\n\n\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater, container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        // Inflate the layout for this fragment\n        val startTime = System.currentTimeMillis()\n        val homeFragmentOpen = AsyncUtils.isHomeFragmentOpen()\n        val inflatedView: View\n\n        val isOpen = AsyncUtils.isHomeFragmentOpen()\n        if (isOpen){\n            AsyncUtils.asyncInflate(context)\n        }\n\n//        inflatedView = AsyncInflateManager.instance.getInflatedView(\n//            context,\n//            R.layout.fragment_asny,\n//            container,\n//            LAUNCH_FRAGMENT_MAIN,\n//            inflater\n//        )\n//\n//        Log.i(\n//            TAG,\n//            \"onCreateView: homeFragmentOpen is $homeFragmentOpen, timeInstance is ${System.currentTimeMillis() - startTime}, ${inflatedView.context}\"\n//        )\n//        return inflatedView\n        return inflater.inflate(R.layout.fragment_asny, container, false)\n    }\n\n    companion object {\n        /**\n         * Use this factory method to create a new instance of\n         * this fragment using the provided parameters.\n         *\n         * @param param1 Parameter 1.\n         * @param param2 Parameter 2.\n         * @return A new instance of fragment AsnyFragment.\n         */\n        // TODO: Rename and change types and number of parameters\n        @JvmStatic\n        fun newInstance(param1: String, param2: String) =\n            AsyncFragment().apply {\n                arguments = Bundle().apply {\n                    putString(ARG_PARAM1, param1)\n                    putString(ARG_PARAM2, param2)\n                }\n            }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/asyncInflate/page/AsyncUtils.kt",
    "content": "package com.xj.anchortask.asyncInflate.page\n\nimport android.content.Context\nimport com.xj.anchortask.R\nimport com.xj.anchortask.asyncInflate.AsyncInflateItem\nimport com.xj.anchortask.asyncInflate.AsyncInflateManager\nimport com.xj.anchortask.asyncInflate.LaunchInflateKey.LAUNCH_FRAGMENT_MAIN\nimport com.xj.anchortask.getSP\n\n/**\n * Created by jun xu on 4/1/21.\n */\nobject AsyncUtils {\n\n    fun asyncInflate(context: Context?) {\n        context ?: return\n        val asyncInflateItem =\n            AsyncInflateItem(\n                LAUNCH_FRAGMENT_MAIN,\n                R.layout.fragment_asny,\n                null,\n                null\n            )\n        AsyncInflateManager.instance.asyncInflate(context, asyncInflateItem)\n    }\n\n    fun isHomeFragmentOpen() =\n        getSP(\"async_config\").getBoolean(\"home_fragment_switch\", true)\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/flowlayout/FlowLayoutDemo.kt",
    "content": "package com.xj.anchortask.flowlayout\n\nimport android.graphics.Color\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport com.xj.anchortask.R\nimport java.util.*\n\n\nclass FlowLayoutDemo : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_flow_layout_demo)\n\n        val tagGroup = findViewById<TagGroup>(R.id.tag_group)\n        val texts: List<String> = Arrays.asList(\n            \"zhang\",\n            \"phil\",\n            \"csdn\",\n            \"android\",\n            \"zhang\",\n            \"phil\",\n            \"csdn\",\n            \"android\",\n            \"zhang\",\n            \"phil\",\n            \"csdn\",\n            \"android\"\n        )\n        val colors: List<Int> = Arrays.asList(\n            Color.RED,\n            Color.DKGRAY,\n            Color.BLUE,\n            Color.RED,\n            Color.DKGRAY,\n            Color.BLUE,\n            Color.RED,\n            Color.DKGRAY,\n            Color.BLUE,\n            Color.RED,\n            Color.DKGRAY,\n            Color.BLUE\n        )\n        tagGroup.setTagView(texts, colors)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/flowlayout/TagGroup.java",
    "content": "package com.xj.anchortask.flowlayout;\n\n/**\n * Created by jun xu on 4/19/21.\n */\n\nimport android.content.Context;\nimport android.graphics.Color;\nimport android.graphics.drawable.GradientDrawable;\nimport android.util.AttributeSet;\nimport android.widget.LinearLayout;\nimport android.widget.TextView;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport com.google.android.flexbox.FlexDirection;\nimport com.google.android.flexbox.FlexWrap;\nimport com.google.android.flexbox.FlexboxLayout;\nimport com.google.android.flexbox.JustifyContent;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n//https://blog.csdn.net/qq_36699930/article/details/112249170\npublic class TagGroup extends FlexboxLayout {\n    private List<String> mTexts;\n    private List<Integer> mColors;\n    private Context mContext;\n    private int TAG_VIEW_COUNT = 0;\n\n    public TagGroup(Context context, AttributeSet attrs) {\n        super(context, attrs);\n        mContext = context;\n        init();\n    }\n\n    private void init() {\n        mTexts = new ArrayList<>();\n        mColors = new ArrayList<>();\n\n        this.setFlexDirection(FlexDirection.ROW);\n        this.setJustifyContent(JustifyContent.FLEX_START);\n        this.setFlexWrap(FlexWrap.WRAP);\n    }\n\n    public void setTagView(@NonNull List<String> texts, @Nullable List<Integer> colors) {\n        if (texts == null || texts.size() == 0) {\n            throw new RuntimeException(\"tag view文本字段不能为空\");\n        }\n\n        this.mTexts = texts;\n        TAG_VIEW_COUNT = texts.size();\n\n        if (colors == null || colors.size() == 0) {\n            for (int i = 0; i < TAG_VIEW_COUNT; i++) {\n                mColors.clear();\n                mColors.add(Color.WHITE);\n            }\n        } else {\n            this.mColors = colors;\n        }\n\n        this.removeAllViews();\n\n        for (int i = 0; i < TAG_VIEW_COUNT; i++) {\n            TextView textView = makeTextView(mTexts.get(i), mColors.get(i));\n            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);\n            //设置每一个子View在整体布局中与其他子View的上下左右的margin\n            layoutParams.setMargins(0, 1, 5, 1);\n\n            this.addView(textView, layoutParams);\n        }\n\n        this.invalidate();\n    }\n\n    //绘制圆角描边的TextView\n    private TextView makeTextView(String s, Integer c) {\n        TextView textView = new TextView(mContext);\n        textView.setText(s);\n        textView.setPadding(10, 5, 10, 5);\n\n        int strokeWidth = 5; // 5px\n        int roundRadius = 15; // 15px\n        int strokeColor = Color.GRAY;\n        int fillColor = c;\n\n        GradientDrawable gd = new GradientDrawable();\n        gd.setColor(fillColor);\n        gd.setCornerRadius(roundRadius);\n        gd.setStroke(strokeWidth, strokeColor);\n\n        textView.setBackground(gd);\n\n        return textView;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/viewStub/MyViewStub.kt",
    "content": "package com.xj.anchortask.viewStub\n\nimport ViewStubTask\nimport android.util.Log\nimport android.view.View\nimport com.xj.anchortask.R\n\n/**\n * Created by jun xu on 4/1/21.\n */\nclass ViewStubTaskTitle(decorView: View) : ViewStubTask(decorView) {\n\n    override fun getViewStubId(): Int {\n        return R.id.vs_title\n    }\n\n    override fun onInflateFinish() {\n        super.onInflateFinish()\n        Log.i(TAG, \"onInflateFinish: ViewStubTaskTitle\")\n    }\n\n\n}\n\nclass ViewStubTaskContent(decorView: View) : ViewStubTask(decorView) {\n\n    override fun getViewStubId(): Int {\n        return R.id.vs_content\n    }\n\n    override fun onInflateFinish() {\n        super.onInflateFinish()\n        Log.i(TAG, \"onInflateFinish: ViewStubTaskContent\")\n    }\n\n\n}\n\nclass ViewStubTaskBottom(decorView: View) : ViewStubTask(decorView) {\n\n    override fun getViewStubId(): Int {\n        return R.id.vs_bottom\n    }\n\n    override fun onInflateFinish() {\n        super.onInflateFinish()\n        Log.i(TAG, \"onInflateFinish: ViewStubTaskBottom\")\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/viewStub/ViewStubDemoActivity.kt",
    "content": "package com.xj.anchortask.viewStub\n\nimport ViewStubTaskManager\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport com.xj.anchortask.R\n\nclass ViewStubDemoActivity : AppCompatActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_view_stub_demo)\n        val decorView = this.window.decorView\n        ViewStubTaskManager.instance(decorView)\n            .addTask(ViewStubTaskContent(decorView))\n            .addTask(ViewStubTaskTitle(decorView))\n            .addTask(ViewStubTaskBottom(decorView))\n            .start()\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/viewStub/ViewStubTask.kt",
    "content": "import android.view.View\nimport android.view.ViewStub\nimport androidx.annotation.CallSuper\nimport androidx.annotation.IdRes\n\n\nabstract class ViewStubTask(val decorView: View) {\n\n    companion object {\n        const val TAG = \"ViewStubTask\"\n    }\n\n    // ViewStub 转化的 View\n    var root: View? = null\n        private set\n\n    // 数据是否准备好\n    var dataIsReady: Boolean = false\n\n    // ViewStub 是否转化完成\n    var onInflateListener: ViewStub.OnInflateListener? = null\n\n    // 当前的 viewStub\n    var viewStub: ViewStub? = null\n        private set\n\n    // 当前 ViewStub 是否转化完成\n    val isInflated: Boolean get() = this.root != null\n\n    abstract fun getViewStubId(): Int\n\n\n    private fun setUpViewStub(viewStub: ViewStub) {\n        this.viewStub = viewStub\n        val proxyListener: ViewStub.OnInflateListener = ViewStub.OnInflateListener { stub, inflated ->\n                this@ViewStubTask.root = inflated\n                this@ViewStubTask.viewStub = null\n                onInflateListener?.onInflate(stub, inflated)\n                onInflateFinish()\n            }\n        viewStub.setOnInflateListener(proxyListener)\n    }\n\n    /**\n     * 调用这个方法，会开始转化 ViewStub\n     */\n    fun inflate(): View? {\n        if (this.root != null) {\n            return this.root!!\n        }\n        val viewStub: ViewStub? = decorView.findViewById(getViewStubId())\n        viewStub?.let {\n            setUpViewStub(viewStub)\n            return viewStub.inflate()\n        } ?: kotlin.run {\n            return null\n        }\n    }\n\n    /**\n     * ViewStub 转化完成回调\n     */\n    open fun onInflateFinish() {\n\n    }\n\n    @CallSuper\n    open fun onDetach() {\n\n    }\n\n\n    @CallSuper\n    open fun onDataReady() {\n        dataIsReady = true\n    }\n\n    // 使用 ViewStub ，建议使用 findViewById，如果直接调用 Activity 或者 根布局的 findViewById，当我们的 view 还没有 add 进入的时候，这时候会为 null\n    fun <T : View?> findViewById(@IdRes id: Int): T? {\n        if (!isInflated) {\n            return null\n        }\n        return if (id == View.NO_ID) {\n            null\n        } else {\n            root?.findViewById<T>(id)\n        }\n\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/com/xj/anchortask/viewStub/ViewStubTaskManager.kt",
    "content": "import android.view.View\nimport androidx.core.view.ViewCompat\nimport java.util.concurrent.CopyOnWriteArrayList\n\n/**\n * Created by jun xu on 3/31/21\n */\nclass ViewStubTaskManager private constructor(val decorView: View) : Runnable {\n\n    private var iViewStubTask: IViewStubTask? = null\n    \n    companion object {\n\n        const val TAG = \"ViewStubTaskManager\"\n\n        @JvmStatic\n        fun instance(decorView: View): ViewStubTaskManager {\n            return ViewStubTaskManager(decorView)\n        }\n    }\n\n    private val queue: MutableList<ViewStubTask> = CopyOnWriteArrayList()\n    private val list: MutableList<ViewStubTask> = CopyOnWriteArrayList()\n\n\n    fun setCallBack(iViewStubTask: IViewStubTask?): ViewStubTaskManager {\n        this.iViewStubTask = iViewStubTask\n        return this\n    }\n\n    fun addTask(viewStubTasks: List<ViewStubTask>): ViewStubTaskManager {\n        queue.addAll(viewStubTasks)\n        list.addAll(viewStubTasks)\n        return this\n    }\n\n    fun addTask(viewStubTask: ViewStubTask): ViewStubTaskManager {\n        queue.add(viewStubTask)\n        list.add(viewStubTask)\n        return this\n    }\n\n\n    fun start() {\n        if (isEmpty()) {\n            return\n        }\n        iViewStubTask?.beforeTaskExecute()\n        // 指定 decorView 绘制下一帧的时候会回调里面的 runnable\n        ViewCompat.postOnAnimation(decorView, this)\n    }\n\n    fun stop() {\n        queue.clear()\n        list.clear()\n        decorView.removeCallbacks(null)\n    }\n\n    private fun isEmpty() = queue.isEmpty() || queue.size == 0\n\n    override fun run() {\n        if (!isEmpty()) {\n            // 当队列不为空的时候，先加载当前 viewStubTask\n            val viewStubTask = queue.removeAt(0)\n            viewStubTask.inflate()\n            iViewStubTask?.onTaskExecute(viewStubTask)\n            // 加载完成之后，再 postOnAnimation 加载下一个\n            ViewCompat.postOnAnimation(decorView, this)\n        } else {\n            iViewStubTask?.afterTaskExecute()\n        }\n\n    }\n\n    fun notifyOnDetach() {\n        list.forEach {\n            it.onDetach()\n        }\n        list.clear()\n    }\n\n    fun notifyOnDataReady() {\n        list.forEach {\n            it.onDataReady()\n        }\n    }\n\n}\n\ninterface IViewStubTask {\n\n    fun beforeTaskExecute()\n\n    fun onTaskExecute(viewStubTask: ViewStubTask)\n\n    fun afterTaskExecute()\n\n\n}"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <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\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        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\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/layout/activity_anchortask_test.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".anchorTask.AnchorTaskTestActivity\">\n\n\n    <Button\n        android:id=\"@+id/btn_execute\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"开始执行\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <Button\n        android:id=\"@+id/btn_execute2\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"开始执行(1.1)\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/btn_execute\" />\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Hello World!\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/btn_execute2\" />\n\n    <TextView\n        android:id=\"@+id/text2\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"15dp\"\n        android:text=\"Hello World!\"\n        android:textSize=\"14sp\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/text\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_async.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\".asyncInflate.page.AsyncFragment\">\n\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/holo_blue_light\" />\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/holo_blue_dark\" />\n\n    <!-- TODO: Update blank fragment layout -->\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/hello_blank_fragment\" />\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/holo_blue_light\" />\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/holo_blue_dark\" />\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/holo_blue_bright\" />\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/black\" />\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <ImageView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"100dp\"\n            android:background=\"@android:color/holo_red_dark\" />\n    </RelativeLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_flow_layout_demo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".flowlayout.FlowLayoutDemo\">\n\n    <com.xj.anchortask.flowlayout.TagGroup\n        android:id=\"@+id/tag_group\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n    </com.xj.anchortask.flowlayout.TagGroup>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\".MainActivity\">\n\n    <Button\n        android:id=\"@+id/btn_view_stub\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"跳转 ViewStub\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <Button\n        android:id=\"@+id/btn_anchortask\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"跳转 AnchorTask\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/btn_view_stub\" />\n\n\n    <Button\n        android:id=\"@+id/btn_async\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"测试异步加载\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/btn_view_stub\" />\n\n    <Button\n        android:id=\"@+id/btn_flow_layout_demo\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"跳转 Flow Layout Demo\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/btn_view_stub\" />\n\n    <LinearLayout\n        android:id=\"@+id/ll_switch\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_asnyc_text\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"首页 Fragment 开启预加载\" />\n\n        <androidx.appcompat.widget.SwitchCompat\n            android:id=\"@+id/switch_async\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\" />\n\n    </LinearLayout>\n\n    <fragment\n        android:id=\"@+id/async_fragment\"\n        android:name=\"com.xj.anchortask.asyncInflate.page.AsyncFragment\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n    </fragment>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_view_stub_demo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\".viewStub.ViewStubDemoActivity\">\n\n    <ViewStub\n        android:id=\"@+id/vs_title\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout=\"@layout/layout_title\" />\n\n    <ViewStub\n        android:id=\"@+id/vs_content\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:layout=\"@layout/layout_content\" />\n\n    <ViewStub\n        android:id=\"@+id/vs_bottom\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout=\"@layout/layout_bottom\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_asny.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\".asyncInflate.page.AsyncFragment\">\n\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/holo_blue_light\" />\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/holo_blue_dark\" />\n\n    <!-- TODO: Update blank fragment layout -->\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/hello_blank_fragment\" />\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/holo_blue_light\" />\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/holo_blue_dark\" />\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/holo_blue_bright\" />\n\n    <ImageView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"@android:color/black\" />\n    <RelativeLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <ImageView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"100dp\"\n            android:background=\"@android:color/holo_red_dark\" />\n    </RelativeLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_bottom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"50dp\">\n\n\n    <TextView\n        android:id=\"@+id/text2\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"15dp\"\n        android:text=\"我是 Bottom\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"18sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_content.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n\n    <TextView\n        android:background=\"@android:color/holo_blue_light\"\n        android:id=\"@+id/text2\"\n        android:gravity=\"center\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginBottom=\"15dp\"\n        android:text=\"我是 Content\"\n        android:textSize=\"30sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_title.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"50dp\">\n\n\n    <TextView\n        android:id=\"@+id/text2\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"15dp\"\n        android:text=\"ViewStubTitle\"\n        android:textSize=\"18sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#6200EE</color>\n    <color name=\"colorPrimaryDark\">#3700B3</color>\n    <color name=\"colorAccent\">#03DAC5</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">AnchorTask</string>\n    <!-- TODO: Remove or change this placeholder text -->\n    <string name=\"hello_blank_fragment\">Hello blank fragment</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.DarkActionBar\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "app/src/test/java/com/xj/anchortask/ExampleUnitTest.kt",
    "content": "package com.xj.anchortask\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}"
  },
  {
    "path": "app/test.gradle",
    "content": "task myTask1 {\n    println \"configure task1\"\n}"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\napply from: \"config.gradle\"\n\nbuildscript {\n    ext.kotlin_version = \"1.3.72\"\n    repositories {\n        google()\n        jcenter()\n        maven {\n            url \"https://dl.bintray.com/xujun94/maven/\"\n        }\n    }\n    dependencies {\n        classpath \"com.android.tools.build:gradle:4.0.2\"\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20\"\n//        classpath 'com.novoda:bintray-release:0.9.2'\n        classpath 'com.github.panpf.bintray-publish:bintray-publish:1.0.0'\n        // NOTE: Do not place your application dependencies here; they belong\n        // in the individual module build.gradle files\n\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n//        maven {\n//            url 'file://Users/junxu/Desktop/gitProject/my-github/AnchorTask/repository'\n//        }\n        maven {\n            url uri('../repository')\n        }\n        maven {\n            url \"https://dl.bintray.com/xujun94/maven/\"\n        }\n    }\n\n\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}"
  },
  {
    "path": "config.gradle",
    "content": "ext {\n    // Sdk and tools\n    anchorTaskPublicVersion = '1.1.0'\n    anchorTaskVersion = anchorTaskPublicVersion\n\n\n    dep = [\n            androidPlugin: 'com.android.tools.build:gradle:2.1.3',\n\n\n    ]\n\n    isCi = \"true\".equals(System.getenv('CI'))\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Feb 01 16:20:03 CST 2021\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.1.1-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app\"s APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Automatically convert third-party libraries to use AndroidX\nandroid.enableJetifier=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "localconfig.gradle",
    "content": "task test22{\n    parseLocalProperties()\n}\n\nProperties loadProperties(String fileName) {\n    File file = rootProject.file(fileName)\n    println(\"parseLocalProperties start\")\n    if (file.exists()) {\n        InputStream inputStream = rootProject.file(fileName).newDataInputStream();\n        Properties properties = new Properties()\n        properties.load(inputStream)\n        return properties\n    }\n    return null\n}\n\ndef parseLocalProperties() {\n    File file = rootProject.file('local.properties')\n    println(\"parseLocalProperties start\")\n    if (file.exists()) {\n        InputStream inputStream = rootProject.file('local.properties').newDataInputStream();\n        Properties properties = new Properties()\n        properties.load(inputStream)\n\n        def containsKey = properties.containsKey(\"packagename\")\n        println(\"parseLocalProperties containsKey is ${containsKey}\")\n        if (containsKey) {\n            println 'packageName:' + properties.getProperty(\"packagename\")\n            ext.packagename = properties.getProperty(\"packagename\")\n            println 'packageName:' + project[\"packagename\"]\n        }\n    }\n}\n\n// 设置属性\ndef static getValue(Properties properties, String name, Object defValue) {\n    if (properties == null) {\n        return defValue\n    }\n\n    if (properties.containsKey(name)) {\n        String value = properties.getProperty(name)\n        return value\n    }\n    return defValue\n}\n\n// 方法复用\next {\n    parseLocalProperties = this.&parseLocalProperties\n    loadProperties = this.&loadProperties\n    getValue = this.&getValue\n}"
  },
  {
    "path": "publish-mavencentral.gradle",
    "content": "apply plugin: 'maven-publish'\napply plugin: 'signing'\n\n\ntask androidSourcesJar(type: Jar) {\n    classifier = 'sources'\n    from android.sourceSets.main.java.source\n}\n\next[\"signing.keyId\"] = ''\next[\"signing.password\"] = ''\next[\"signing.secretKeyRingFile\"] = ''\next[\"ossrhUsername\"] = ''\next[\"ossrhPassword\"] = ''\n\nFile secretPropsFile = project.rootProject.file('local.properties')\nif (secretPropsFile.exists()) {\n    println \"Found secret props file, loading props\"\n    Properties p = new Properties()\n    p.load(new FileInputStream(secretPropsFile))\n    p.each { name, value ->\n        ext[name] = value\n        println(\"name is \" + name + \";value is \" + value)\n    }\n} else {\n    println \"No props file, loading env vars\"\n}\npublishing {\n    publications {\n        release(MavenPublication) {\n            // The coordinates of the library, being set from variables that\n            // we'll set up in a moment\n            groupId PUBLISH_GROUP_ID\n            artifactId PUBLISH_ARTIFACT_ID\n            version PUBLISH_VERSION\n\n            // Two artifacts, the `aar` and the sources\n            artifact(\"$buildDir/outputs/aar/${project.getName()}-release.aar\")\n            artifact androidSourcesJar\n\n            // Self-explanatory metadata for the most part\n            pom {\n                name = PUBLISH_ARTIFACT_ID\n                description = 'AnchorTask'\n                // If your project has a dedicated site, use its URL here\n                url = 'https://github.com/gdutxiaoxu/'\n                licenses {\n                    license {\n                        //协议类型，一般默认Apache License2.0的话不用改：\n                        name = 'The Apache License, Version 2.0'\n                        url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'\n                    }\n                }\n                developers {\n                    developer {\n                        name = 'xujun'\n                        email = 'gdutxiaoxu@163.com'\n                    }\n                }\n                // Version control info, if you're using GitHub, follow the format as seen here\n                scm {\n                    //修改成你的Git地址：\n                    connection = 'scm:git:github.com/gdutxiaoxu/AnchorTask.git'\n                    developerConnection = 'scm:git:ssh://github.com/gdutxiaoxu/AnchorTask.git'\n                    //分支地址：\n                    url = 'https://github.com/gdutxiaoxu/AnchorTask/tree/master'\n                }\n                // A slightly hacky fix so that your POM will include any transitive dependencies\n                // that your library builds upon\n                withXml {\n                    def dependenciesNode = asNode().appendNode('dependencies')\n\n                    project.configurations.implementation.allDependencies.each {\n                        println(it)\n                        if (it.name == \"unspecified\") {\n                            // 忽略无法识别的\n                            return\n                        }\n\n                        def dependencyNode = dependenciesNode.appendNode('dependency')\n                        dependencyNode.appendNode('groupId', it.group)\n                        dependencyNode.appendNode('artifactId', it.name)\n                        dependencyNode.appendNode('version', it.version)\n                    }\n                }\n            }\n        }\n    }\n    repositories {\n        // The repository to publish to, Sonatype/MavenCentral\n        maven {\n            // This is an arbitrary name, you may also use \"mavencentral\" or\n            // any other name that's descriptive for you\n\n            def releasesRepoUrl = \"https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/\"\n            def snapshotsRepoUrl = \"https://s01.oss.sonatype.org/content/repositories/snapshots/\"\n            // You only need this if you want to publish snapshots, otherwise just set the URL\n            // to the release repo directly\n            url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl\n\n            // The username and password we've fetched earlier\n            credentials {\n                username ossrhUsername\n                password ossrhPassword\n            }\n        }\n    }\n}\nsigning {\n    sign publishing.publications\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':anchortasklibrary'\ninclude ':app'\nrootProject.name = \"AnchorTask\""
  }
]