我正在为我的应用程序构建一个简单的 FileExplorer,并使用协程获取给定路径中的文件,并在显示它们时,内存使用量出现峰值。我在帖子底部显示了探查器工具选项卡。
我最好的猜测是,适配器正在为列表中的每个项目创建一个视图持有者,并且使用应用程序和设备本身的所有内存。
编辑:通过使用RelativeLayout而不是ConstraintLayout,它将内存使用量减少了3倍,并且显示列表需要几秒钟的时间。
内容快速摘要:
0 - 获取路径中内容的函数
1 - Android Studio 的运行控制台上出现 OutOfMemoryException 消息
2 - 垃圾收集器日志
3 - OOM 错误指向的代码片段
4 - 上面的代码片段被调用的地方
5 - ViewHolder代码
6 - 分析器屏幕截图显示最大峰值(超过 1GB)的概述
7 - 声明 RecyclerView 的 DialogFragment 布局文件
8 - 行
9 - 分析器工具中的选项卡显示 ConstraintLayout 调用和 onMeasure 及相关函数
10 - RecyclerView 和 ConstraintLayout 版本
实际获取文件的函数
private fun getFilesOnPath(path : String, showHiddenFiles : Boolean = false, onlyFolders : Boolean = false) : List<File> {
val file = File(path)
var listOfFiles = listOf<File>()
try {
listOfFiles = file.listFiles()
.filter { showHiddenFiles || !it.name.startsWith(".") }
.filter { !onlyFolders || it.isDirectory }.toList()
} catch (exception : IllegalStateException) {
Timber.tag(LOG_TAG).e("${exception.message} \n ${exception.cause}")
} finally {
return listOfFiles
}
}
当我实现 ProgressBar 并在适当的时间显示它们时,会出现“beforeMain”和“afterMain”
我注意到,在文件较少的文件夹上,UI 不会滞后,直到加载文件列表,但是当单击这个特别大的 WhatsApp 文件夹时,应用程序将耗尽内存,并在控制台上显示此信息(PS:此错误与项目列表无关,它被加载和过滤就好了):
at java.lang.Throwable.nativeFillInStackTrace(Native method)
at java.lang.Throwable.fillInStackTrace(Throwable.java:775)
at java.lang.Throwable.<init>(Throwable.java:258)
at java.lang.Error.<init>(Error.java:70)
at java.lang.VirtualMachineError.<init>(VirtualMachineError.java:53)
at java.lang.OutOfMemoryError.<init>(OutOfMemoryError.java:58)
at java.lang.reflect.Constructor.newInstance0(Native method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
at android.view.LayoutInflater.createView(LayoutInflater.java:686)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:829)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:769)
at android.view.LayoutInflater.inflate(LayoutInflater.java:531)
at android.view.LayoutInflater.inflate(LayoutInflater.java:461)
at goldengentleman.goldennotebook.adapters.FileExplorerAdapter$FilesViewHolder$Companion.from(FileExplorerAdapter.kt:153)
at goldengentleman.goldennotebook.adapters.FileExplorerAdapter.onCreateViewHolder(FileExplorerAdapter.kt:52)
at androidx.recyclerview.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:7201)
at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6332)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6216)
at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6212)
at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2314)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1631)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1591)
at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:668)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4215)
at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3615)
at android.view.View.measure(View.java:24967)
at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(ConstraintLayout.java:736)
at androidx.constraintlayout.solver.widgets.analyzer.BasicMeasure.measure(BasicMeasure.java:399)
at androidx.constraintlayout.solver.widgets.analyzer.BasicMeasure.measureChildren(BasicMeasure.java:105)
at androidx.constraintlayout.solver.widgets.analyzer.BasicMeasure.solverMeasure(BasicMeasure.java:232)
at androidx.constraintlayout.solver.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:113)
at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(ConstraintLayout.java:1480)
at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1555)
at android.view.View.measure(View.java:24967)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7134)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at android.view.View.measure(View.java:24967)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7134)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at android.view.View.measure(View.java:24967)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7134)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
at com.android.internal.policy.DecorView.onMeasure(DecorView.java:992)
at android.view.View.measure(View.java:24967)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3270)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:2001)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2300)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1861)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8478)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:949)
at android.view.Choreographer.doCallbacks(Choreographer.java:761)
at android.view.Choreographer.doFrame(Choreographer.java:696)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7045)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:964)
控制台上有明显的垃圾收集器日志:
I/nnotebook.debu: Clamp target GC heap from 216MB to 192MB
Alloc concurrent copying GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 192MB/192MB, paused 145us total 826.488ms
问题可能是一个可笑的大列表,实际上有 6000 个项目,但该错误使得问题看起来像是在适配器上,这就是控制台指向的位置:
companion object {
fun from(parent : ViewGroup) : FilesViewHolder = FilesViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_file_explorer_file, parent, false))
}
以及调用上述函数的地方:
override fun onCreateViewHolder(parent : ViewGroup, viewType : Int) : RecyclerView.ViewHolder = when (viewType) {
MODE_FOLDERS -> FoldersViewHolder.from(parent)
MODE_FILES -> FilesViewHolder.from(parent)
else -> FilesViewHolder.from(parent)
}
编辑:
这是 ViewHolder 类(PS:不要试图理解 onClick 和 onClickListeners,它只是多选的逻辑):
class FilesViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) {
private val fileName : TextView = itemView.findViewById(R.id.file_name_textView)
private val fileIcon : ImageView = itemView.findViewById(R.id.file_icon_imageView)
private val fileFormat : TextView = itemView.findViewById(R.id.file_format_textView)
private val fileSize : TextView = itemView.findViewById(R.id.file_size_textView)
private val fileTimeCreated : TextView = itemView.findViewById(R.id.time_created_textView)
private val root : ConstraintLayout = itemView.findViewById(R.id.root)
fun bind(item : FileModel, context : Context, adapter : FileExplorerAdapter) {
if (item.fileType == FileType.FOLDER) {
fileName.text = item.name
fileIcon.setImageDrawable(context.getDrawable(R.drawable.folder_icon))
fileSize.visibility = View.INVISIBLE
fileTimeCreated.visibility = View.INVISIBLE
fileFormat.visibility = View.INVISIBLE
root.setBackgroundResource(R.drawable.border_square)
} else {
fileSize.visibility = View.VISIBLE
fileTimeCreated.visibility = View.VISIBLE
fileFormat.visibility = View.VISIBLE
fileName.text = item.name
fileFormat.text = item.extension
fileSize.text = item.sizeInMB
fileTimeCreated.text = Time.convertUnixToDateTime(item.timeCreated)
val ext = item.extension
fileIcon.apply {
setImageDrawable(context.getDrawable(
if (item.fileType == FileType.FOLDER) R.drawable.folder_icon
else if (ext == "pdf") R.drawable.pdf_box
else if (ext == "doc" || ext == "docx") R.drawable.file_word
else if (ext == "mp3" || ext == "3gp") R.drawable.music
else if (ext == "mp4" || ext == "webm") R.drawable.video
else if (ext == "jpg" || ext == "png") R.drawable.image
else R.drawable.file))
}
if (item in adapter.selectedFiles) {
root.setBackgroundResource(R.drawable.border_square_selected)
} else {
root.setBackgroundResource(R.drawable.border_square)
}
}
root.setOnClickListener {
// Does multi-selection stuff like changing the rows background
}
root.setOnLongClickListener {
// does the same as the above onClickListener
true
}
}
companion object {
fun from(parent : ViewGroup) : FilesViewHolder = FilesViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.row_file_explorer_file, parent, false))
}
}
这是 Profiler 在其最大峰值期间的情况:
这个js在dialogfragment布局文件中声明了recyclerView:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:app = "http://schemas.android.com/apk/res-auto"
xmlns:tools = "http://schemas.android.com/tools"
android:layout_width = "match_parent"
android:layout_height = "match_parent"
android:background = "@drawable/dialog_fullscreen_background">
<com.google.android.material.appbar.AppBarLayout
android:id = "@+id/appBarLayout"
android:layout_width = "match_parent"
android:layout_height = "wrap_content"
android:elevation = "12dp"
app:layout_constraintEnd_toEndOf = "parent"
app:layout_constraintStart_toStartOf = "parent"
app:layout_constraintTop_toTopOf = "parent">
<androidx.appcompat.widget.Toolbar
android:id = "@+id/toolbar"
android:layout_width = "match_parent"
android:layout_height = "?attr/actionBarSize"
android:background = "?attr/toolbar_bottom_nav_color"
android:paddingStart = "6dp"
android:paddingEnd = "16dp"
android:elevation = "@dimen/toolbar_nav_elevation"
app:subtitleTextColor = "?attr/secondary_text_color"
app:titleTextColor = "?attr/primary_text_color"
app:contentInsetStartWithNavigation = "0dp"
app:navigationIcon = "@drawable/close_x"
tools:title = "@string/internal_storage" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id = "@+id/recyclerView"
android:layout_width = "match_parent"
android:layout_height = "0dp"
app:layoutManager = "androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf = "@+id/_constraintLayout2"
app:layout_constraintEnd_toEndOf = "parent"
app:layout_constraintStart_toStartOf = "parent"
app:layout_constraintTop_toBottomOf = "@+id/appBarLayout"
tools:listitem = "@layout/row_file_explorer" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id = "@+id/_constraintLayout2"
android:layout_width = "match_parent"
android:layout_height = "64dp"
android:background = "?attr/toolbar_bottom_nav_color"
android:elevation = "@dimen/toolbar_nav_elevation"
app:layout_constraintBottom_toBottomOf = "parent"
app:layout_constraintEnd_toEndOf = "parent"
app:layout_constraintHorizontal_bias = "0.0"
app:layout_constraintStart_toStartOf = "parent">
<Button
android:id = "@+id/save_button"
style = "@style/dialogButtonStyle"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_marginEnd = "8dp"
android:text = "@string/save"
app:layout_constraintBottom_toBottomOf = "parent"
app:layout_constraintEnd_toEndOf = "parent"
app:layout_constraintTop_toTopOf = "parent" />
<Button
android:id = "@+id/cancel_button"
style = "@style/dialogButtonStyle"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_marginEnd = "8dp"
android:text = "@string/cancel"
app:layout_constraintBottom_toBottomOf = "parent"
app:layout_constraintEnd_toStartOf = "@+id/save_button"
app:layout_constraintTop_toTopOf = "parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
还有这一行:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:app = "http://schemas.android.com/apk/res-auto"
xmlns:tools = "http://schemas.android.com/tools"
android:id = "@+id/root"
android:layout_width = "match_parent"
android:layout_height = "75dp"
android:background = "@drawable/border_square"
android:foreground = "@drawable/custom_ripple_no_border">
<TextView
android:id = "@+id/file_name_textView"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_marginStart = "16dp"
android:ellipsize = "start"
android:singleLine = "true"
android:textColor = "?attr/primary_text_color"
android:textSize = "20sp"
app:layout_constraintBottom_toBottomOf = "@+id/file_icon_imageView"
app:layout_constraintStart_toEndOf = "@+id/file_icon_imageView"
app:layout_constraintTop_toTopOf = "@+id/file_icon_imageView"
tools:text = "File file file" />
<ImageView
android:id = "@+id/file_icon_imageView"
android:layout_width = "50dp"
android:layout_height = "50dp"
android:layout_marginStart = "16dp"
android:layout_marginTop = "16dp"
android:layout_marginBottom = "16dp"
android:tint = "?attr/primary_text_color"
app:layout_constraintBottom_toBottomOf = "parent"
app:layout_constraintStart_toStartOf = "parent"
app:layout_constraintTop_toTopOf = "parent"
app:srcCompat = "@drawable/folder_icon"
tools:ignore = "ContentDescription" />
<TextView
android:id = "@+id/file_format_textView"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_marginEnd = "8dp"
android:layout_marginBottom = "8dp"
android:textColor = "?attr/secondary_text_color"
android:textSize = "12sp"
app:layout_constraintBottom_toBottomOf = "parent"
app:layout_constraintEnd_toEndOf = "parent"
tools:text = "Image File" />
<TextView
android:id = "@+id/file_size_textView"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_marginTop = "8dp"
android:layout_marginEnd = "8dp"
android:textColor = "?attr/secondary_text_color"
android:textSize = "12sp"
app:layout_constraintEnd_toEndOf = "parent"
app:layout_constraintTop_toTopOf = "parent"
tools:text = "100KB" />
<TextView
android:id = "@+id/time_created_textView"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:layout_marginTop = "4dp"
android:textColor = "?attr/secondary_text_color"
android:textSize = "12sp"
app:layout_constraintBottom_toBottomOf = "parent"
app:layout_constraintStart_toStartOf = "@+id/file_name_textView"
app:layout_constraintTop_toBottomOf = "@+id/file_name_textView"
tools:text = "25/08/2000" />
</androidx.constraintlayout.widget.ConstraintLayout
现在,对于探查器工具选项卡。
它显示了很多似乎与 ConstraintLayout 相关的内容,例如:
还有很多其他的也基本上显示了相同的,很多很多调用 onMeasure 和相关的 ui 函数。
app/buildgradle 上的 RecyclerView 和 ConstraintLayout 版本
// ConstraintLayout
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6'
// RecyclerView
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha03'