Repository: gdutxiaoxu/AnchorTask Branch: master Commit: 189b8956a0db Files: 78 Total size: 121.2 KB Directory structure: gitextract_gkjzk3zl/ ├── .gitignore ├── README.md ├── anchortasklibrary/ │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── xj/ │ │ └── anchortask/ │ │ └── library/ │ │ └── ExampleInstrumentedTest.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── xj/ │ │ └── anchortask/ │ │ └── library/ │ │ ├── AnchorConfig.kt │ │ ├── AnchorProject.kt │ │ ├── AnchorTaskException.kt │ │ ├── AnchorTaskRunnable.kt │ │ ├── AnchorTaskUtils.kt │ │ ├── IAnchorTask.kt │ │ ├── IAnchorTaskCreator.kt │ │ ├── ProjectBuilder.kt │ │ ├── ProjectListener.kt │ │ ├── TaskExecutorManager.kt │ │ ├── ThreadUtils.kt │ │ ├── log/ │ │ │ └── LogUtils.kt │ │ └── monitor/ │ │ ├── ExecuteMonitor.kt │ │ └── OnGetMonitorRecordCallback.kt │ └── test/ │ └── java/ │ └── com/ │ └── xj/ │ └── anchortask/ │ └── library/ │ └── ExampleUnitTest.kt ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ ├── src/ │ │ ├── androidTest/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── xj/ │ │ │ └── anchortask/ │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── xj/ │ │ │ │ └── anchortask/ │ │ │ │ ├── LogUtils.kt │ │ │ │ ├── LoggerPrinter.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MyApplication.kt │ │ │ │ ├── SPUtil.kt │ │ │ │ ├── anchorTask/ │ │ │ │ │ ├── AnchorTaskTestActivity.kt │ │ │ │ │ ├── ApplicationAnchorTaskCreator.kt │ │ │ │ │ └── TestTaskUtils.kt │ │ │ │ ├── appstartup/ │ │ │ │ │ └── InitializerSample.kt │ │ │ │ ├── asyncInflate/ │ │ │ │ │ ├── AsyncActivity.kt │ │ │ │ │ ├── AsyncInflateItem.kt │ │ │ │ │ ├── AsyncInflateKey.kt │ │ │ │ │ ├── AsyncInflateManager.kt │ │ │ │ │ ├── ThreadUtils.java │ │ │ │ │ └── page/ │ │ │ │ │ ├── AsyncFragment.kt │ │ │ │ │ └── AsyncUtils.kt │ │ │ │ ├── flowlayout/ │ │ │ │ │ ├── FlowLayoutDemo.kt │ │ │ │ │ └── TagGroup.java │ │ │ │ └── viewStub/ │ │ │ │ ├── MyViewStub.kt │ │ │ │ ├── ViewStubDemoActivity.kt │ │ │ │ ├── ViewStubTask.kt │ │ │ │ └── ViewStubTaskManager.kt │ │ │ └── res/ │ │ │ ├── drawable/ │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── drawable-v24/ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── layout/ │ │ │ │ ├── activity_anchortask_test.xml │ │ │ │ ├── activity_async.xml │ │ │ │ ├── activity_flow_layout_demo.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── activity_view_stub_demo.xml │ │ │ │ ├── fragment_asny.xml │ │ │ │ ├── layout_bottom.xml │ │ │ │ ├── layout_content.xml │ │ │ │ └── layout_title.xml │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── values/ │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── xj/ │ │ └── anchortask/ │ │ └── ExampleUnitTest.kt │ └── test.gradle ├── build.gradle ├── config.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── localconfig.gradle ├── publish-mavencentral.gradle └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/caches /.idea/libraries /.idea/modules.xml /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store /build /captures .externalNativeBuild .cxx ================================================ FILE: README.md ================================================ > 我的 CSDN 博客:https://blog.csdn.net/gdutxiaoxu
> 我的掘金:https://juejin.im/user/2207475076966584
> github: https://github.com/gdutxiaoxu/
> 微信公众号:程序员徐公
# AnchorTask 锚点任务,可以用来解决多线程加载任务依赖的问题。实现原理是使用有向无环图,常见的,比如 Android 启动优化,通常会进行多线程异步加载。 # 基本使用 第一步:在 moulde build.gradle 配置远程依赖 ``` implementation 'io.github.gdutxiaoxu:anchortask:1.1.0' ``` 最新的版本号可以看这里 [lastedt version](https://github.com/gdutxiaoxu/AnchorTask/tags) # 具体使用文档 ## 0.1.0 版本 0.1.0 版本使用说明见这里 [AnchorTask 0.1.0 版本使用说明](https://github.com/gdutxiaoxu/AnchorTask/wiki/AnchorTask-0.1.0-%E7%89%88%E6%9C%AC%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E), 0.1.0 版本实现借鉴了 [android-startup](https://github.com/idisfkj/android-startup),[AppStartFaster](https://github.com/NoEndToLF/AppStartFaster),[AnchorTask 0.1.0 原理 ](https://github.com/gdutxiaoxu/AnchorTask/wiki/AnchorTask-0.1.0-%E5%8E%9F%E7%90%86) ## 1.0.0 版本 [AnchorTask 1.0.0 版本使用说明](https://github.com/gdutxiaoxu/AnchorTask/wiki/AnchorTask-1.0.0-%E7%89%88%E6%9C%AC%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E),参考了阿里 [alpha](https://github.com/alibaba/alpha) [AnchorTask-1.0.0-原理说明](https://github.com/gdutxiaoxu/AnchorTask/wiki/AnchorTask-1.0.0-%E5%8E%9F%E7%90%86%E8%AF%B4%E6%98%8E) ## 两个版本之间区别 1. 之前的 0.1.0 版本 配置前置依赖任务,是通过 `AnchorTask getDependsTaskList` 的方式,这种方式不太直观,1.0.0 放弃了这种方式,参考阿里 `Alpha` 的方式,通过 `addTask(TASK_NAME_THREE).afterTask(TASK_NAME_ZERO, TASK_NAME_ONE)` 2. 1.0.0 版本新增了 Project 类,并增加 `OnProjectExecuteListener` 监听 3. 1.0.0 版本新增 `OnGetMonitorRecordCallback` 监听,方便统计各个任务的耗时 # 实现原理 AnchorTask 的原理不复杂,本质是有向无环图与多线程知识的结合。 1. 根据 BFS 构建出有向无环图,并得到它的拓扑排序 2. 在多线程执行过程中,我们是通过任务的子任务关系和 CounDownLatch 确保先后执行关系的 1. 前置任务没有执行完毕的话,等待,执行完毕的话,往下走 2. 执行任务 3. 通知子任务,当前任务执行完毕了,相应的计数器(入度数)要减一。 [Android 启动优化(一) - 有向无环图 ](https://juejin.cn/post/6926794003794903048) [Android 启动优化(二) - 拓扑排序的原理以及解题思路](https://juejin.cn/post/6930805971673415694) # 特别鸣谢 在实现这个开源框架的时候,借鉴了以下开源框架的思想。AppStartFaster 主要是通过 ClassName 找到相应的 Task,而阿里 alpha 是通过 taskName 找到相应的 Task,并且需要指定 ITaskCreator。两种方式各有优缺点,没有优劣之说,具体看使用场景。 [android-startup](https://github.com/idisfkj/android-startup) [alpha](https://github.com/alibaba/alpha) [AppStartFaster](https://github.com/NoEndToLF/AppStartFaster) # 系列文章 这几篇文章从 0 到 1,讲解 DAG 有向无环图是怎么实现的,以及在 Android 启动优化的应用。 **推荐理由:现在挺多文章一谈到启动优化,动不动就聊拓扑结构,这篇文章从数据结构到算法、到设计都给大家说清楚了,开源项目也有非常强的借鉴意义。** [Android 启动优化(一) - 有向无环图]( https://mp.weixin.qq.com/s/xWYe-uxgXTPuitYcLgXYNg) [Android 启动优化(二) - 拓扑排序的原理以及解题思路]( https://mp.weixin.qq.com/s/ShfxD_Z7M_NuWYNodn-vqA) [Android 启动优化(三)- AnchorTask 开源了]( https://mp.weixin.qq.com/s/YRUpf9jKEwIHV0A4FqltXg) [Android 启动优化(四)- AnchorTask 是怎么实现的](https://mp.weixin.qq.com/s/6RKco9JTm6ZrFyw99k9Rlg) [Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了]( https://mp.weixin.qq.com/s/0MsJa0ZepWkPUs-ymnVb-w) [Android 启动优化(六)- 深入理解布局优化](https://mp.weixin.qq.com/s/7_dQd2wGZYKWf9kHNlv2fg) **如果觉得对你有所帮助的,可以关注我的微信公众号,程序员徐公。主要更新 Android 技术,算法,职场相关的。** ![](https://raw.githubusercontent.com/gdutxiaoxu/blog_pic/master/21/0120210409172003.png) ================================================ FILE: anchortasklibrary/.gitignore ================================================ /build ================================================ FILE: anchortasklibrary/build.gradle ================================================ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'maven' //apply plugin: 'com.novoda.bintray-release' //apply plugin: 'com.github.panpf.bintray-publish' // 发布到本地仓库配置 uploadArchives { repositories.mavenDeployer { // 配置本地仓库路径,项目根目录下的repository目录中 repository(url: uri('../repository')) pom.groupId = "com.xj.android"// 唯一标识(通常为模块包名,也可以任意) pom.artifactId = "anchortask" // 项目名称(通常为类库模块名称,也可以任意) pom.version = "${anchorTaskPublicVersion}-local" // 版本号 } } // //publish { // userOrg = 'xujun94' // groupId = 'com.xj.android' // artifactId = 'anchortask' // publishVersion = "${anchorTaskPublicVersion}" // desc = 'anchortask' // website = 'https://github.com/gdutxiaoxu/anchortask' //} android { compileSdkVersion 30 buildToolsVersion "29.0.3" defaultConfig { minSdkVersion 16 targetSdkVersion 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { kotlinOptions.freeCompilerArgs += ['-module-name', "com.xj.android.anchortask"] } } dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } ext { PUBLISH_GROUP_ID = "com.xj.android" //项目包名 PUBLISH_ARTIFACT_ID = 'anchortask' //项目名 PUBLISH_VERSION = "${anchorTaskPublicVersion}" //版本号 } apply from: "${rootProject.projectDir}/publish-mavencentral.gradle" ================================================ FILE: anchortasklibrary/consumer-rules.pro ================================================ ================================================ FILE: anchortasklibrary/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: anchortasklibrary/src/androidTest/java/com/xj/anchortask/library/ExampleInstrumentedTest.kt ================================================ package com.xj.anchortask.library import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Test import org.junit.runner.RunWith import org.junit.Assert.* /** * Instrumented test, which will execute on an Android device. * * See [testing documentation](http://d.android.com/tools/testing). */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @Test fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.xj.anchortask.library.test", appContext.packageName) } } ================================================ FILE: anchortasklibrary/src/main/AndroidManifest.xml ================================================ / ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorConfig.kt ================================================ package com.xj.anchortask.library import com.xj.anchortask.library.log.LogUtils /** * Created by jun xu on 2/9/21. */ class AnchorConfig( logLevel: LogUtils.LogLevel = LogUtils.LogLevel.NONE, waringTime: Long = Long.MAX_VALUE, showToastToAlarm: Boolean = false ) { } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorProject.kt ================================================ package com.xj.anchortask.library import android.content.Context import com.xj.anchortask.library.log.LogUtils import com.xj.anchortask.library.monitor.ExecuteMonitor import com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CountDownLatch import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger /** * Created by jun xu on 2/1/21. */ class AnchorProject private constructor(val builder: Builder) { init { LogUtils.init("AnchorTaskLibrary") } private val threadPoolExecutor: ThreadPoolExecutor = builder.threadPoolExecutor ?: TaskExecutorManager.instance.cpuThreadPoolExecutor // 存储所有的任务,key 是 taskName,value 是 AnchorTask private val taskMap: MutableMap = builder.taskMap // 储存当前任务的子任务, key 是当前任务的 taskName,value 是 AnchorTask 的 list private val taskChildMap: MutableMap?> = builder.taskChildMap //需要等待的任务总数,用于阻塞 private val countDownLatch: CountDownLatch = builder.countDownLatch // 拓扑排序之后的主线程任务 private val mainList = builder.mainList // 拓扑排序之后的子线程任务 private val threadList = builder.threadList private val totalTaskSize = builder.list.size private val finishTask = AtomicInteger(0) private val listeners: CopyOnWriteArrayList = CopyOnWriteArrayList() private var iAnchorTaskCreator: IAnchorTaskCreator? = null private var cacheTask: AnchorTask? = null private val executeMonitor: ExecuteMonitor = ExecuteMonitor() var onGetMonitorRecordCallback: OnGetMonitorRecordCallback? = null fun addListener(onProjectExecuteListener: OnProjectExecuteListener) { listeners.add(onProjectExecuteListener) } fun removeListener(onProjectExecuteListener: OnProjectExecuteListener) { listeners.remove(onProjectExecuteListener) } fun record(taskName: String, executeTime: Long) { executeMonitor.record(taskName, executeTime) } /** * 通知 child countdown,当前的阻塞任务书也需要 countdown */ fun setNotifyChildren(anchorTask: AnchorTask) { taskChildMap[anchorTask.getTaskName()]?.forEach { taskMap[it.getTaskName()]?.countdown() } if (anchorTask.needWait()) { countDownLatch.countDown() } listeners.forEach { it.onTaskFinish(anchorTask.getTaskName()) } finishTask.incrementAndGet() if (finishTask.get() == totalTaskSize) { executeMonitor.recordProjectFinish() ThreadUtils.runOnUiThread(Runnable { onGetMonitorRecordCallback?.onGetProjectExecuteTime(executeMonitor.projectCostTime) onGetMonitorRecordCallback?.onGetTaskExecuteRecord(executeMonitor.executeTimeMap) }) listeners.forEach { it.onProjectFinish() } } } fun start(): AnchorProject { executeMonitor.recordProjectStart() this.listeners.forEach { it.onProjectStart() } this.threadList.forEach { threadPoolExecutor.execute(AnchorTaskRunnable(this, anchorTask = it)) } this.mainList.forEach { AnchorTaskRunnable(this, anchorTask = it).run() } return this } fun await(timeOutMillion: Long = -1) { if (timeOutMillion > 0) { countDownLatch.await(timeOutMillion, TimeUnit.MILLISECONDS) } else { countDownLatch.await() } } class Builder constructor() { companion object { private const val TAG = "AnchorTaskDispatcher" } var threadPoolExecutor: ThreadPoolExecutor? = null private set private lateinit var context: Context private var logLevel: LogUtils.LogLevel = LogUtils.logLevel private var timeOutMillion: Long = -1 // 存储所有的任务 val list: MutableList = ArrayList() // 存储所有的任务,key 是 taskName,value 是 AnchorTask val taskMap: MutableMap = HashMap() // 储存当前任务的子任务, key 是当前任务的 taskName,value 是 AnchorTask 的 list val taskChildMap: MutableMap?> = HashMap() // 拓扑排序之后的主线程任务 val mainList: MutableList = ArrayList() // 拓扑排序之后的子线程任务 val threadList: MutableList = ArrayList() //需要等待的任务总数,用于阻塞 lateinit var countDownLatch: CountDownLatch //需要等待的任务总数,用于CountDownLatch private val needWaitCount: AtomicInteger = AtomicInteger() private var startTime = -1L private val listeners: CopyOnWriteArrayList = CopyOnWriteArrayList() private var iAnchorTaskCreator: IAnchorTaskCreator? = null private val anchorTaskWrapper: TaskCreatorWrap = TaskCreatorWrap(iAnchorTaskCreator) private var cacheTask: AnchorTask? = null fun setAnchorTaskCreator(iAnchorTaskCreator: IAnchorTaskCreator): Builder { this.iAnchorTaskCreator = iAnchorTaskCreator anchorTaskWrapper.iAnchorTaskCreator = iAnchorTaskCreator return this } fun setContext(context: Context): Builder { this.context = context return this } fun setLogLevel(logLevel: LogUtils.LogLevel): Builder { this.logLevel = logLevel LogUtils.logLevel = logLevel return this } fun setThreadPoolExecutor(threadPoolExecutor: ThreadPoolExecutor?): Builder { this.threadPoolExecutor = threadPoolExecutor return this } fun setTimeOutMillion(timeOutMillion: Long): Builder { this.timeOutMillion = timeOutMillion return this } fun addTask(taskName: String): Builder { val createTask = anchorTaskWrapper.createTask(taskName) createTask ?: let { throw AnchorTaskException("could not find anchorTask, taskName is $taskName") } return addTask(anchorTask = createTask) } fun afterTask(vararg taskNames: String): Builder { cacheTask ?: let { throw AnchorTaskException("should be call addTask first") } taskNames.forEach { taskName -> val createTask = anchorTaskWrapper.createTask(taskName) createTask ?: let { throw AnchorTaskException("could not find anchorTask, taskName is $taskName") } cacheTask?.afterTask(taskName) } return this } fun afterTask(vararg anchorTasks: AnchorTask): Builder { cacheTask ?: let { throw AnchorTaskException("should be call addTask first") } anchorTasks.forEach { cacheTask?.afterTask(it.getTaskName()) } return this } fun addTask(anchorTask: AnchorTask): Builder { cacheTask = anchorTask list.add(anchorTask) anchorTask.onAdd() if (anchorTask.needWait()) { needWaitCount.incrementAndGet() } return this } fun build(): AnchorProject { val sortResult = AnchorTaskUtils.getSortResult(list, taskMap, taskChildMap) LogUtils.d(TAG, "start: sortResult is $sortResult") sortResult.forEach { if (it.isRunOnMainThread()) { mainList.add(it) } else { threadList.add(it) } } countDownLatch = CountDownLatch(needWaitCount.get()) return AnchorProject(this) } } } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskException.kt ================================================ package com.xj.anchortask.library import java.lang.RuntimeException /** * Created by jun xu on 2/1/21. */ class AnchorTaskException(message: String?) : RuntimeException(message) { } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskRunnable.kt ================================================ package com.xj.anchortask.library import android.os.Process import android.os.SystemClock /** * Created by jun xu on 2/2/21. * */ class AnchorTaskRunnable( private val anchorProject: AnchorProject, private val anchorTask: AnchorTask ) : Runnable { override fun run() { Process.setThreadPriority(anchorTask.priority()) // 前置任务没有执行完毕的话,等待,执行完毕的话,往下走 anchorTask.await() anchorTask.onStart() // 执行任务 val startTime = SystemClock.elapsedRealtime() anchorTask.run() val executeTime = SystemClock.elapsedRealtime() - startTime anchorProject.record(anchorTask.getTaskName(), executeTime) anchorTask.onFinish() // 通知子任务,当前任务执行完毕了,相应的计数器要减一。 anchorProject.setNotifyChildren(anchorTask) } } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/AnchorTaskUtils.kt ================================================ package com.xj.anchortask.library import com.xj.anchortask.library.log.LogUtils import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashMap import kotlin.collections.set /** * Created by jun xu on 2/1/21. */ object AnchorTaskUtils { @JvmStatic fun getSortResult( list: MutableList, taskMap: MutableMap, taskChildMap: MutableMap?> ): MutableList { val result = ArrayList() // 入度为 0 的队列 val queue = ArrayDeque() val taskIntegerHashMap = HashMap() // 建立每个 task 的入度关系 list.forEach { anchorTask: AnchorTask -> val taskName = anchorTask.getTaskName() if (taskIntegerHashMap.containsKey(taskName)) { throw AnchorTaskException("anchorTask is repeat, anchorTask is $anchorTask, list is $list") } val size = anchorTask.getDependsTaskList()?.size ?: 0 taskIntegerHashMap[taskName] = size taskMap[taskName] = anchorTask if (size == 0) { queue.offer(anchorTask) } } // 建立每个 task 的 children 关系 list.forEach { anchorTask: AnchorTask -> anchorTask.getDependsTaskList()?.forEach { taskName: String -> var list = taskChildMap[taskName] if (list == null) { list = ArrayList() } list.add(anchorTask) taskChildMap[taskName] = list } } taskChildMap.entries.iterator().forEach { LogUtils.d("TAG","key is ${it.key}, value is ${it.value}") } // 使用 BFS 方法获得有向无环图的拓扑排序 while (!queue.isEmpty()) { val anchorTask = queue.pop() result.add(anchorTask) val taskName = anchorTask.getTaskName() taskChildMap[taskName]?.forEach { // 遍历所有依赖这个顶点的顶点,移除该顶点之后,如果入度为 0,加入到改队列当中 val key = it.getTaskName() var result = taskIntegerHashMap[key] ?: 0 result-- if (result == 0) { queue.offer(it) } taskIntegerHashMap[key] = result } } // size 不相等,证明有环 if (list.size != result.size) { throw AnchorTaskException("Ring appeared,Please check.list is $list, result is $result") } return result } } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/IAnchorTask.kt ================================================ package com.xj.anchortask.library import android.os.Process import androidx.annotation.CallSuper import androidx.annotation.IntRange import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CountDownLatch /** * Created by jun xu on 2/1/21. */ interface IAnchorTask : IAnchorCallBack { /** * 获取任务昵称 */ fun getTaskName(): String /** * 是否在主线程执行 */ fun isRunOnMainThread(): Boolean /** * 任务优先级别 */ @IntRange( from = Process.THREAD_PRIORITY_FOREGROUND.toLong(), to = Process.THREAD_PRIORITY_LOWEST.toLong() ) fun priority(): Int /** * 调用 await 方法,是否需要等待改任务执行完成 * true 不需要 * false 需要 */ fun needWait(): Boolean /** * 任务被执行的时候回调 */ fun run() } interface IAnchorCallBack { fun onAdd() fun onStart() fun onFinish() } class SimpleAnchorCallBack : IAnchorCallBack { override fun onAdd() { } override fun onStart() { } override fun onFinish() { } } abstract class AnchorTask(private val name: String) : IAnchorTask { companion object { const val TAG = "AnchorTask" } private lateinit var countDownLatch: CountDownLatch private val copyOnWriteArrayList: CopyOnWriteArrayList by lazy { CopyOnWriteArrayList() } val dependList: MutableList = ArrayList() private fun getListSize() = getDependsTaskList()?.size ?: 0 override fun getTaskName(): String { return name } override fun priority(): Int { return Process.THREAD_PRIORITY_FOREGROUND } override fun needWait(): Boolean { return true } fun afterTask(taskName: String) { dependList.add(taskName) } /** * self call,await */ fun await() { tryToInitCountDown() countDownLatch.await() } @Synchronized private fun tryToInitCountDown() { if (!this::countDownLatch.isInitialized) { countDownLatch = CountDownLatch(dependList.size) } } /** * parent call, countDown */ fun countdown() { tryToInitCountDown() countDownLatch.countDown() } override fun isRunOnMainThread(): Boolean { return false } fun getDependsTaskList(): List? { return dependList } @CallSuper override fun onAdd() { copyOnWriteArrayList.forEach { it.onAdd() } } @CallSuper override fun onStart() { copyOnWriteArrayList.forEach { it.onStart() } } @CallSuper override fun onFinish() { copyOnWriteArrayList.forEach { it.onFinish() } } fun addCallback(iAnchorCallBack: IAnchorCallBack?) { iAnchorCallBack ?: return copyOnWriteArrayList.add(iAnchorCallBack) } fun removeCallback(iAnchorCallBack: IAnchorCallBack?) { iAnchorCallBack ?: return copyOnWriteArrayList.remove(iAnchorCallBack) } override fun toString(): String { return "AnchorTask(name='$name',dependList is $dependList)" } } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/IAnchorTaskCreator.kt ================================================ package com.xj.anchortask.library /** * Created by jun xu on 2/9/21. */ open interface IAnchorTaskCreator { /** * 根据Task名称,创建Task实例。这个接口需要使用者自己实现。创建后的实例会被缓存起来。 * @param taskName Task名称 * @return Task实例 */ fun createTask(taskName: String): AnchorTask? } open class TaskCreatorWrap(var iAnchorTaskCreator: IAnchorTaskCreator?) : IAnchorTaskCreator { private val map: MutableMap = HashMap() override fun createTask(taskName: String): AnchorTask? { val anchorTask = map[taskName] anchorTask?.let { return it } return iAnchorTaskCreator?.createTask(taskName) } fun checkTaskIsExits(taskName: String): Boolean { return map.containsKey(taskName) } } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/ProjectBuilder.kt ================================================ package com.xj.anchortask.library import android.content.Context import com.xj.anchortask.library.log.LogUtils import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CountDownLatch import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.atomic.AtomicInteger /** * Created by jun xu on 2/1/21. */ ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/ProjectListener.kt ================================================ package com.xj.anchortask.library /** * Created by jun xu on 2/8/21. */ interface OnProjectExecuteListener { /** * 当`Project`开始执行时,调用该函数。

