@@ -23,13 +23,13 @@ import android.graphics.Color
2323import android.graphics.Rect
2424import android.graphics.drawable.GradientDrawable
2525import android.os.Bundle
26+ import android.os.Process
2627import android.text.SpannableStringBuilder
2728import android.text.Spanned
2829import android.text.TextUtils
2930import android.text.method.LinkMovementMethod
3031import android.text.style.ClickableSpan
3132import android.view.View
32- import android.view.ViewGroup
3333import android.view.ViewTreeObserver.OnGlobalLayoutListener
3434import androidx.activity.OnBackPressedCallback
3535import androidx.activity.result.ActivityResult
@@ -39,17 +39,25 @@ import androidx.activity.viewModels
3939import androidx.annotation.GravityInt
4040import androidx.annotation.StringRes
4141import androidx.appcompat.app.ActionBarDrawerToggle
42+ import androidx.collection.MutableIntIntMap
4243import androidx.core.view.GravityCompat
43- import androidx.core.view.updateLayoutParams
4444import androidx.core.view.updatePaddingRelative
45+ import com.blankj.utilcode.constant.MemoryConstants
46+ import com.blankj.utilcode.util.ConvertUtils.byte2MemorySize
4547import com.blankj.utilcode.util.FileUtils
4648import com.blankj.utilcode.util.KeyboardUtils
4749import com.blankj.utilcode.util.ThreadUtils
50+ import com.github.mikephil.charting.components.AxisBase
51+ import com.github.mikephil.charting.data.Entry
52+ import com.github.mikephil.charting.data.LineData
53+ import com.github.mikephil.charting.data.LineDataSet
54+ import com.github.mikephil.charting.formatter.IAxisValueFormatter
4855import com.google.android.material.bottomsheet.BottomSheetBehavior
4956import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
5057import com.google.android.material.snackbar.Snackbar
5158import com.google.android.material.tabs.TabLayout
5259import com.google.android.material.tabs.TabLayout.Tab
60+ import com.itsaky.androidide.R
5361import com.itsaky.androidide.R.string
5462import com.itsaky.androidide.actions.ActionItem.Location.EDITOR_FILE_TABS
5563import com.itsaky.androidide.adapters.DiagnosticsAdapter
@@ -82,7 +90,9 @@ import com.itsaky.androidide.utils.ApkInstallationSessionCallback
8290import com.itsaky.androidide.utils.DialogUtils.newMaterialDialogBuilder
8391import com.itsaky.androidide.utils.InstallationResultHandler.onResult
8492import com.itsaky.androidide.utils.IntentUtils
93+ import com.itsaky.androidide.utils.MemoryUsageWatcher
8594import com.itsaky.androidide.utils.flashError
95+ import com.itsaky.androidide.utils.resolveAttr
8696import com.itsaky.androidide.viewmodel.EditorViewModel
8797import com.itsaky.androidide.xml.resources.ResourceTableRegistry
8898import com.itsaky.androidide.xml.versions.ApiVersionsRegistry
@@ -94,6 +104,7 @@ import org.greenrobot.eventbus.ThreadMode.MAIN
94104import org.slf4j.Logger
95105import org.slf4j.LoggerFactory
96106import java.io.File
107+ import kotlin.math.roundToLong
97108
98109/* *
99110 * Base class for EditorActivity which handles most of the view related things.
@@ -108,6 +119,8 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele
108119 protected var diagnosticInfoBinding: LayoutDiagnosticInfoBinding ? = null
109120 protected var filesTreeFragment: FileTreeFragment ? = null
110121 protected var editorBottomSheet: BottomSheetBehavior <out View ?>? = null
122+ protected val memoryUsageWatcher = MemoryUsageWatcher ()
123+ protected val pidToDatasetIdxMap = MutableIntIntMap (initialCapacity = 3 )
111124
112125 var isDestroying = false
113126 protected set
@@ -141,9 +154,33 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele
141154 }
142155 }
143156
157+ private val memoryUsageListener = MemoryUsageWatcher .MemoryUsageListener { memoryUsage ->
158+ memoryUsage.forEachValue { proc ->
159+ _binding ?.memUsageView?.chart?.apply {
160+ val dataset = (data.getDataSetByIndex(pidToDatasetIdxMap[proc.pid]) as LineDataSet ? ) ? : run {
161+ log.error(" No dataset found for process: {}: {}" , proc.pid, proc.pname)
162+ return @forEachValue
163+ }
164+
165+ dataset.entries.mapIndexed { index, entry ->
166+ entry.y = byte2MemorySize(proc.usageHistory[index], MemoryConstants .MB ).toFloat()
167+ }
168+
169+ dataset.label = " %s - %.2fMB" .format(proc.pname, dataset.entries.last().y)
170+ dataset.notifyDataSetChanged()
171+ data.notifyDataChanged()
172+ notifyDataSetChanged()
173+ invalidate()
174+ }
175+ }
176+ }
177+
144178 private var optionsMenuInvalidator: Runnable ? = null
145179
146180 companion object {
181+ @JvmStatic protected val PROC_IDE = " IDE"
182+ @JvmStatic protected val PROC_GRADLE_TOOLING = " Gradle Tooling"
183+ @JvmStatic protected val PROC_GRADLE_DAEMON = " Gradle Daemon"
147184
148185 @JvmStatic
149186 protected val log: Logger = LoggerFactory .getLogger(BaseEditorActivity ::class .java)
@@ -180,6 +217,8 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele
180217 installationCallback = null
181218
182219 if (isDestroying) {
220+ memoryUsageWatcher.stopWatching(true )
221+ memoryUsageWatcher.listener = null
183222 editorActivityScope.cancelIfActive(" Activity is being destroyed" )
184223 }
185224 }
@@ -258,10 +297,81 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele
258297
259298 uiDesignerResultLauncher = registerForActivityResult(StartActivityForResult (),
260299 this ::handleUiDesignerResult)
300+
301+ setupMemUsageChart()
302+ watchMemory()
303+ }
304+
305+ private fun setupMemUsageChart () {
306+ binding.memUsageView.chart.apply {
307+ val colorAccent = resolveAttr(R .attr.colorAccent)
308+
309+ isDragEnabled = false
310+ description.isEnabled = false
311+ xAxis.axisLineColor = colorAccent
312+ axisRight.axisLineColor = colorAccent
313+
314+ setPinchZoom(false )
315+ setBackgroundColor(resolveAttr(R .attr.colorSurfaceContainer))
316+ setDrawGridBackground(true )
317+ setScaleEnabled(true )
318+
319+ axisLeft.isEnabled = false
320+ axisRight.valueFormatter = object :
321+ IAxisValueFormatter {
322+ override fun getFormattedValue (value : Float , axis : AxisBase ? ): String {
323+ return " %dMB" .format(value.roundToLong())
324+ }
325+ }
326+ }
327+ }
328+
329+ private fun watchMemory () {
330+ memoryUsageWatcher.listener = memoryUsageListener
331+ memoryUsageWatcher.watchProcess(Process .myPid(), PROC_IDE )
332+ resetMemUsageChart()
333+ }
334+
335+ protected fun resetMemUsageChart () {
336+ val processes = memoryUsageWatcher.getMemoryUsages()
337+ val datasets = Array (processes.size) { index ->
338+ LineDataSet (List (MemoryUsageWatcher .MAX_USAGE_ENTRIES ) { Entry (it.toFloat(), 0f ) }, processes[index].pname)
339+ }
340+
341+ for ((index, proc) in processes.withIndex()) {
342+ val dataset = datasets[index]
343+ dataset.color = getMemUsageLineColorFor(proc)
344+ dataset.setDrawIcons(false )
345+ dataset.setDrawCircles(false )
346+ dataset.setDrawCircleHole(false )
347+ dataset.setDrawValues(false )
348+ dataset.formLineWidth = 1f
349+ dataset.formSize = 15f
350+ dataset.isHighlightEnabled = false
351+ pidToDatasetIdxMap[proc.pid] = index
352+ }
353+
354+ binding.memUsageView.chart.apply {
355+ data = LineData (* datasets)
356+ notifyDataSetChanged()
357+ invalidate()
358+ }
359+ }
360+
361+ private fun getMemUsageLineColorFor (proc : MemoryUsageWatcher .ProcessMemoryInfo ): Int {
362+ return when (proc.pname) {
363+ PROC_IDE -> Color .BLUE
364+ PROC_GRADLE_TOOLING -> Color .RED
365+ PROC_GRADLE_DAEMON -> Color .GREEN
366+ else -> throw IllegalArgumentException (" Unknown process: $proc " )
367+ }
261368 }
262369
263370 override fun onPause () {
264371 super .onPause()
372+ memoryUsageWatcher.listener = null
373+ memoryUsageWatcher.stopWatching(false )
374+
265375 this .isDestroying = isFinishing
266376 getFileTreeFragment()?.saveTreeState()
267377 }
@@ -270,6 +380,9 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele
270380 super .onResume()
271381 invalidateOptionsMenu()
272382
383+ memoryUsageWatcher.listener = memoryUsageListener
384+ memoryUsageWatcher.startWatching()
385+
273386 try {
274387 getFileTreeFragment()?.listProjectFiles()
275388 } catch (th: Throwable ) {
@@ -280,7 +393,6 @@ abstract class BaseEditorActivity : EdgeToEdgeIDEActivity(), TabLayout.OnTabSele
280393
281394 override fun onStop () {
282395 super .onStop()
283-
284396 checkIsDestroying()
285397 }
286398
0 commit comments