Design and Implementation of a Lightweight Startup Information Collection and Visualization Framework
This article presents a lightweight framework for constructing, collecting, and visualizing Android startup task information, replacing heavy systrace data with concise logs, Kotlin data structures, AspectJ instrumentation, and Python scripts that generate readable timelines and discrete charts to aid performance analysis.
Background
Within the existing startup framework, tasks are clearly defined, but the startup sequence is a critical piece of information. The current method of obtaining this information relies on systrace, which has several drawbacks: in release builds it lacks some details (e.g., IO thread information), and the output is heavy and hard to read, making it difficult for developers to investigate and optimize startup tasks.
To address these issues, this article proposes a lightweight solution for describing, collecting, and reconstructing startup information that works flexibly in both release and debug modes, improves readability, and reduces the cost of troubleshooting.
1. Scheme Design
The lightweight startup information construction scheme consists of three parts:
Startup information construction: extracts key information into a new data structure.
Startup information collection: gathers and outputs task information to the reconstruction module.
Startup information reconstruction: builds the information and outputs visual graphs.
2. Specific Module Implementation
2.1 Startup Information Construction
data class InitDataStruct(
var startTime: Long = 0,
var duration: Long = 0,
var currentProcess: String = "",
var taskName: String = ""
)The key dimensions of startup information are:
Normalized start time
Duration
Thread (process) name
Task name
Non‑essential tasks that should be filtered out include non‑startup tasks and startup task stacks.
Example JSON format:
{"task_name":"class com.xxx.xxxTask","start_time":5,"duration":9,"current_process":"AA xxxThread#4"}2.2 Startup Information Collection
Since the company platform is not integrated, the result is output as logs. An AspectJ approach is used to insert hooks around each task: @PointCut("execution(* com.xxx.xxx.xxxTask.run(*))") and then inject code with @Before and @After annotations.
2.3 Startup Information Collection and Rendering
Collected logs are JSON strings like the example above. A Python utility converts them into a readable data structure.
# In the client, JSON is saved
def toInitInfo(json):
return InitInfo(json["start_time"], json["duration"], json["current_process"], str(json["task_name"]).split('.')[-1])
class InitInfo:
# startTime and duration are normalized
def __init__(self, startTime, duration, currentProcessName, taskName):
self.startTime = startTime
self.taskName = taskName
self.duration = duration
self.currentProcessName = currentProcessName
def printitself(self):
print("task_name : " + self.taskName)
print("\tstartTime : " + str(self.startTime))
print("\tduration : " + str(self.duration))
print("\tcurrentProcessName : " + self.currentProcessName)
def getNameCombineDuration(self):
return self.taskName + " " + str(self.duration)
def getConstructLen(self):
return len(self.getNameCombineDuration()) + 2
def generateFormatStr(self, perTime, perBlank):
totalLen = max(3, int(1.0 * perBlank * max(1, self.duration) / perTime))
cntLen = max(0, totalLen - self.getConstructLen())
strr = "|" + (cntLen // 2 + cntLen % 2) * "-" + self.getNameCombineDuration()[0:min(totalLen - 2, len(self.getNameCombineDuration()))] + cntLen // 2 * "-" + "|"
return strr
def generateBlank(self, timeNow, perTime, perBlank):
strr = max(0, int((self.startTime - timeNow) / perTime) * perBlank) * " "
return strrAll tasks are inserted into a list and sorted by end time:
def sortByEnd(initInfo1, initInfo2):
return (initInfo1.startTime + initInfo1.duration) <= (initInfo2.startTime + initInfo2.duration)
def dealWithList():
for item in line_jsons:
if taskMap.has_key(item.currentProcessName):
taskMap[item.currentProcessName].append(item)
else:
taskMap[item.currentProcessName] = []
taskMap[item.currentProcessName].append(item)Two drawing methods are provided:
Method 1 – timeline view where the X‑axis is fixed time intervals and the Y‑axis is the process name.
def drawMp():
duraLen = 0
maxLen = 0
# 10ms interval
currentPerTime = 10
endFile = open("timeline.txt", "w")
for key in taskMap.keys():
maxLen = max(maxLen, len(key))
for item in line_jsons:
duraLen = max(duraLen, item.getConstructLen())
# draw axis
xplot = maxLen * " " + " :"
for index in range(0, (line_jsons[-1].startTime + line_jsons[-1].duration) // currentPerTime):
cntLen = duraLen - 2 - len(str(index * currentPerTime))
xplot += "|" + (cntLen // 2 + cntLen % 2) * "-" + str(index * currentPerTime) + cntLen // 2 * "-" + "|"
endFile.write(xplot + "
")
for key in taskMap.keys():
strr = key + (maxLen - len(key)) * " " + " :"
timeNow = 0
for item in taskMap[key]:
item.printitself()
strr += item.generateBlank(timeNow, perTime=currentPerTime, perBlank=duraLen)
strr += item.generateFormatStr(10, duraLen)
timeNow = item.startTime + item.duration
strr += "
"
endFile.write(strr)
endFile.close()Method 2 – discrete view where the X‑axis is discretized task time and the Y‑axis is the process name.
def drawMp2():
# discretize time points
dura = []
filee = open("timeline2.txt", "w")
for item in line_jsons:
duraLen = max(duraLen, len(item.getNameCombineDuration()) + 2)
dura.append(item.startTime)
dura.append(item.startTime + item.duration)
duraCordi = sorted(set(dura))
maxLen = 0
for key in taskMap.keys():
maxLen = max(maxLen, len(key))
for key in taskMap.keys():
currentIndex = 0
strr = key + (maxLen - len(key)) * " " + " :"
for item in taskMap[key]:
stIndex = bisect.bisect_left(duraCordi, item.startTime)
edIndex = bisect.bisect_left(duraCordi, item.startTime + max(item.duration, 1))
strr += addBlank(currentIndex, stIndex)
strr += formatString(stIndex, edIndex, item.getNameCombineDuration(), duraLen=duraLen)
currentIndex = edIndex
strr += "
"
filee.write(strr)
filee.close()3. Effect Comparison
First visualization – startup duration per unit time.
Second visualization – discretized startup time.
By examining the second chart, one can see that the main thread has many startup tasks, indicating a possible long‑tail effect. Compared with systrace, this approach is much lighter.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ByteDance Dali Intelligent Technology Team
Technical practice sharing from the ByteDance Dali Intelligent Technology Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
