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 技术,算法,职场相关的。**

================================================
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
================================================
================================================
FILE: app/src/main/res/layout/activity_async.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_flow_layout_demo.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
================================================
FILE: app/src/main/res/layout/activity_view_stub_demo.xml
================================================
================================================
FILE: app/src/main/res/layout/fragment_asny.xml
================================================
================================================
FILE: app/src/main/res/layout/layout_bottom.xml
================================================
================================================
FILE: app/src/main/res/layout/layout_content.xml
================================================
================================================
FILE: app/src/main/res/layout/layout_title.xml
================================================
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
================================================
FILE: app/src/main/res/values/colors.xml
================================================
#6200EE
#3700B3
#03DAC5
================================================
FILE: app/src/main/res/values/strings.xml
================================================
AnchorTask
Hello blank fragment
================================================
FILE: app/src/main/res/values/styles.xml
================================================
================================================
FILE: app/src/test/java/com/xj/anchortask/ExampleUnitTest.kt
================================================
package com.xj.anchortask
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
================================================
FILE: app/test.gradle
================================================
task myTask1 {
println "configure task1"
}
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from: "config.gradle"
buildscript {
ext.kotlin_version = "1.3.72"
repositories {
google()
jcenter()
maven {
url "https://dl.bintray.com/xujun94/maven/"
}
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20"
// classpath 'com.novoda:bintray-release:0.9.2'
classpath 'com.github.panpf.bintray-publish:bintray-publish:1.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
// maven {
// url 'file://Users/junxu/Desktop/gitProject/my-github/AnchorTask/repository'
// }
maven {
url uri('../repository')
}
maven {
url "https://dl.bintray.com/xujun94/maven/"
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: config.gradle
================================================
ext {
// Sdk and tools
anchorTaskPublicVersion = '1.1.0'
anchorTaskVersion = anchorTaskPublicVersion
dep = [
androidPlugin: 'com.android.tools.build:gradle:2.1.3',
]
isCi = "true".equals(System.getenv('CI'))
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Mon Feb 01 16:20:03 CST 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: localconfig.gradle
================================================
task test22{
parseLocalProperties()
}
Properties loadProperties(String fileName) {
File file = rootProject.file(fileName)
println("parseLocalProperties start")
if (file.exists()) {
InputStream inputStream = rootProject.file(fileName).newDataInputStream();
Properties properties = new Properties()
properties.load(inputStream)
return properties
}
return null
}
def parseLocalProperties() {
File file = rootProject.file('local.properties')
println("parseLocalProperties start")
if (file.exists()) {
InputStream inputStream = rootProject.file('local.properties').newDataInputStream();
Properties properties = new Properties()
properties.load(inputStream)
def containsKey = properties.containsKey("packagename")
println("parseLocalProperties containsKey is ${containsKey}")
if (containsKey) {
println 'packageName:' + properties.getProperty("packagename")
ext.packagename = properties.getProperty("packagename")
println 'packageName:' + project["packagename"]
}
}
}
// 设置属性
def static getValue(Properties properties, String name, Object defValue) {
if (properties == null) {
return defValue
}
if (properties.containsKey(name)) {
String value = properties.getProperty(name)
return value
}
return defValue
}
// 方法复用
ext {
parseLocalProperties = this.&parseLocalProperties
loadProperties = this.&loadProperties
getValue = this.&getValue
}
================================================
FILE: publish-mavencentral.gradle
================================================
apply plugin: 'maven-publish'
apply plugin: 'signing'
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.source
}
ext["signing.keyId"] = ''
ext["signing.password"] = ''
ext["signing.secretKeyRingFile"] = ''
ext["ossrhUsername"] = ''
ext["ossrhPassword"] = ''
File secretPropsFile = project.rootProject.file('local.properties')
if (secretPropsFile.exists()) {
println "Found secret props file, loading props"
Properties p = new Properties()
p.load(new FileInputStream(secretPropsFile))
p.each { name, value ->
ext[name] = value
println("name is " + name + ";value is " + value)
}
} else {
println "No props file, loading env vars"
}
publishing {
publications {
release(MavenPublication) {
// The coordinates of the library, being set from variables that
// we'll set up in a moment
groupId PUBLISH_GROUP_ID
artifactId PUBLISH_ARTIFACT_ID
version PUBLISH_VERSION
// Two artifacts, the `aar` and the sources
artifact("$buildDir/outputs/aar/${project.getName()}-release.aar")
artifact androidSourcesJar
// Self-explanatory metadata for the most part
pom {
name = PUBLISH_ARTIFACT_ID
description = 'AnchorTask'
// If your project has a dedicated site, use its URL here
url = 'https://github.com/gdutxiaoxu/'
licenses {
license {
//协议类型,一般默认Apache License2.0的话不用改:
name = 'The Apache License, Version 2.0'
url = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
name = 'xujun'
email = 'gdutxiaoxu@163.com'
}
}
// Version control info, if you're using GitHub, follow the format as seen here
scm {
//修改成你的Git地址:
connection = 'scm:git:github.com/gdutxiaoxu/AnchorTask.git'
developerConnection = 'scm:git:ssh://github.com/gdutxiaoxu/AnchorTask.git'
//分支地址:
url = 'https://github.com/gdutxiaoxu/AnchorTask/tree/master'
}
// A slightly hacky fix so that your POM will include any transitive dependencies
// that your library builds upon
withXml {
def dependenciesNode = asNode().appendNode('dependencies')
project.configurations.implementation.allDependencies.each {
println(it)
if (it.name == "unspecified") {
// 忽略无法识别的
return
}
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
}
}
}
}
}
repositories {
// The repository to publish to, Sonatype/MavenCentral
maven {
// This is an arbitrary name, you may also use "mavencentral" or
// any other name that's descriptive for you
def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
// You only need this if you want to publish snapshots, otherwise just set the URL
// to the release repo directly
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
// The username and password we've fetched earlier
credentials {
username ossrhUsername
password ossrhPassword
}
}
}
}
signing {
sign publishing.publications
}
================================================
FILE: settings.gradle
================================================
include ':anchortasklibrary'
include ':app'
rootProject.name = "AnchorTask"