Я создаю простой FileExplorer для своего приложения, и с помощью Coroutines я получаю файлы по заданному пути, и при их отображении наблюдаются всплески использования памяти. Я показываю вкладки инструментов профилировщика внизу сообщения. Мое лучшее предположение состоит в том, что адаптер создает средство просмотра для каждого отдельного элемента в списке и использует всю память приложения и самого устройства.
Изменить: с помощью RelativeLayout вместо ConstraintLayout он уменьшился использование памяти в 3 раза, и для отображения списка требуется несколько секунд.
Краткое описание содержимого:
0 - функция, которая получает содержимое в пути
1 - сообщение OutOfMemoryException на консоли запуска AndroidStudio
2 - журнал сборщика мусора
3 - фрагмент кода, на который указывает ошибка OOM
4 - Где приведенный выше фрагмент кода называется
5 - Код ViewHolder
6 - Снимок экрана профилировщика, показывающий обзор самого большого всплеска (более 1 ГБ)
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
}
}
«beforeMain» и «afterMain» используются, когда я реализовать ProgressBar и отображать их в нужное время
Я заметил, что в папках с меньшим количеством файлов пользовательский интерфейс не задерживается, пока не будет загружен список файлов, но при нажатии на эту особенно большую папку 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: Не трогайте y, чтобы понять onClick и onClickListeners, это просто logi c для множественного выбора):
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, где 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 и связанных функций пользовательского интерфейса.
Версии RecyclerView и ConstraintLayout в app / buildgradle
// ConstraintLayout
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6'
// RecyclerView
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha03'