Глава 3. Жизненный цикл Activity Каждый метод регистрации существует в двух вариантах: один получает строковый тег и строку сообщения, а второй получает эти же аргументы и экземпляр Throwable , упрощающий регистрацию информации о конкретном исключении, которое может быть выдано вашим приложением. В листинге 3.8 представлены примеры сигнатуры методов журнала. Для сборки строк сообщений используйте стандартные средства конкатенации строк Java — или String.format , если их окажется недостаточно. Листинг 3.8. Различные способы регистрации в Android // Регистрация сообщения с уровнем отладки "debug" Log.d(TAG, "Current question index: " + mCurrentIndex); TrueFalse question; try { question = mAnswerKey[mCurrentIndex]; } catch (ArrayIndexOutOfBoundsException ex) { // Регистрация сообщения с уровнем отладки "error" // вместе с трассировкой стека исключений Log.e(TAG, "Index was out of bounds", ex); } Отладка приложений Android В этой главе вы узнаете, что делать, если в приложении скрывается ошибка. В част- ности, вы научитесь использовать LogCat, Android Lint и отладчик среды Eclipse. Чтобы потренироваться в починке, нужно сначала что-нибудь сломать. В файле QuizActivity.java закомментируйте строку кода onCreate(Bundle) , в которой мы полу- чаем mQuestionTextView Листинг 4.1. Из программы исключается важная строка (QuizActivity.java) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate() called"); setContentView(R.layout.activity_quiz); mQuestionTextView = (TextView)findViewById(R.id.question_text_view); //mQuestionTextView = (TextView)findViewById(R.id.question_text_view); mTrueButton = (Button)findViewById(R.id.true_button); mTrueButton.setOnClickListener(new View.OnClickListener() { }); } Запустите GeoQuiz и посмотрите, что получится. На рис. 4.1 показано сообщение, которое выводится при сбое приложения. В разных версиях Android используются слегка различающиеся сообщения, но все они озна- чают одно и то же. Конечно, вы и так знаете, что случилось с приложением, но если бы не знали — было бы полезно взглянуть на приложение в другой перспективе. 4
90 Глава 4. Отладка приложений Android Рис. 4.1. Сбой в приложении GeoQuiz Перспектива DDMS Выберите в меню Eclipse команду Window Open Perspective DDMS Рис. 4.2. Перспектива DDMS
Исключения и трассировка стека 91Перспективой (perspective) в Eclipse называется предопределенный набор панелей. Обычно во время отладки используются одни панели, во время редактирования — другие; Eclipse объединяет такие панели в перспективу. Перспективы определяются заранее, но жестко не фиксируются. Панели можно добавлять и удалять, а Eclipse запоминает ваш выбор. А если вам когда-нибудь захочется вернуться к исходному состоянию, достаточно выполнить команду Window Reset Perspective... Перспектива по умолчанию, в которой вы редактировали код, называется перспек-тивой Java. Перспективы, открытые в настоящее время, перечислены на кнопках в правом верхнем углу инструментальной области Eclipse. При помощи этих кнопок можно быстро переключаться между перспективами. На рис. 4.2 изображена перспектива DDMS (Dalvik Debug Monitor Service). Меха- низм DDMS обеспечивает всю техническую основу отладки в Android. В перспек- тиву DDMS входят панели LogCat и Devices На панели Devices представлены устройства и виртуальные устройства, подклю- ченные к компьютеру. Обычно на этой панели решаются проблемы, относящиеся к конкретному устройству. Например, устройство не входит в список вариантов, предлагаемых при запуске приложения? Щелкните на кнопке с треугольником в правом верхнем углу и вы- берите команду Reset adb . Часто после перезагрузки adb устройство обнаруживается системой. А может, LogCat выводит данные по другому устройству? Нет проблем — щелкните, чтобы выбрать интересующее вас устройство, а LogCat переключится на вывод данных по выбранному устройству. Исключения и трассировка стекаВернемся к проблеме с нашим приложением. Чтобы понять, что произошло, раз- верните панель LogCat. Прокрутите список и найдите текст, выделенный красным шрифтом. Это стандартный отчет об исключениях Android. В отчете приводится исключение верхнего уровня и данные трассировки стека; затем исключение, которое привело к этому исключению, и его трассировка стека; а так далее, пока не будет найдено исключение, не имеющее причины. Как правило, в написанном вами коде интерес представляет именно последнее исключение. В данном примере это исключение java.lang.NullPointerException Строка непосредственно под именем исключения содержит начало трассировки стека. В ней указывается класс и метод, в котором произошло исключение, а также имя файла и номер строки кода. Сделайте двойной щелчок в этой строке; Eclipse открывает указанную строку кода. В открывшейся строке программа впервые обращается к переменной mQuestion- TextView в методе updateQuestion() . Имя исключения NullPointerException под- сказывает суть проблемы: переменная не была инициализирована. Раскомментируйте строку с инициализацией mQuestionTextView , чтобы исправить ошибку. 92Глава 4. Отладка приложений Android Рис. 4.3. Исключение и трассировка стека в LogCat Помните: когда в вашей программе возникают исключения времени выполнения, следует искать последнее исключение в LogCat и первую строку трассировки стека со ссылкой на написанный вами код. Именно здесь возникает проблема, и именно здесь следует искать ответы. Даже если сбой происходит на неподключенном устройстве, не все потеряно. Устройство сохраняет последние строки, выводимые в журнал. Длина и срок хра- нения журнала зависят от устройства, но обычно можно рассчитывать на то, что результаты будут храниться минимум десять минут. Подключите устройство, вы- зовите перспективу DDMS в Eclipse и выберите свое устройство на панели Devices LogCat заполняется данными из сохраненного журнала. Диагностика ошибок поведенияПроблемы с приложениями не всегда приводят к сбоям — в некоторых случаях приложение просто начинает некорректно работать. Допустим, пользователь на- жимает кнопку Next , а в приложении ничего не происходит. Такие ошибки относятся к категории ошибок поведения. Диагностика ошибок поведения 93 В файле QuizActivity.java внесите изменение в слушателя mNextButton и закомменти- руйте код, увеличивающий mCurrentIndex Листинг 4.2. Из программы исключается важная строка (QuizActivity.java) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mNextButton = (Button)findViewById(R.id.next_button); mNextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCurrentIndex = (mCurrentIndex + 1) % mAnswerKey.length; //mCurrentIndex = (mCurrentIndex + 1) % mAnswerKey.length; updateQuestion(); } }); } Запустите GeoQuiz и нажмите кнопку Next . Ничего не происходит. Рис. 4.4. Кнопка Next не работает Эта ошибка коварнее предыдущей. Она не приводит к выдаче исключения, поэто- му исправление ошибки не сводится к простому устранению исключения. Кроме того, некорректное поведение может быть вызвано разными причинами: то ли в программе не изменяется индекс, то ли не вызывается метод updateQuestion()
94 Глава 4. Отладка приложений Android Если вы не знаете причину происходящего, необходимо ее выяснить. Далее я покажу два основных приема диагностики: сохранение трассировки стека и использование отладчика для назначения точки прерывания. Сохранение трассировки стека Включите команду сохранения отладочного вывода в метод updateQuestion() класса QuizActivity : Листинг 4.3. Использование Exception public class QuizActivity extends Activity { public void updateQuestion() { Log.d(TAG, "Updating question text for question #" + mCurrentIndex, new Exception()); int question = mAnswerKey[mCurrentIndex].getQuestion(); mQuestionTextView.setText(question); } Версия Log.d с сигнатурой Log.d(String, String, Throwable) регистрирует в жур- нале все данные трассировки стека — как уже встречавшееся ранее исключение AndroidRuntime . По данным трассировки стека вы сможете определить, в какой момент произошел вызов updateQuestion() При вызове Log.d(…) вовсе не обязательно передавать перехваченное исключение. Вы можете создать новый экземпляр Exception() и передать его методу без ини- циирования исключения. В журнале будет сохранена информация о том, где было создано исключение. Запустите приложение GeoQuiz, нажмите кнопку Next и проверьте вывод в LogCat. Рис. 4.5. Результаты
Установка точек прерывания 95Верхняя строка трассировки стека соответствует строке, в которой в журнале были зарегистрированы данные Exception . Следующая строка показывает, где был вы- зван set -метод — в реализации onTextChanged(…) . Сделайте двойной щелчок на этой строке; открывается позиция, в которой заголовку присваивается фиксированная строка. Но пока не торопитесь исправлять ошибку; сейчас мы найдем ее повторно при помощи отладчика. Регистрация в журнале данных трассировки стека — мощный инструмент отлад- ки, но он выводит довольно большой объем данных, которые, к тому же, содержат внутреннюю информацию о программе. Оставьте в программе несколько таких команд, и вскоре вывод LogCat превращается в невразумительную мешанину. Кроме того, по трассировке стека конкуренты могут разобраться, как работает ваш код, и похитить ваши идеи. С другой стороны, в некоторых ситуациях нужна именно трассировка стека с ин- формацией, показывающей, что делает ваш код. Если вы обратитесь за помощью на сайты https://stackoverflow.com или forums.bignerdranch.com, в вопрос часто жела- тельно включить трассировку стека. Информацию можно либо скопировать прямо из LogCat, либо сохранить в текстовом файле (щелкните на кнопке с изображением дискеты в правом верхнем углу панели LogCat ). Прежде чем продолжать, закомментируйте константу TAG и удалите команду log из QuizActivity.java Листинг 4.4. Прощай, друг (QuizActivity.java) public class QuizActivity extends Activity { public void updateQuestion() { Log.d(TAG, "Updating question text for question #" + mCurrentIndex, new Exception()); int question = mAnswerKey[mCurrentIndex].getQuestion(); mQuestionTextView.setText(question); } Закомментированная константа TAG подавляет предупреждение о неиспользуемой переменной. Вообще говоря, ее можно удалить, но она неожиданно может снова понадобиться для записи в журнал. Установка точек прерыванияПопробуем найти ту же ошибку при помощи отладчика среды Eclipse. Мы установим точку прерывания в updateQuestion() , чтобы увидеть, был ли вызван этот метод. Точка прерывания останавливает выполнение программы в заданной позиции, чтобы вы могли в пошаговом режиме проверить, что происходит далее. В файле QuizActivity.java вернитесь к методу updateQuestion() . В первой строке метода сделайте двойной щелчок на серой полосе слева от кода. На месте щелчка появляется синий кружок; он обозначает точку прерывания. 96Глава 4. Отладка приложений Android Рис. 4.6. Точка прерывания Чтобы задействовать отладчик и активизировать точку прерывания, необходимо запустить приложение в отладочном режиме (в отличие от обычного запуска). Для этого щелкните правой кнопкой мыши на проекте GeoQuiz и выберите команду Debug As Android Application Устройство сообщает, что оно ожидает подключения отладчика, а затем продолжает работу, как обычно. Когда приложение запустится и заработает под управлением отладчика, его вы- полнение прерывается. Запуск GeoQuiz привел к вызову метода QuizActivity. onCreate(Bundle) , который вызвал updateQuestion() , что привело к срабатыванию точки прерывания. Если вы впервые используете отладчик, на экране появляется большое окно со словами об открытии перспективы Debug . Щелкните на кнопке Yes , чтобы открыть перспективу Debug Рис. 4.7. Переключение на перспективу Debug Eclipse открывает перспективу Debug . Центральное место в ней занимает панель редактора. В редакторе открыт файл QuizActivity.java , а в нем выделена строка с точкой прерывания, вызвавшей остановку выполнения. Над редактором расположена панель Debug с текущей трассировкой стека. Кнопки с желтыми стрелками в верхней части панели используются для пошагового выполнения программы. Из трассировки стека видно, что метод updateQuestion() был вызван из onCreate(Bundle) . Так как нас интересует поведение кнопки Next , нажмите кнопку Resume , чтобы продолжить выполнение программы. Затем снова нажмите кнопку Next , чтобы увидеть, активизируется ли точка прерывания (она должна активизироваться). Теперь, когда мы добрались до интересного момента выполнения программы, можно немного осмотреться. Наверху справа найдите панель Variables . На ней можно просмо- треть текущие значения объектов вашей программы. Когда эта панель открывается Установка точек прерывания 97в первый раз, на ней выводится только одно значение: this (сам объект QuizActiv- ity ). Щелкните на кнопке с треугольником рядом с this или нажмите клавишу со стрелкой вправо. Результат должен выглядеть примерно так, как на рис. 4.9. Пошаговое выпол- нение с выходом из текущего метода Продолжить Отключиться Пошаговое вы- полнение с об- ходом методов Рис. 4.8. Панель Debug Рис. 4.9. Панель просмотра переменных Цветные фигуры рядом с именами переменных обозначают их видимость: зеленый кружок — открытая (public) переменная; синий треугольник — переменная с видимостью по умолчанию (видимость в пределах пакета); желтый ромб — защищенная (protected) переменная; красный квадрат — закрытая (private) переменная. 98 Глава 4. Отладка приложений Android В развернутом виде объект this выглядит немного устрашающе. В него включены не только переменные экземпляра, объявленные в QuizActivity , но и все переменные, объявленные в суперклассе, Activity , в суперклассе Activity , в его суперклассе и т. д. Нас сейчас интересует только одно значение: mCurrentIndex . Прокрутите список и найдите в нем mCurrentIndex . Разумеется, переменная равна 0. Код выглядит вполне нормально. Чтобы продолжить расследование, необходимо выйти из метода. Щелкните на кнопке, расположенной справа от кнопки Step Over (если навести на нее указатель мыши, выводится подсказка Step Return ). (Из-за вызова вспомогательного метода access$1(QuizActivity) на кнопке Step Return придется щелкнуть дважды.) Взгляните на панель редактора — управление передано слушателю OnClickListener кнопки mNextButton , в точку непосредственно после вызова updateQuestion() Удобно, что и говорить. Ошибку нужно исправить, но прежде чем вносить какие-либо изменения в код, не- обходимо прервать отладку приложения. Это можно сделать двумя способами: либо остановив программу, либо простым отключением отладчика. Чтобы остановить программу, следует выбрать процесс вашей программы на панели DDMS Devices и щелкнуть на кнопке с красным стоп-сигналом. Вариант с отключением обычно проще: щелкните на кнопке Disconnect (см. рис. 4.8). Затем снова переключитесь на перспективу Java и верните OnClickListener в прежнее состояние. Листинг 4.5. Возвращение к исходному состоянию (QuizActivity.java) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mNextButton = (Button)findViewById(R.id.next_button); mNextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //mCurrentIndex = (mCurrentIndex + 1) % mAnswerKey.length; mCurrentIndex = (mCurrentIndex + 1) % mAnswerKey.length; updateQuestion(); } }); } Также необходимо убрать точку прерывания. Найдите панель Breakpoints рядом с панелью Variables . (Если эта панель не отображается, откройте ее командой Window Show View Breakpoints .) Выделите точку прерывания и щелкните на темно- серой кнопке X в верхней части панели. Мы рассмотрели два способа поиска проблемной строки кода: сохранение в жур- нале трассировки стека и установка точки прерывания в отладчике. Какой способ лучше? Каждый находит свои применения, и скорее всего, один из них станет для вас основным.
Установка точек прерывания 99Преимущество трассировки стека заключается в том, что трассировки из нескольких источников просматриваются в одном журнале. С другой стороны, чтобы получить новую информацию о программе, вы должны добавить новые команды регистра- ции, заново построить приложение, развернуть его и добраться до нужной точки. С отладчиком работать проще. Запустив приложение с подключенным отладчиком (команда Debug As Android Application ), вы сможете установить точку прерывания во время работы приложения, а потом поэкспериментировать для получения инфор- мации сразу о разных проблемах. Прерывания по исключениямЕсли вам недостаточно этих решений, вы также можете использовать отладчик для перехвата исключений. Вернитесь к коду приложения и снова закомментируйте строку с присваиванием mQuestionTextView . Выполните команду Run Add Java Exception Breakpoint... , чтобы вызвать диалоговое окно прерывания по исключению. Рис. 4.10. Установка точки прерывания по исключению Диалоговое окно позволяет установить точку прерывания, которая срабатывает при инициировании исключения, где бы оно ни произошло. Прерывания можно ограничить только неперехваченными исключениями или же применить их как к перехваченным, так и неперехваченным исключениям. В Android большинство исключений перехватывается инфраструктурой, которая выводит приведенное ранее диалоговое окно и завершает процесс. Это означает, что обычно для прерываний по исключениям следует выбирать режим перехваченных исключений Suspend on caught exceptions |