* **注意:**该回调函数在`Task`所在线程中回调,注意线程安全。 */ fun onProjectStart() /** * 当`Project`其中一个`Task`执行结束时,调用该函数。

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

* **注意:**该回调函数在`Task`所在线程中回调,注意线程安全。 */ fun onProjectFinish() } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/TaskExecutorManager.kt ================================================ package com.xj.anchortask.library import java.util.concurrent.* class TaskExecutorManager private constructor() { //获得cpu密集型线程池,因为占据CPU的时间片过多的话会影响性能,所以这里控制了最大并发,防止主线程的时间片减少 //CPU 密集型任务的线程池 val cpuThreadPoolExecutor: ThreadPoolExecutor //获得io密集型线程池,有好多任务其实占用的CPU time非常少,所以使用缓存线程池,基本上来着不拒 // IO 密集型任务的线程池 val ioThreadPoolExecutor: ExecutorService //线程池队列 private val mPoolWorkQueue: BlockingQueue = LinkedBlockingQueue() // 这个是为了保障任务超出BlockingQueue的最大值,且线程池中的线程数已经达到MAXIMUM_POOL_SIZE时候,还有任务到来会采取任务拒绝策略,这里定义的策略就是 //再开一个缓存线程池去执行。当然BlockingQueue默认的最大值是int_max,所以理论上这里是用不到的 private val mHandler = RejectedExecutionHandler { r, executor -> Executors.newCachedThreadPool().execute(r) } companion object { //CPU 核数 private val CPU_COUNT = Runtime.getRuntime().availableProcessors() //线程池线程数 private val CORE_POOL_SIZE = Math.max( 2, Math.min(CPU_COUNT - 1, 5) ) //线程池线程数的最大值 private val MAXIMUM_POOL_SIZE = CORE_POOL_SIZE //线程空置回收时间 private const val KEEP_ALIVE_SECONDS = 5 @JvmStatic val instance: TaskExecutorManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { TaskExecutorManager() } } //初始化线程池 init { cpuThreadPoolExecutor = ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS.toLong(), TimeUnit.SECONDS, mPoolWorkQueue, Executors.defaultThreadFactory(), mHandler ).apply { allowCoreThreadTimeOut(true) } ioThreadPoolExecutor = Executors.newCachedThreadPool(Executors.defaultThreadFactory()) } } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/ThreadUtils.kt ================================================ package com.xj.anchortask.library import android.os.Handler import android.os.Looper object ThreadUtils { fun runOnUiThread(r: Runnable) { if (isMainThread) { r.run() } else { LazyHolder.sUiThreadHandler.post(r) } } fun runOnUiThreadAtFront(r: Runnable) { if (isMainThread) { r.run() } else { LazyHolder.sUiThreadHandler.postAtFrontOfQueue(r) } } fun runOnUiThread(r: Runnable?, delay: Long) { LazyHolder.sUiThreadHandler.postDelayed(r!!, delay) } fun removeCallbacks(r: Runnable?) { LazyHolder.sUiThreadHandler.removeCallbacks(r!!) } val isMainThread: Boolean get() = Looper.getMainLooper() == Looper.myLooper() fun checkAtMainThread() { val e = RuntimeException("not main thread") LazyHolder.sUiThreadHandler.postDelayed({ throw e }, 500) } private object LazyHolder { val sUiThreadHandler = Handler(Looper.getMainLooper()) } } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/log/LogUtils.kt ================================================ package com.xj.anchortask.library.log import android.util.Log /** * Created by jun xu on 2/2/21. */ object LogUtils { enum class LogLevel { NONE { override val value: Int get() = -1 }, ERROR { override val value: Int get() = 0 }, WARN { override val value: Int get() = 1 }, INFO { override val value: Int get() = 2 }, DEBUG { override val value: Int get() = 3 }; abstract val value: Int } private var TAG = "SAF_L" var logLevel = LogLevel.INFO // 日志的等级,可以进行配置,最好在Application中进行全局的配置 @JvmStatic fun init(clazz: Class<*>) { TAG = clazz.simpleName } /** * 支持用户自己传tag,可扩展性更好 * @param tag */ @JvmStatic fun init(tag: String) { TAG = tag } @JvmStatic fun e(tag: String, msg: String) { if (LogLevel.ERROR.value <= logLevel.value) { if (msg.isNotBlank()) { Log.e("$TAG$tag", msg) } } } @JvmStatic fun w(tag: String, msg: String) { if (LogLevel.WARN.value <= logLevel.value) { if (msg.isNotBlank()) { Log.e("$TAG$tag", msg) } } } @JvmStatic fun i(tag: String, msg: String) { if (LogLevel.INFO.value <= logLevel.value) { if (msg.isNotBlank()) { Log.i("$TAG$tag", msg) } } } @JvmStatic fun d(tag: String, msg: String) { if (LogLevel.DEBUG.value <= logLevel.value) { if (msg.isNotBlank()) { Log.d("$TAG$tag", msg) } } } } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/monitor/ExecuteMonitor.kt ================================================ /* * Copyright 2018 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.xj.anchortask.library.monitor import android.os.Handler import android.os.Looper import java.util.concurrent.ConcurrentHashMap /** * * 监控`Project`执行性能的类。会记录每一个`Task`执行时间,以及整个`Project`执行时间。 * */ internal class ExecuteMonitor { private val mExecuteTimeMap: MutableMap = ConcurrentHashMap() private var mStartTime: Long = 0 /** * @return `Project`执行时间。 */ var projectCostTime: Long = 0 private set private var mHandler: Handler? = null /** * 记录`task`执行时间。 * * @param taskName `task`的名称 * @param executeTime 执行的时间 */ fun record(taskName: String, executeTime: Long) { // AlphaLog.d(AlphaLog.GLOBAL_TAG, "AlphaTask-->Startup task %s cost time: %s ms, in thread: %s", taskName, executeTime, Thread.currentThread().getName()); // if (executeTime >= AlphaConfig.getWarmingTime()) { // toastToWarn("AlphaTask %s run too long, cost time: %s", taskName, executeTime); // } mExecuteTimeMap[taskName] = executeTime } /** * @return 已执行完的每个task的执行时间。 */ val executeTimeMap: Map get() = mExecuteTimeMap /** * 在`Project`开始执行时打点,记录开始时间。 */ fun recordProjectStart() { mStartTime = System.currentTimeMillis() } /** * 在`Project`结束时打点,记录耗时。 */ fun recordProjectFinish() { projectCostTime = System.currentTimeMillis() - mStartTime // AlphaLog.d("==ALPHA==", "tm start up cost time: %s ms", mProjectCostTime); } /** * 通过弹出`toast`来告警。 * * @param msg 告警内容 * @param args format参数 */ private fun toastToWarn(msg: String, vararg args: Any) { handler.post { val formattedMsg: String formattedMsg = if (args == null) { msg } else { String.format(msg, *args) } // Toast.makeText(AlphaConfig.getContext(), formattedMsg, Toast.LENGTH_SHORT).show(); } } private val handler: Handler private get() { if (mHandler == null) { mHandler = Handler(Looper.getMainLooper()) } return mHandler!! } } ================================================ FILE: anchortasklibrary/src/main/java/com/xj/anchortask/library/monitor/OnGetMonitorRecordCallback.kt ================================================ /* * Copyright 2018 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.xj.anchortask.library.monitor /** * * 获取`Project`执行性能记录的回调 * */ interface OnGetMonitorRecordCallback { /** * 获取`task`执行的耗时。 * @param result `task`执行的耗时。`key`是`task`名称,`value`是`task`执行耗时,单位是毫秒。 */ fun onGetTaskExecuteRecord(result: Map?) /** * 获取整个`Project`执行耗时。 * @param costTime 整个`Project`执行耗时。 */ fun onGetProjectExecuteTime(costTime: Long) } ================================================ FILE: anchortasklibrary/src/test/java/com/xj/anchortask/library/ExampleUnitTest.kt ================================================ package com.xj.anchortask.library import org.junit.Test import org.junit.Assert.* /** * Example local unit test, which will execute on the development machine (host). * * See [testing documentation](http://d.android.com/tools/testing). */ class ExampleUnitTest { @Test fun addition_isCorrect() { assertEquals(4, 2 + 2) } } ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply from: '../localconfig.gradle' android { compileSdkVersion 30 buildToolsVersion "29.0.3" defaultConfig { applicationId "com.xj.anchortask" minSdkVersion 16 targetSdkVersion 30 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } Properties properties = loadProperties("local.properties") String mode = null if (properties != null) { mode = getValue(properties, "mode", "local") } println("mode is $mode") print("anchorTaskVersion is ${anchorTaskVersion}") dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'com.google.android:flexbox:2.0.1' implementation "androidx.startup:startup-runtime:1.0.0" implementation 'androidx.work:work-runtime:2.3.4' implementation 'com.google.android.material:material:1.4.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' if (mode == "local") { implementation "com.xj.android:anchortask:${anchorTaskVersion}-local" } else if (mode == "remote") { implementation "com.xj.android:anchortask:${anchorTaskVersion}" } else { implementation project(path: ':anchortasklibrary') } } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile ================================================ FILE: app/src/androidTest/java/com/xj/anchortask/ExampleInstrumentedTest.kt ================================================ package com.xj.anchortask import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Test import org.junit.runner.RunWith import org.junit.Assert.* /** * Instrumented test, which will execute on an Android device. * * See [testing documentation](http://d.android.com/tools/testing). */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { @Test fun useAppContext() { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.xj.anchortask", appContext.packageName) } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/xj/anchortask/LogUtils.kt ================================================ package com.xj.anchortask import android.util.Log import org.json.JSONArray import org.json.JSONException import org.json.JSONObject /** * Created by jun xu on 2/2/21. */ object LogUtils { enum class LogLevel { ERROR { override val value: Int get() = 0 }, WARN { override val value: Int get() = 1 }, INFO { override val value: Int get() = 2 }, DEBUG { override val value: Int get() = 3 }; abstract val value: Int } private var TAG = "AnchorTask" var logLevel = LogLevel.DEBUG // 日志的等级,可以进行配置,最好在Application中进行全局的配置 var printMsgStack = false @JvmStatic fun init(clazz: Class<*>) { TAG = clazz.simpleName } /** * 支持用户自己传tag,可扩展性更好 * @param tag */ @JvmStatic fun init(tag: String) { TAG = tag } @JvmStatic fun e(tag: String, msg: String) { if (LogLevel.ERROR.value <= logLevel.value) { if (msg.isNotBlank()) { val s = getMethodNames() Log.e("${TAG}_${tag}", String.format(s, msg)) } } } @JvmStatic fun w(tag: String, msg: String) { if (LogLevel.WARN.value <= logLevel.value) { if (msg.isNotBlank()) { val s = getMethodNames() Log.e("${TAG}_${tag}", String.format(s, msg)) } } } @JvmStatic fun i(tag: String, msg: String) { if (LogLevel.INFO.value <= logLevel.value) { if (msg.isNotBlank()) { val s = getMethodNames() Log.i("${TAG}_${tag}", String.format(s, msg)) } } } @JvmStatic fun d(tag: String, msg: String) { if (LogLevel.DEBUG.value <= logLevel.value) { if (msg.isNotBlank()) { val s = getMethodNames() Log.d("${TAG}_${tag}", String.format(s, msg)) } } } @JvmStatic fun json(tag: String, json: String) { var json = json if (json.isBlank()) { d(tag, "Empty/Null json content") return } try { json = json.trim { it <= ' ' } if (json.startsWith("{")) { val jsonObject = JSONObject(json) var message = jsonObject.toString(LoggerPrinter.JSON_INDENT) message = message.replace("\n".toRegex(), "\n║ ") val s = getMethodNames() println(String.format(s, message)) return } if (json.startsWith("[")) { val jsonArray = JSONArray(json) var message = jsonArray.toString(LoggerPrinter.JSON_INDENT) message = message.replace("\n".toRegex(), "\n║ ") val s = getMethodNames() println(String.format(s, message)) return } e(tag, "Invalid Json") } catch (e: JSONException) { e(tag, "Invalid Json") } } private fun getMethodNames(): String { if (!printMsgStack) { return "%s" } val sElements = Thread.currentThread().stackTrace var stackOffset = LoggerPrinter.getStackOffset(sElements) stackOffset++ val builder = StringBuilder() builder.append(LoggerPrinter.TOP_BORDER).append("\r\n") // 添加当前线程名 .append("║ " + "Thread: " + Thread.currentThread().name).append("\r\n") .append(LoggerPrinter.MIDDLE_BORDER).append("\r\n") // 添加类名、方法名、行数 .append("║ ") .append(sElements[stackOffset].className) .append(".") .append(sElements[stackOffset].methodName) .append(" ") .append(" (") .append(sElements[stackOffset].fileName) .append(":") .append(sElements[stackOffset].lineNumber) .append(")") .append("\r\n") .append(LoggerPrinter.MIDDLE_BORDER).append("\r\n") // 添加打印的日志信息 .append("║ ").append("%s").append("\r\n") .append(LoggerPrinter.BOTTOM_BORDER).append("\r\n") return builder.toString() } } ================================================ FILE: app/src/main/java/com/xj/anchortask/LoggerPrinter.kt ================================================ package com.xj.anchortask /** * Created by jun xu on 2/2/21. */ object LoggerPrinter { private val MIN_STACK_OFFSET = 3 /** * Drawing toolbox */ private val TOP_LEFT_CORNER = '╔' private val BOTTOM_LEFT_CORNER = '╚' private val MIDDLE_CORNER = '╟' private val HORIZONTAL_DOUBLE_LINE = '║' private val DOUBLE_DIVIDER = "════════════════════════════════════════════" private val SINGLE_DIVIDER = "────────────────────────────────────────────" val TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER val BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER val MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER /** * It is used for json pretty print */ val JSON_INDENT = 2 fun getStackOffset(trace: Array): Int { var i = MIN_STACK_OFFSET while (i < trace.size) { val e = trace[i] val name = e.className if (name != LoggerPrinter::class.java.name && name != LogUtils::class.java.name) { return --i } i++ } return -1 } } ================================================ FILE: app/src/main/java/com/xj/anchortask/MainActivity.kt ================================================ package com.xj.anchortask import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.xj.anchortask.anchorTask.AnchorTaskTestActivity import com.xj.anchortask.asyncInflate.AsyncActivity import com.xj.anchortask.asyncInflate.page.AsyncUtils import com.xj.anchortask.asyncInflate.page.AsyncUtils.isHomeFragmentOpen import com.xj.anchortask.flowlayout.FlowLayoutDemo import com.xj.anchortask.viewStub.ViewStubDemoActivity import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn_view_stub.setOnClickListener { startActivity(Intent(this@MainActivity, ViewStubDemoActivity::class.java)) } btn_anchortask.setOnClickListener { startActivity(Intent(this@MainActivity, AnchorTaskTestActivity::class.java)) } btn_flow_layout_demo.setOnClickListener { startActivity(Intent(this@MainActivity, FlowLayoutDemo::class.java)) } btn_async.setOnClickListener { startActivity(Intent(this@MainActivity, AsyncActivity::class.java)) } val isOpen = AsyncUtils.isHomeFragmentOpen() updateText(isOpen) switch_async.isChecked = isOpen ll_switch.setOnClickListener { val b = !switch_async.isChecked updateChecked(b) } switch_async.setOnCheckedChangeListener { buttonView, isChecked -> updateChecked(isChecked) } } private fun updateChecked(b: Boolean) { updateText(b) switch_async.isChecked = b getSP("async_config").spApplyBoolean("home_fragment_switch", b) } private fun updateText(b: Boolean) { if (b) { tv_asnyc_text.setText("首页 Fragment 开启异步加载") } else { tv_asnyc_text.setText("首页 Fragment 关闭异步加载") } } } ================================================ FILE: app/src/main/java/com/xj/anchortask/MyApplication.kt ================================================ package com.xj.anchortask import android.app.Application import android.content.Context import android.util.Log import com.xj.anchortask.anchorTask.TestTaskUtils import com.xj.anchortask.asyncInflate.page.AsyncUtils import com.xj.anchortask.library.OnProjectExecuteListener import com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback /** * Created by jun xu on 2/1/21. */ class MyApplication : Application() { companion object { const val TAG = "AnchorTaskApplication" lateinit var myApplication: MyApplication private set @JvmStatic fun getInstance(): Application { return myApplication } } override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) Log.i(TAG, "attachBaseContext: ") } override fun onCreate() { super.onCreate() Log.i(TAG, "onCreate: ") myApplication = this TestTaskUtils.executeTask(this, projectExecuteListener = object : OnProjectExecuteListener { override fun onProjectFinish() { } override fun onProjectStart() { } override fun onTaskFinish(taskName: String) { } }, onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback { override fun onGetProjectExecuteTime(costTime: Long) { Log.i(TAG, "onGetProjectExecuteTime: costTime is $costTime") } override fun onGetTaskExecuteRecord(result: Map?) { Log.i(TAG, "onGetTaskExecuteRecord: result is $result") } }) } } ================================================ FILE: app/src/main/java/com/xj/anchortask/SPUtil.kt ================================================ package com.xj.anchortask import android.content.Context import android.content.SharedPreferences fun getSP(name: String) = MyApplication.getInstance().getSharedPreferences(name, Context.MODE_PRIVATE)!! fun SharedPreferences.getSPEditor() = this.edit()!! fun SharedPreferences.spApplyClear() = getSPEditor().clear().apply() fun SharedPreferences.spCommitClear() = getSPEditor().clear().commit() fun SharedPreferences.spApplyInt(key: String, value: Int) = getSPEditor().putInt(key, value).apply() fun SharedPreferences.spCommitInt(key: String, value: Int) = getSPEditor().putInt(key, value).commit() fun SharedPreferences.spGetInt(key: String, defValue: Int = 0): Int = this.getInt(key, defValue) fun SharedPreferences.spApplyLong(key: String, value: Long) = getSPEditor().putLong(key, value).apply() fun SharedPreferences.spCommitLong(key: String, value: Long) = getSPEditor().putLong(key, value).commit() fun SharedPreferences.spGetLong(key: String, defValue: Long = 0): Long = this.getLong(key, defValue) fun SharedPreferences.spApplyFloat(key: String, value: Float) = getSPEditor().putFloat(key, value).apply() fun SharedPreferences.spCommitFloat(key: String, value: Float) = getSPEditor().putFloat(key, value).commit() fun SharedPreferences.spGetFloat(key: String, defValue: Float = 0F): Float = this.getFloat(key, defValue) fun SharedPreferences.spApplyBoolean(key: String, value: Boolean) = getSPEditor().putBoolean(key, value).apply() fun SharedPreferences.spCommitBoolean(key: String, value: Boolean) = getSPEditor().putBoolean(key, value).commit() fun SharedPreferences.spGetBoolean(key: String, defValue: Boolean = false): Boolean = this.getBoolean(key, defValue) fun SharedPreferences.spApplyString(key: String, value: String) = getSPEditor().putString(key, value).apply() fun SharedPreferences.spCommitString(key: String, value: String) = getSPEditor().putString(key, value).commit() fun SharedPreferences.spGetString(key: String, defValue: String = ""): String? = this.getString(key, defValue) fun SharedPreferences.spApplyStringSet(key: String, value: Set) = getSPEditor().putStringSet(key, value).apply() fun SharedPreferences.spCommitStringSet(key: String, value: Set) = getSPEditor().putStringSet(key, value).commit() fun SharedPreferences.spGetStringSet(key: String, defValue: Set? = null): Set? = this.getStringSet(key, defValue) fun SharedPreferences.spCommitRemove(key: String) = getSPEditor().remove(key).commit() fun SharedPreferences.spApplyRemove(key: String) = getSPEditor().remove(key).commit() ================================================ FILE: app/src/main/java/com/xj/anchortask/anchorTask/AnchorTaskTestActivity.kt ================================================ package com.xj.anchortask.anchorTask import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.xj.anchortask.R import com.xj.anchortask.library.OnProjectExecuteListener import com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback import kotlinx.android.synthetic.main.activity_anchortask_test.* class AnchorTaskTestActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_anchortask_test) initAnchorTask() } private fun initAnchorTask() { val sb2 = StringBuilder() text2.text = "正在执行中" val projectExecuteListener = object : OnProjectExecuteListener { val sb = StringBuffer() override fun onProjectStart() { if (sb.length > 0) { sb.delete(0, sb.length) } } override fun onTaskFinish(taskName: String) { sb.append("task $taskName execute finish \n") } override fun onProjectFinish() { text.post { text.setText(sb.toString()) } } } val onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback { override fun onGetTaskExecuteRecord(result: Map?) { result?.entries?.iterator()?.forEach { sb2.append(it.key).append("执行耗时").append(it.value).append("毫秒\n") } text2.text = sb2.toString() } override fun onGetProjectExecuteTime(costTime: Long) { sb2.append("总共执行耗时").append(costTime).append("毫秒\n") } } btn_execute.setOnClickListener { sb2.clear() text2.text = "正在执行中" try { TestTaskUtils.executeTask( this, projectExecuteListener, onGetMonitorRecordCallback ) } catch (e: Exception) { e.printStackTrace() } } btn_execute2.setOnClickListener { sb2.clear() text2.text = "正在执行中2" try { TestTaskUtils.executeTask( this, projectExecuteListener, onGetMonitorRecordCallback ) } catch (e: Exception) { e.printStackTrace() } } } } ================================================ FILE: app/src/main/java/com/xj/anchortask/anchorTask/ApplicationAnchorTaskCreator.kt ================================================ package com.xj.anchortask.anchorTask import com.xj.anchortask.LogUtils import com.xj.anchortask.library.AnchorTask import com.xj.anchortask.library.IAnchorTaskCreator /** * Created by jun xu on 2/1/21. */ const val TASK_NAME_ZERO = "zero" const val TASK_NAME_ONE = "one" const val TASK_NAME_TWO = "two" const val TASK_NAME_THREE = "three" const val TASK_NAME_FOUR = "four" const val TASK_NAME_FIVE = "five" class ApplicationAnchorTaskCreator : IAnchorTaskCreator { override fun createTask(taskName: String): AnchorTask? { when (taskName) { TASK_NAME_ZERO -> { return AnchorTaskZero() } TASK_NAME_ONE -> { return AnchorTaskOne() } TASK_NAME_TWO -> { return AnchorTaskTwo() } TASK_NAME_THREE -> { return AnchorTaskThree() } TASK_NAME_FOUR -> { return AnchorTaskFour() } TASK_NAME_FIVE -> { return AnchorTaskFive() } } return null } } class AnchorTaskZero() : AnchorTask(TASK_NAME_ZERO) { override fun isRunOnMainThread(): Boolean { return false } override fun run() { val start = System.currentTimeMillis() try { Thread.sleep(300) } catch (e: Exception) { } LogUtils.i( TAG, "AnchorTaskOne: " + (System.currentTimeMillis() - start) ) } } class AnchorTaskOne : AnchorTask(TASK_NAME_ONE) { override fun isRunOnMainThread(): Boolean { return false } override fun run() { val start = System.currentTimeMillis() try { Thread.sleep(300) } catch (e: Exception) { } LogUtils.i( TAG, "AnchorTaskOne: " + (System.currentTimeMillis() - start) ) } } class AnchorTaskTwo : AnchorTask(TASK_NAME_TWO) { override fun isRunOnMainThread(): Boolean { return false } override fun run() { val start = System.currentTimeMillis() try { Thread.sleep(300) } catch (e: Exception) { } LogUtils.i( TAG, "AnchorTaskTwo执行耗时: " + (System.currentTimeMillis() - start) ) } } class AnchorTaskThree : AnchorTask(TASK_NAME_THREE) { override fun isRunOnMainThread(): Boolean { return false } override fun run() { val start = System.currentTimeMillis() try { Thread.sleep(300) } catch (e: Exception) { } LogUtils.i( TAG, "AnchorTaskThree执行耗时: " + (System.currentTimeMillis() - start) ) } } class AnchorTaskFour : AnchorTask(TASK_NAME_FOUR) { override fun isRunOnMainThread(): Boolean { return false } override fun run() { val start = System.currentTimeMillis() try { Thread.sleep(300) } catch (e: Exception) { } LogUtils.i( TAG, "AnchorTaskFour执行耗时: " + (System.currentTimeMillis() - start) ) } } class AnchorTaskFive : AnchorTask(TASK_NAME_FIVE) { override fun isRunOnMainThread(): Boolean { return false } override fun run() { val start = System.currentTimeMillis() try { Thread.sleep(300) } catch (e: Exception) { } LogUtils.i( TAG, "AnchorTaskFive执行耗时: " + (System.currentTimeMillis() - start) ) } } ================================================ FILE: app/src/main/java/com/xj/anchortask/anchorTask/TestTaskUtils.kt ================================================ package com.xj.anchortask.anchorTask import android.content.Context import com.xj.anchortask.MyApplication import com.xj.anchortask.library.AnchorProject import com.xj.anchortask.library.OnProjectExecuteListener import com.xj.anchortask.library.TaskExecutorManager import com.xj.anchortask.library.log.LogUtils import com.xj.anchortask.library.monitor.OnGetMonitorRecordCallback /** * Created by jun xu on 2/9/21. */ object TestTaskUtils { fun executeTask( context: Context, projectExecuteListener: OnProjectExecuteListener? = null, onGetMonitorRecordCallback: OnGetMonitorRecordCallback? = null ) { val project = AnchorProject.Builder().setContext(context).setLogLevel(LogUtils.LogLevel.DEBUG) .setAnchorTaskCreator(ApplicationAnchorTaskCreator()) .addTask(TASK_NAME_ZERO) .addTask(TASK_NAME_ONE) .addTask(TASK_NAME_TWO) .addTask(TASK_NAME_THREE).afterTask( TASK_NAME_ZERO, TASK_NAME_ONE ) .addTask(TASK_NAME_FOUR).afterTask( TASK_NAME_ONE, TASK_NAME_TWO ) .addTask(TASK_NAME_FIVE).afterTask( TASK_NAME_THREE, TASK_NAME_FOUR ) .setThreadPoolExecutor(TaskExecutorManager.instance.cpuThreadPoolExecutor) .build() project.addListener(object : OnProjectExecuteListener { override fun onProjectStart() { com.xj.anchortask.LogUtils.i( MyApplication.TAG, "onProjectStart " ) } override fun onTaskFinish(taskName: String) { com.xj.anchortask.LogUtils.i( MyApplication.TAG, "onTaskFinish, taskName is $taskName" ) } override fun onProjectFinish() { com.xj.anchortask.LogUtils.i( MyApplication.TAG, "onProjectFinish " ) } }) projectExecuteListener?.let { project.addListener(it) } project.onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback { override fun onGetTaskExecuteRecord(result: Map?) { onGetMonitorRecordCallback?.onGetTaskExecuteRecord(result) } override fun onGetProjectExecuteTime(costTime: Long) { onGetMonitorRecordCallback?.onGetProjectExecuteTime(costTime) } } project.start().await(1000) } fun executeTask2( context: Context, projectExecuteListener: OnProjectExecuteListener? = null, onGetMonitorRecordCallback: OnGetMonitorRecordCallback? = null ) { val anchorTaskZero = AnchorTaskZero() val anchorTaskOne = AnchorTaskOne() val anchorTaskTwo = AnchorTaskTwo() val anchorTaskThree = AnchorTaskThree() val anchorTaskFour = AnchorTaskFour() val anchorTaskFive = AnchorTaskFive() val project = AnchorProject.Builder().setContext(context).setLogLevel(LogUtils.LogLevel.DEBUG) .setAnchorTaskCreator(ApplicationAnchorTaskCreator()) .addTask(anchorTaskZero) .addTask(anchorTaskOne) .addTask(anchorTaskTwo) .addTask(anchorTaskThree).afterTask( anchorTaskZero, anchorTaskOne ) .addTask(TASK_NAME_FOUR).afterTask( anchorTaskOne, anchorTaskTwo ) .addTask(TASK_NAME_FIVE).afterTask( anchorTaskThree, anchorTaskFive ) .setThreadPoolExecutor(TaskExecutorManager.instance.cpuThreadPoolExecutor) .build() project.addListener(object : OnProjectExecuteListener { override fun onProjectStart() { com.xj.anchortask.LogUtils.i( MyApplication.TAG, "onProjectStart " ) } override fun onTaskFinish(taskName: String) { com.xj.anchortask.LogUtils.i( MyApplication.TAG, "onTaskFinish, taskName is $taskName" ) } override fun onProjectFinish() { com.xj.anchortask.LogUtils.i( MyApplication.TAG, "onProjectFinish " ) } }) projectExecuteListener?.let { project.addListener(it) } project.onGetMonitorRecordCallback = object : OnGetMonitorRecordCallback { override fun onGetTaskExecuteRecord(result: Map?) { onGetMonitorRecordCallback?.onGetTaskExecuteRecord(result) } override fun onGetProjectExecuteTime(costTime: Long) { onGetMonitorRecordCallback?.onGetProjectExecuteTime(costTime) } } project.start().await(1000) } } ================================================ FILE: app/src/main/java/com/xj/anchortask/appstartup/InitializerSample.kt ================================================ package com.xj.anchortask.appstartup import android.content.Context import android.util.Log import androidx.startup.AppInitializer import androidx.startup.Initializer import androidx.work.WorkManager /** * Created by jun xu on 4/17/21. */ const val TAG = "AnchorTaskApplication" class WorkManagerInitializer : Initializer { override fun create(context: Context): WorkManager { Log.i(TAG, "create: WorkManagerInitializer init") AppInitializer.getInstance(context) .initializeComponent(ExampleLoggerInitializer::class.java) val let: Int = context.let { 123 } return WorkManager.getInstance(context) } override fun dependencies(): List>> { // No dependencies on other libraries. return emptyList() } } // Initializes ExampleLogger. class ExampleLoggerInitializer : Initializer { override fun create(context: Context): ExampleLogger { Log.i(TAG, "create: ExampleLoggerInitializer init") val apply: Context = context.apply { } return ExampleLogger(WorkManager.getInstance(context)) } override fun dependencies(): List>> { // Defines a dependency on WorkManagerInitializer so it can be // initialized after WorkManager is initialized. return listOf(WorkManagerInitializer::class.java) } } class ExampleLogger(val workManager: WorkManager) { } fun T.let2(block: (T) -> R): R { return block(this) } fun T.apply2(block: T.() -> Unit): T { block() return this } ================================================ FILE: app/src/main/java/com/xj/anchortask/asyncInflate/AsyncActivity.kt ================================================ package com.xj.anchortask.asyncInflate import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.xj.anchortask.R class AsyncActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val asyncInflateItem = AsyncInflateItem( LaunchInflateKey.LAUNCH_ACTIVITY, R.layout.activity_async, null, null ) AsyncInflateManager.instance.asyncInflate(this, asyncInflateItem) Thread.sleep(2) val inflatedView = AsyncInflateManager.instance.getInflatedView( this, R.layout.activity_async, null, LaunchInflateKey.LAUNCH_ACTIVITY, layoutInflater ) setContentView(inflatedView) } override fun onDestroy() { super.onDestroy() AsyncInflateManager.instance.remove(LaunchInflateKey.LAUNCH_ACTIVITY) } } ================================================ FILE: app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateItem.kt ================================================ package com.xj.anchortask.asyncInflate import android.view.View import android.view.ViewGroup /** * Created by jun xu on 4/1/21. */ class AsyncInflateItem constructor(var inflateKey: String, var layoutResId: Int, var parent: ViewGroup? = null, var callback: OnInflateFinishedCallback? = null) { var inflatedView: View? = null private var cancelled = false private var inflating = false fun isCancelled(): Boolean { synchronized(this) { return cancelled } } fun setCancelled(cancelled: Boolean) { synchronized(this) { this.cancelled = cancelled } } fun isInflating(): Boolean { synchronized(this) { return inflating } } fun setInflating(inflating: Boolean) { synchronized(this) { this.inflating = inflating } } override fun toString(): String { return "AsyncInflateItem(inflateKey='$inflateKey', layoutResId=$layoutResId, parent=$parent, callback=$callback, inflatedView=$inflatedView, cancelled=$cancelled, inflating=$inflating)" } interface OnInflateFinishedCallback { fun onInflateFinished(item: AsyncInflateItem) } } ================================================ FILE: app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateKey.kt ================================================ package com.xj.anchortask.asyncInflate /** * @author pjt */ object LaunchInflateKey { /** * 首页Fragment * [R.layout.fragment_tabs] */ const val LAUNCH_FRAGMENT_MAIN = "launch_fragment_main" const val LAUNCH_ACTIVITY = "async_activity" } ================================================ FILE: app/src/main/java/com/xj/anchortask/asyncInflate/AsyncInflateManager.kt ================================================ package com.xj.anchortask.asyncInflate import android.content.Context import android.content.MutableContextWrapper import android.text.TextUtils import android.util.AttributeSet import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.UiThread import java.util.concurrent.* /** * Created by jun xu on 4/1/21 * * * 用来提供子线程inflate view的功能,避免某个view层级太深太复杂,主线程inflate会耗时很长, * 实就是对 AsyncLayoutInflater进行了抽取和封装 */ class AsyncInflateManager private constructor() { private val mInflateMap //保存inflateKey以及InflateItem,里面包含所有要进行inflate的任务 : ConcurrentHashMap = ConcurrentHashMap() private val mInflateLatchMap: ConcurrentHashMap = ConcurrentHashMap() companion object { private const val TAG = "AsyncInflateManager" private val cpuCount = Runtime.getRuntime().availableProcessors() val threadPool = ThreadPoolExecutor( cpuCount, cpuCount * 2, 5, TimeUnit.SECONDS, LinkedBlockingDeque() ).apply { allowCoreThreadTimeOut(true) } @JvmStatic val instance by lazy { AsyncInflateManager() } /** * 空方法,为了可以提前加载 AsyncInflateManager,并初始化 mThreadPool */ fun init() { } } /** * 用来获得异步inflate出来的view * * @param context * @param layoutResId 需要拿的layoutId * @param parent container * @param inflateKey 每一个View会对应一个inflateKey,因为可能许多地方用的同一个 layout,但是需要inflate多个,用InflateKey进行区分 * @param inflater 外部传进来的inflater,外面如果有inflater,传进来,用来进行可能的SyncInflate, * @return 最后inflate出来的view */ @UiThread fun getInflatedView( context: Context?, layoutResId: Int, parent: ViewGroup?, inflateKey: String?, inflater: LayoutInflater ): View { if (!TextUtils.isEmpty(inflateKey) && mInflateMap.containsKey(inflateKey)) { val item = mInflateMap[inflateKey] val latch = mInflateLatchMap[inflateKey] if (item != null) { val resultView = item.inflatedView if (resultView != null) { //拿到了view直接返回 removeInflateKey(item) replaceContextForView(resultView, context) Log.i(TAG, "getInflatedView from cache: inflateKey is $inflateKey") return resultView } if (item.isInflating() && latch != null) { //没拿到view,但是在inflate中,等待返回 try { latch.await() } catch (e: InterruptedException) { Log.e(TAG, e.message, e) } val inflatedView = item.inflatedView if (inflatedView != null) { removeInflateKey(item) Log.i(TAG, "getInflatedView from OtherThread: inflateKey is $inflateKey") replaceContextForView(inflatedView, context) return inflatedView } } //如果还没开始inflate,则设置为false,UI线程进行inflate item.setCancelled(true) } } Log.i(TAG, "getInflatedView from UI: inflateKey is $inflateKey") //拿异步inflate的View失败,UI线程inflate return inflater.inflate(layoutResId, parent, false) } /** * 如果 inflater初始化时是传进来的application,inflate出来的 view 的 context 没法用来 startActivity, * 因此用 MutableContextWrapper 进行包装,后续进行替换 */ private fun replaceContextForView( inflatedView: View?, context: Context? ) { if (inflatedView == null || context == null) { return } val cxt = inflatedView.context if (cxt is MutableContextWrapper) { cxt.baseContext = context } } fun asyncInflate( context: Context, vararg items: AsyncInflateItem? ) { items.forEach { item -> if (item == null || item.layoutResId == 0 || mInflateMap.containsKey(item.inflateKey) || item.isCancelled() || item.isInflating()) { return } mInflateMap[item.inflateKey] = item onAsyncInflateReady(item) inflateWithThreadPool(context, item) } } fun cancel() { } fun remove(inflateKey: String) { mInflateMap.remove(inflateKey) mInflateLatchMap.remove(inflateKey) } private fun onAsyncInflateReady(item: AsyncInflateItem) {} private fun onAsyncInflateStart(item: AsyncInflateItem) {} private fun onAsyncInflateEnd(item: AsyncInflateItem, success: Boolean) { item.setInflating(false) val latch = mInflateLatchMap[item.inflateKey] latch?.countDown() if (success && item.callback != null) { removeInflateKey(item) if (item.isCancelled()) { // 已经取消了,不再回调 return } ThreadUtils.runOnUiThread { item.callback?.onInflateFinished(item) } } } private fun removeInflateKey(item: AsyncInflateItem) { } private fun inflateWithThreadPool( context: Context, item: AsyncInflateItem ) { threadPool.execute { if (!item.isInflating() && !item.isCancelled()) { try { onAsyncInflateStart(item) item.setInflating(true) mInflateLatchMap[item.inflateKey] = CountDownLatch(1) val currentTimeMillis = System.currentTimeMillis() item.inflatedView = BasicInflater(context).inflate(item.layoutResId, item.parent, false) onAsyncInflateEnd(item, true) val l = System.currentTimeMillis() - currentTimeMillis Log.i(TAG, "inflateWithThreadPool: inflateKey is ${item.inflateKey}, time is ${l}") } catch (e: RuntimeException) { Log.e( TAG, "Failed to inflate resource in the background! Retrying on the UI thread", e ) onAsyncInflateEnd(item, false) } } } } /** * copy from AsyncLayoutInflater - actual inflater */ private class BasicInflater(context: Context?) : LayoutInflater(context) { override fun cloneInContext(newContext: Context): LayoutInflater { return BasicInflater(newContext) } @Throws(ClassNotFoundException::class) override fun onCreateView( name: String, attrs: AttributeSet ): View { for (prefix in sClassPrefixList) { try { val view = this.createView(name, prefix, attrs) if (view != null) { return view } } catch (ignored: ClassNotFoundException) { } } return super.onCreateView(name, attrs) } companion object { private val sClassPrefixList = arrayOf("android.widget.", "android.webkit.", "android.app.") } } } ================================================ FILE: app/src/main/java/com/xj/anchortask/asyncInflate/ThreadUtils.java ================================================ package com.xj.anchortask.asyncInflate; import android.os.Handler; import android.os.Looper; /** * Created by jun xu on 4/1/21. */ public class ThreadUtils { public static void runOnUiThread(Runnable r) { if (isMainThread()) { r.run(); } else { LazyHolder.sUiThreadHandler.post(r); } } public static void runOnUiThreadAtFront(Runnable r) { if (isMainThread()) { r.run(); } else { LazyHolder.sUiThreadHandler.postAtFrontOfQueue(r); } } public static void runOnUiThread(Runnable r, long delay) { LazyHolder.sUiThreadHandler.postDelayed(r, delay); } public static void postOnUiThread(Runnable r) { LazyHolder.sUiThreadHandler.post(r); } public static void removeCallbacks(Runnable r) { LazyHolder.sUiThreadHandler.removeCallbacks(r); } public static boolean isMainThread() { return Looper.getMainLooper() == Looper.myLooper(); } private static class LazyHolder { private static Handler sUiThreadHandler = new Handler(Looper.getMainLooper()); } } ================================================ FILE: app/src/main/java/com/xj/anchortask/asyncInflate/page/AsyncFragment.kt ================================================ package com.xj.anchortask.asyncInflate.page import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import com.xj.anchortask.R import com.xj.anchortask.asyncInflate.AsyncInflateManager import com.xj.anchortask.asyncInflate.LaunchInflateKey.LAUNCH_FRAGMENT_MAIN // TODO: Rename parameter arguments, choose names that match // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private const val ARG_PARAM1 = "param1" private const val ARG_PARAM2 = "param2" private const val TAG = "AsyncFragment" /** * A simple [Fragment] subclass. * Use the [AsyncFragment.newInstance] factory method to * create an instance of this fragment. */ class AsyncFragment : Fragment() { // TODO: Rename and change types of parameters private var param1: String? = null private var param2: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { param1 = it.getString(ARG_PARAM1) param2 = it.getString(ARG_PARAM2) } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment val startTime = System.currentTimeMillis() val homeFragmentOpen = AsyncUtils.isHomeFragmentOpen() val inflatedView: View val isOpen = AsyncUtils.isHomeFragmentOpen() if (isOpen){ AsyncUtils.asyncInflate(context) } // inflatedView = AsyncInflateManager.instance.getInflatedView( // context, // R.layout.fragment_asny, // container, // LAUNCH_FRAGMENT_MAIN, // inflater // ) // // Log.i( // TAG, // "onCreateView: homeFragmentOpen is $homeFragmentOpen, timeInstance is ${System.currentTimeMillis() - startTime}, ${inflatedView.context}" // ) // return inflatedView return inflater.inflate(R.layout.fragment_asny, container, false) } companion object { /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment AsnyFragment. */ // TODO: Rename and change types and number of parameters @JvmStatic fun newInstance(param1: String, param2: String) = AsyncFragment().apply { arguments = Bundle().apply { putString(ARG_PARAM1, param1) putString(ARG_PARAM2, param2) } } } } ================================================ FILE: app/src/main/java/com/xj/anchortask/asyncInflate/page/AsyncUtils.kt ================================================ package com.xj.anchortask.asyncInflate.page import android.content.Context import com.xj.anchortask.R import com.xj.anchortask.asyncInflate.AsyncInflateItem import com.xj.anchortask.asyncInflate.AsyncInflateManager import com.xj.anchortask.asyncInflate.LaunchInflateKey.LAUNCH_FRAGMENT_MAIN import com.xj.anchortask.getSP /** * Created by jun xu on 4/1/21. */ object AsyncUtils { fun asyncInflate(context: Context?) { context ?: return val asyncInflateItem = AsyncInflateItem( LAUNCH_FRAGMENT_MAIN, R.layout.fragment_asny, null, null ) AsyncInflateManager.instance.asyncInflate(context, asyncInflateItem) } fun isHomeFragmentOpen() = getSP("async_config").getBoolean("home_fragment_switch", true) } ================================================ FILE: app/src/main/java/com/xj/anchortask/flowlayout/FlowLayoutDemo.kt ================================================ package com.xj.anchortask.flowlayout import android.graphics.Color import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.xj.anchortask.R import java.util.* class FlowLayoutDemo : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_flow_layout_demo) val tagGroup = findViewById(R.id.tag_group) val texts: List = Arrays.asList( "zhang", "phil", "csdn", "android", "zhang", "phil", "csdn", "android", "zhang", "phil", "csdn", "android" ) val colors: List = Arrays.asList( Color.RED, Color.DKGRAY, Color.BLUE, Color.RED, Color.DKGRAY, Color.BLUE, Color.RED, Color.DKGRAY, Color.BLUE, Color.RED, Color.DKGRAY, Color.BLUE ) tagGroup.setTagView(texts, colors) } } ================================================ FILE: app/src/main/java/com/xj/anchortask/flowlayout/TagGroup.java ================================================ package com.xj.anchortask.flowlayout; /** * Created by jun xu on 4/19/21. */ import android.content.Context; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; import android.util.AttributeSet; import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.flexbox.FlexDirection; import com.google.android.flexbox.FlexWrap; import com.google.android.flexbox.FlexboxLayout; import com.google.android.flexbox.JustifyContent; import java.util.ArrayList; import java.util.List; //https://blog.csdn.net/qq_36699930/article/details/112249170 public class TagGroup extends FlexboxLayout { private List mTexts; private List mColors; private Context mContext; private int TAG_VIEW_COUNT = 0; public TagGroup(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; init(); } private void init() { mTexts = new ArrayList<>(); mColors = new ArrayList<>(); this.setFlexDirection(FlexDirection.ROW); this.setJustifyContent(JustifyContent.FLEX_START); this.setFlexWrap(FlexWrap.WRAP); } public void setTagView(@NonNull List texts, @Nullable List colors) { if (texts == null || texts.size() == 0) { throw new RuntimeException("tag view文本字段不能为空"); } this.mTexts = texts; TAG_VIEW_COUNT = texts.size(); if (colors == null || colors.size() == 0) { for (int i = 0; i < TAG_VIEW_COUNT; i++) { mColors.clear(); mColors.add(Color.WHITE); } } else { this.mColors = colors; } this.removeAllViews(); for (int i = 0; i < TAG_VIEW_COUNT; i++) { TextView textView = makeTextView(mTexts.get(i), mColors.get(i)); LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); //设置每一个子View在整体布局中与其他子View的上下左右的margin layoutParams.setMargins(0, 1, 5, 1); this.addView(textView, layoutParams); } this.invalidate(); } //绘制圆角描边的TextView private TextView makeTextView(String s, Integer c) { TextView textView = new TextView(mContext); textView.setText(s); textView.setPadding(10, 5, 10, 5); int strokeWidth = 5; // 5px int roundRadius = 15; // 15px int strokeColor = Color.GRAY; int fillColor = c; GradientDrawable gd = new GradientDrawable(); gd.setColor(fillColor); gd.setCornerRadius(roundRadius); gd.setStroke(strokeWidth, strokeColor); textView.setBackground(gd); return textView; } } ================================================ FILE: app/src/main/java/com/xj/anchortask/viewStub/MyViewStub.kt ================================================ package com.xj.anchortask.viewStub import ViewStubTask import android.util.Log import android.view.View import com.xj.anchortask.R /** * Created by jun xu on 4/1/21. */ class ViewStubTaskTitle(decorView: View) : ViewStubTask(decorView) { override fun getViewStubId(): Int { return R.id.vs_title } override fun onInflateFinish() { super.onInflateFinish() Log.i(TAG, "onInflateFinish: ViewStubTaskTitle") } } class ViewStubTaskContent(decorView: View) : ViewStubTask(decorView) { override fun getViewStubId(): Int { return R.id.vs_content } override fun onInflateFinish() { super.onInflateFinish() Log.i(TAG, "onInflateFinish: ViewStubTaskContent") } } class ViewStubTaskBottom(decorView: View) : ViewStubTask(decorView) { override fun getViewStubId(): Int { return R.id.vs_bottom } override fun onInflateFinish() { super.onInflateFinish() Log.i(TAG, "onInflateFinish: ViewStubTaskBottom") } } ================================================ FILE: app/src/main/java/com/xj/anchortask/viewStub/ViewStubDemoActivity.kt ================================================ package com.xj.anchortask.viewStub import ViewStubTaskManager import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.xj.anchortask.R class ViewStubDemoActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_view_stub_demo) val decorView = this.window.decorView ViewStubTaskManager.instance(decorView) .addTask(ViewStubTaskContent(decorView)) .addTask(ViewStubTaskTitle(decorView)) .addTask(ViewStubTaskBottom(decorView)) .start() } } ================================================ FILE: app/src/main/java/com/xj/anchortask/viewStub/ViewStubTask.kt ================================================ import android.view.View import android.view.ViewStub import androidx.annotation.CallSuper import androidx.annotation.IdRes abstract class ViewStubTask(val decorView: View) { companion object { const val TAG = "ViewStubTask" } // ViewStub 转化的 View var root: View? = null private set // 数据是否准备好 var dataIsReady: Boolean = false // ViewStub 是否转化完成 var onInflateListener: ViewStub.OnInflateListener? = null // 当前的 viewStub var viewStub: ViewStub? = null private set // 当前 ViewStub 是否转化完成 val isInflated: Boolean get() = this.root != null abstract fun getViewStubId(): Int private fun setUpViewStub(viewStub: ViewStub) { this.viewStub = viewStub val proxyListener: ViewStub.OnInflateListener = ViewStub.OnInflateListener { stub, inflated -> this@ViewStubTask.root = inflated this@ViewStubTask.viewStub = null onInflateListener?.onInflate(stub, inflated) onInflateFinish() } viewStub.setOnInflateListener(proxyListener) } /** * 调用这个方法,会开始转化 ViewStub */ fun inflate(): View? { if (this.root != null) { return this.root!! } val viewStub: ViewStub? = decorView.findViewById(getViewStubId()) viewStub?.let { setUpViewStub(viewStub) return viewStub.inflate() } ?: kotlin.run { return null } } /** * ViewStub 转化完成回调 */ open fun onInflateFinish() { } @CallSuper open fun onDetach() { } @CallSuper open fun onDataReady() { dataIsReady = true } // 使用 ViewStub ,建议使用 findViewById,如果直接调用 Activity 或者 根布局的 findViewById,当我们的 view 还没有 add 进入的时候,这时候会为 null fun findViewById(@IdRes id: Int): T? { if (!isInflated) { return null } return if (id == View.NO_ID) { null } else { root?.findViewById(id) } } } ================================================ FILE: app/src/main/java/com/xj/anchortask/viewStub/ViewStubTaskManager.kt ================================================ import android.view.View import androidx.core.view.ViewCompat import java.util.concurrent.CopyOnWriteArrayList /** * Created by jun xu on 3/31/21 */ class ViewStubTaskManager private constructor(val decorView: View) : Runnable { private var iViewStubTask: IViewStubTask? = null companion object { const val TAG = "ViewStubTaskManager" @JvmStatic fun instance(decorView: View): ViewStubTaskManager { return ViewStubTaskManager(decorView) } } private val queue: MutableList = CopyOnWriteArrayList() private val list: MutableList = CopyOnWriteArrayList() fun setCallBack(iViewStubTask: IViewStubTask?): ViewStubTaskManager { this.iViewStubTask = iViewStubTask return this } fun addTask(viewStubTasks: List): ViewStubTaskManager { queue.addAll(viewStubTasks) list.addAll(viewStubTasks) return this } fun addTask(viewStubTask: ViewStubTask): ViewStubTaskManager { queue.add(viewStubTask) list.add(viewStubTask) return this } fun start() { if (isEmpty()) { return } iViewStubTask?.beforeTaskExecute() // 指定 decorView 绘制下一帧的时候会回调里面的 runnable ViewCompat.postOnAnimation(decorView, this) } fun stop() { queue.clear() list.clear() decorView.removeCallbacks(null) } private fun isEmpty() = queue.isEmpty() || queue.size == 0 override fun run() { if (!isEmpty()) { // 当队列不为空的时候,先加载当前 viewStubTask val viewStubTask = queue.removeAt(0) viewStubTask.inflate() iViewStubTask?.onTaskExecute(viewStubTask) // 加载完成之后,再 postOnAnimation 加载下一个 ViewCompat.postOnAnimation(decorView, this) } else { iViewStubTask?.afterTaskExecute() } } fun notifyOnDetach() { list.forEach { it.onDetach() } list.clear() } fun notifyOnDataReady() { list.forEach { it.onDataReady() } } } interface IViewStubTask { fun beforeTaskExecute() fun onTaskExecute(viewStubTask: ViewStubTask) fun afterTaskExecute() } ================================================ FILE: app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ ================================================ FILE: app/src/main/res/layout/activity_anchortask_test.xml ================================================