我正在为我的应用程序构建一个简单的FileExplorer,并使用协程在给定路径中获取文件,并且在显示它们时,内存使用率出现峰值。我在帖子底部显示了探查器工具选项卡。
我最好的猜测是,适配器正在为列表中的每个项目创建一个Viewholder,并且正在使用应用程序和设备本身的所有内存。
编辑:通过使用RelativeLayout而不是ConstraintLayout,它将内存使用量减少了3倍,显示列表需要花费几秒钟。
内容简要说明:
0-获取路径内容的函数
1-AndroidStudio的“运行”控制台上出现OutOfMemoryException消息
2-垃圾收集器日志
3-OOM错误指向的代码段
4-上面的代码段称为
5-ViewHolder代码
6-Profiler屏幕截图,显示最大峰值的概述(超过1GB)
7-DialogFragment布局文件,其中声明了RecyclerView
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在其最大峰值期间:
在对话框片段布局文件中声明了recyclerView的js:
<?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'
最佳答案
显然,“bug”一直都在ConstraintLayout lib中。我使用的版本:
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6'
显然存在2.0.0-beta7版本解决的某种内存泄漏问题。不幸的是,我的AndroidStudio并没有告诉我甚至没有任何更新。
非常感谢@YuriyMysochenko发现它,并感谢试图帮助我的人们!
关于android - 当加载包含6000个项目的列表时,ConstraintLayout与RecyclerVIew(ListAdapter)一起使用了巨大的内存量(最大1GB),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62441342/