Каков размер размера стека нити пользовательского интерфейса Android и как его преодолеть?
Я получаю java.lang.StackOverflowErrors, когда рисуется иерархия просмотров:
at android.view.View.draw(View.java:6880)
at android.view.ViewGroup.drawChild(ViewGroup.java:1646)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
at android.view.View.draw(View.java:6883)
at android.view.ViewGroup.drawChild(ViewGroup.java:1646)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1373)
...
Исследование указывает на то, что иерархия моего представления слишком глубока для Android. Действительно, используя Иерархический просмотрщик, я вижу, что мое самое длинное вложение - 19 (!)
Мое приложение выглядит как приложение Google Play Store (с вкладками салфетки). Каждая вкладка представляет собой вложенный фрагмент внутри плейера просмотра фрагментов - с поддержкой v4 и HoloEverywhere. Очевидно, именно поэтому моя иерархия немного сошла с ума.
Мои вопросы:
-
Каков реальный размер размера стека? Я не нашел способа измерить размер стека потока пользовательского интерфейса. Некоторые слухи в сети говорят, что 8 КБ, но есть ли способ правильно измерить это на некоторых образцах?
-
Изменяется ли ограничение размера стека с ОС ver? Такая же иерархия не сбой на устройстве 4.0.3, а сбой на устройстве 2.3.3 (идентичное оборудование). Почему это?
-
Есть ли какое-либо решение, кроме оптимизации иерархии вручную? Я не нашел возможности увеличить смехотворно небольшой стек потока пользовательского интерфейса. Извините, но ограничение кадров в 60-100 кадров - это шутка.
-
Предполагая, нет ли чудо-решение на # 3, какие-либо рекомендации о том, где должна выполняться оптимизация иерархии ядра?
-
Сумасшедшая идея - я заметил, что каждый уровень представления добавляет около 3 вызовов функций (View.draw, ViewGroup.dispatchDraw, ViewGroup.drawChild). Может быть, я могу сделать свою собственную реализацию ViewGroup (настраиваемые макеты), которая менее расточительна для стека во время draw()?
Ответы
Ответ 1
Я считаю, что основной поток потоков управляется JVM - в случае Android - Dalvik JVM. Соответствующая константа, если я не ошибаюсь, находится в dalvik/vm/Thread.h
под #define kDefaultStackSize
Просмотр размеров стека через историю источников dalvik:
Итак, сколько вложенных представлений вы можете иметь:
Невозможно сказать точно. Предполагая, что размеры стека описаны выше, все зависит от того, сколько вызовов функций у вас есть в самом глубоком вложенности (и сколько переменных каждая функция принимает и т.д.). Кажется, что проблемная область - это когда просматриваются взгляды, начиная с android.view.ViewRoot.draw()
. Каждая точка зрения вызывает ничью своих детей, и она идет настолько глубоко, насколько ваша самая глубокая вложенность.
Я бы выполнил эмпирические тесты, по крайней мере, на устройствах, появляющихся во всех граничных группах выше. Похоже, что использование эмулятора дает точные результаты (хотя я только сравнил собственный эмулятор x86 с реальным устройством).
Имейте в виду, что оптимизация использования реальных виджетов/макетов может также повлиять на это. Сказав это, я считаю, что большая часть пространства стека съедается каждой иерархией раскладки, добавляя около 3 вложенных вызовов функций: draw()
→ dispatchDraw()
→ drawChild()
, и этот дизайн не сильно изменился с 2.3 - 4.2.
Ответ 2
Я бы дал этот и этот выстрел, он решит многие ваши вопросы и поможет вам понять, почему в uithread не нужен большой стек. Надеюсь, это поможет!
Ответ 3
Я не знаю, что такое ограничение размера стека, и, честно говоря, я не думаю, что поиск этого будет очень полезен. Как предполагает ваше второе предложение, это может сильно зависеть от того, какая версия Android и/или Dalvik VM присутствует на устройстве.
Что касается оптимизации ваших макетов, некоторые параметры включают в себя:
-
Используйте RelativeLayout вместо вложенности ViewGroups, особенно LinearLayouts внутри LinearLayouts, чтобы помочь сгладить вашу иерархию просмотров. Это не универсальное решение; на самом деле, вложенные RelativeLayouts могут помешать работе (потому что RelativeLayout всегда measure()
дважды, поэтому их вложенность оказывает экспоненциальное влияние на фазу измерения).
-
Используйте пользовательские представления/группы представлений в соответствии с вашим пятым вопросом. Я слышал о нескольких приложениях, которые это делают, и я думаю, что даже некоторые из приложений Google делают это.
- Если вы обнаружите в своей иерархии вид бесполезных детей, вы можете попробовать использовать тег
<merge>
в некоторых своих макетах (я сам не нашел много применений для них).
Ответ 4
Сумасшедшая идея 5 - Может быть, это не так безумно. Я объясняю вам эту теорию, и вы пытаетесь каким-то образом реализовать ее. Допустим, у нас есть 3 вложенных представления A > B > C. Вместо того, чтобы C быть вложенным в B, он вложен в D (какое-то несвязанное представление), и когда B пойдет, чтобы привлечь его, ему нужно вызвать B.draw(). Конечно, проблемы, с которыми вы можете столкнуться, - плохая компоновка. Но для этого можно найти решения.
Ответ 5
Лучшее объяснение моей сумасшедшей идеи 5. Я не говорю, что это хорошая идея:), но я хотел уточнить, как это можно сделать.
Мы создадим собственные реализации базовых макетов (LinearLayout, FrameLayout, RelativeLayout) и используем их вместо оригиналов.
Новые макеты расширят исходные, но переопределяют функцию draw()
.
Оригинальная функция draw вызывает dispatchDraw()
, которая вызывает drawChild()
- и только затем вы попадаете в draw()
вашего ребенка. Это означает, что draw()
в каждом гнездо добавляет 3 вызова функций. Мы попытаемся минимизировать его в 1 вызов функции вручную.
Это отвратительная часть. Вы знакомы с функциями inline
? Теоретически мы пытаемся сделать вызовы dispatchDraw()
и drawChild()
inline. Поскольку java действительно не поддерживает встроенный интерфейс, лучшим способом было бы сделать их вручную (фактически скопируйте код внутри в одну отвратительную функцию).
Где это становится немного сложнее? Реализация, которую мы будем использовать, будет поступать из Android-источников. Проблема в том, что эти источники, возможно, немного изменились между версиями Android. Поэтому нужно было бы сопоставить эти изменения и сделать набор переключателей в коде, чтобы вести себя точно так же, как в оригинале.
Кажется, слишком много работы, и это один адский взлом.