Глава 23. Подробнее об интентах и задачах неявный интент с использованием startActivity(…) (или startActivityForRe- sult(…) ), ОС незаметно включает в интент категорию Intent.CATEGORY_DEFAULT Таким образом, если вы хотите, чтобы фильтр интентов соответствовал неявным интентам, отправленным через startActivity(…) , вы должны включить в этот фильтр интентов категорию DEFAULT Активность с фильтром интентов MAIN/LAUNCHER является главной точкой входа приложения, которому она принадлежит. Для нее важно лишь то, что она явля- ется главной точкой входа приложения, а является ли она главной точкой входа «по умолчанию» — несущественно, поэтому она не обязана включать категорию CATEGORY_DEFAULT Так как фильтры интентов MAIN/LAUNCHER могут не включать CATEGORY_DEFAULT , надежность их соответствия неявным интентам, отправленным вызовом startAc- tivity(…) , не гарантирована. Поэтому мы используем интент для прямого запроса у PackageManager информации об активностях с фильтром интентов MAIN/LAUNCHER Следующий шаг — отображение меток этих активностей в списке ListView экзем- пляра NerdLauncherFragment . Метка (label) активности представляет собой ото- бражаемое имя — нечто, понятное пользователю. Если учесть, что эти активности являются активностями лаунчера, такой меткой, скорее всего, должно быть имя приложения. Метки активностей вместе с другими метаданными содержатся в объектах Re- solveInfo , возвращаемых PackageManager Сначала добавьте следующий код для сортировки объектов ResolveInfo , воз- вращаемых PackageManager , в алфавитном порядке меток, получаемых методом ResolveInfo.loadLabel(…) Листинг 23.3. Алфавитная сортировка (NerdLauncherFragment.java) Log.i("NerdLauncher", "I've found " + activities.size() + " activities."); Collections.sort(activities, new Comparator() { public int compare(ResolveInfo a, ResolveInfo b) { PackageManager pm = getActivity().getPackageManager(); return String.CASE_INSENSITIVE_ORDER.compare( a.loadLabel(pm).toString(), b.loadLabel(pm).toString()); } }); Затем создайте объект ArrayAdapter , который создаст простые представления эле- ментов списка с метками активностей, и назначьте этот адаптер ListView Листинг 23.4. Создание адаптера (NerdLauncherFragment.java) Collections.sort(activities, new Comparator() { } }); Создание явных интентов на стадии выполнения 387 ArrayAdapter adapter = new ArrayAdapter( getActivity(), android.R.layout.simple_list_item_1, activities) { public View getView(int pos, View convertView, ViewGroup parent) { View v = super.getView(pos, convertView, parent); // В документации сказано, что simple_list_item_1 // является TextView; преобразуем для задания текстового значения. TextView tv = (TextView)v; ResolveInfo ri = getItem(pos); tv.setText(ri.loadLabel(pm)); return v; } }; setListAdapter(adapter); Запустите приложение NerdLauncher; вы увидите список ListView , заполненный метками активностей. Рис. 23.2. Список активностей Создание явных интентов на стадии выполнения Мы использовали неявный интент для сбора информации об активностях и выводе ее в формате списка. Следующим шагом должен стать запуск выбранной активно- сти при нажатии пользователем на элементе списка. Для запуска активности будет использоваться явный интент.
388Глава 23. Подробнее об интентах и задачах Для создания явного интента нам потребуются дополнительные данные из ResolveInfo — в частности, имя пакета и имя класса активности. Эти данные мож- но получить из части ResolveInfo с именем ActivityInfo . (О том, какие данные доступны в разных частях ResolveInfo , можно узнать из документации.) В файле NerdLauncherFragment.java переопределите метод onListItemClick(…) для по- лучения объекта ActivityInfo для элемента списка. Затем используйте его данные для создания явного интента, запускающего активность. Листинг 23.5. Реализация onListItemClick(…) (NerdLauncherFragment.java) @Overridepublic void onListItemClick(ListView l, View v, int position, long id) { ResolveInfo resolveInfo = (ResolveInfo)l.getAdapter().getItem(position); ActivityInfo activityInfo = resolveInfo.activityInfo; if (activityInfo == null) return; Intent i = new Intent(Intent.ACTION_MAIN); i.setClassName(activityInfo.applicationInfo.packageName, activityInfo.name); startActivity(i);}Обратите внимание: в этом интенте мы отправляем действие как часть явного интен- та. Большинство приложений ведет себя одинаково независимо от того, включено действие или нет, однако некоторые приложения могут изменять свое поведение. Одна и та же активность может отображать разные интерфейсы в зависимости от того, как она была запущена. Вам как программисту лучше всего четко объявить свои намерения и позволить активностям запуститься так, как они считают нужным. В листинге 23.5 мы получаем имя пакета и имя класса из метаданных и используем их для создания явной активности методом Intent : public Intent setClassName(String packageName, String className) Этот способ отличается от того, который использовался нами для создания явных интентов в прошлом. Ранее мы использовали конструктор Intent , получающий объекты Context и Class : public Intent(Context packageContext, Class> cls) Этот конструктор использует свои параметры для получения ComponentName — имени пакета, объединенного с именем класса. Когда вы передаете Activity и Class для создания Intent , конструктор определяет полное имя пакета по Activity Также можно самостоятельно создать ComponentName по именам пакета и класса и использовать следующий метод Intent для создания явного интента: public Intent setComponent(ComponentName component) Однако решение с методом setClassName(…) , автоматически создающим имя ком- понента, получается более компактным. Запустите NerdLauncher и посмотрите, как работает запуск приложений. Задачи и стек возврата 389 Задачи и стек возврата Android использует задачи для отслеживания текущего состояния пользователя в каждом выполняемом приложении. Задача (task) представляет собой стек актив- ностей, с которыми имеет дело пользователь. Активность в нижней позиции стека называется базовой активностью, а активность в верхней позиции видна пользовате- лю. При нажатии кнопки Back верхняя активность извлекается из стека. Если нажать кнопку Back при просмотре базовой активности, вы вернетесь к домашнему экрану. Диспетчер задач позволяет переключаться между задачами без изменения состояния каждой задачи. Например, если вы начинаете вводить новый контакт и переключа- етесь на проверку своей публикации в Твиттере, будут запущены две задачи. При переключении на редактирование контактов сохраняется ваша текущая позиция в обеих задачах. Иногда запускаемая активность должна добавляться к текущей задаче. В других случаях она должна запускаться в новой задаче, независимой от запустившей ее активности. Запуск активности Нажатие кнопки Back Запуск активности (с флагом новой задачи) Рис. 23.3. Задачи и стек возврата
390Глава 23. Подробнее об интентах и задачах По умолчанию новые активности запускаются в текущей задаче. В приложении CriminalIntent все запускавшиеся активности добавлялись к текущей задаче. Это относилось даже к активностям, которые не являлись частью приложения Crim- inalIntent (например, при запуске активности для отправки отчета). Преимущество добавления активности к текущей задаче заключается в том, что пользователь мо- жет выполнять обратную навигацию внутри задачи, а не в иерархии приложений. В текущей версии все активности, запускаемые из NerdLauncher, добавляются в задачу NerdLaucher. Чтобы убедиться в этом, запустите CriminalIntent из Nerd- Launcher и вызовите диспетчер задач. (Нажмите кнопку Recents , если она имеется на устройстве; в противном случае используйте долгое нажатие кнопки Home .) Вы не найдете в списке CriminalIntent. Запущенная активность CrimeListAc- tivity была добавлена в задачу NerdLauncher. Нажатие на задаче NerdLauncher вернет вас к экрану CriminalIntent, который вы просматривали перед запуском диспетчера задач. Рис. 23.4. Приложение CriminalIntent не выполняется в отдельной задаче Мы хотим, чтобы приложение NerdLauncher запускало активности в новых задачах. Далее пользователь может переключаться между выполняемыми приложениями так, как считает нужным. Чтобы при запуске новой активности запускалась новая задача, следует добавить в интент соответствующий флаг. Запустите приложение NerdLauncher и выберите CriminalIntent. На этот раз при вызове диспетчера задач становится видно, что CriminalIntent выполняется в от- дельной задаче. Использование NerdLauncher в качестве домашнего экрана 391Листинг 23.6. Добавление флага в интент (NerdLauncherFragment.java) Intent i = new Intent(Intent.ACTION_MAIN); i.setClassName(activityInfo.applicationInfo.packageName, activityInfo.name); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(i); Рис. 23.5. CriminalIntent выполняется в собственной задаче Повторный запуск CriminalIntent из NerdLauncher не приведет к созданию второй задачи CriminalIntent. Флаг FLAG_ACTIVITY_NEW_TASK создает только одну задачу на активность. У CrimeListActivity уже имеется работающая задача, поэтому Android переключится на эту задачу вместо запуска новой. Использование NerdLauncher в качестве домашнего экранаНо кому захочется запускать приложение, чтобы запускать другие приложения? Гораздо логичнее использовать NerdLauncher как замену для домашнего экрана устройства. Откройте файл AndroidManifest.xml в NerdLauncher и добавьте следующий фрагмент в главный фильтр интентов. Листинг 23.7. Изменение категорий NerdLauncher (AndroidManifest.xml) продолжение
392 Глава 23. Подробнее об интентах и задачах
Добавление категорий HOME и DEFAULT означает, что активность NerdLauncher долж- на включаться в число вариантов домашнего экрана. Нажмите кнопку Home и вам будет предложено использовать NerdLauncher. (Если вы назначите NerdLauncher домашним экраном, а потом захотите отменить свой выбор, выполните команду Settings Applications Manage Applications . Выберите All , найдите NerdLauncher и сбросьте режим запуска по умолчанию Launch by default . При следующем нажатии кнопки Home вы сможете выбрать новый домашний экран по умолчанию.) Упражнение. Значки, изменение порядка задач В этой главе мы использовали метод ResolveInfo.loadLabel(…) для отображения содержательных имен в лаунчере. Класс ResolveInfo предоставляет аналогичный метод loadIcon() для получения значка, отображаемого для каждого приложения. Вам предлагается несложное упражнение: снабдить каждое приложение в Nerd- Launcher значком. Если вам захочется большего, добавьте в NerdLauncher другую активность для переключения между выполняемыми задачами. Для этого используйте системную службу ActivityManager , которая предоставляет информацию об активностях, зада- чах и приложениях, выполняемых в настоящее время. В отличие от PackageManager класс Activity не предоставляет вспомогательного метода getActivityManager() для получения доступа к этой системной службе. Вместо этого для получения объекта ActivityManager следует вызвать Activity. getSystemService() с передачей в параметре константы Activity.ACTIVITY_SERVICE Вызовите getRunningTasks() для получения списка выполняемых задач, упоря- доченных по времени запуска (от новых к старым). Чтобы вывести одну из таких задач на передний план, вызовите метод moveTaskToFront() . Обязательно сверьтесь со справочной документацией Android — для переключения между задачами в ма- нифест приложения необходимо добавить дополнительное разрешение. Для любознательных: процессы и задачи Для существования любого объекта необходима память и виртуальная машина. Процесс представляет собой место, созданное ОС, в котором существуют объекты вашего приложения и в котором выполняется само приложение. Процессам могут принадлежать ресурсы, находящиеся под управлением ОС — па- мять, сетевые сокеты, открытые файлы и т. д. Процесс также содержит минимум один (а вероятно, несколько) программный поток (thread). На платформе Android процесс всегда выполняется ровно на одной виртуальной машине Dalvik.
Для любознательных: процессы и задачи 393Как правило, каждый компонент приложения в Android связывается ровно с од- ним процессом (хотя встречаются довольно смутные исключения). Приложение создается с собственным процессом, который становится процессом по умолчанию для всех компонентов приложения. Отдельные компоненты можно назначать разным процессам, но мы рекомендуем придерживаться процесса по умолчанию. Если вы думаете, что какой-то код должен выполняться в другом процессе, аналогичного результата обычно удается добиться с использованием многопоточности (multi-threading), которая программируется в Android намного проще, чем многопроцессное выполнение. Каждый экземпляр активности существует ровно в одном процессе и ровно в од- ной задаче. Впрочем, на этом все сходство и завершается. Задачи содержат только активности и часто состоят из активностей разных приложений. С другой стороны, процессы содержат только выполняемый код и объекты приложения. Процессы и задачи легко спутать, потому что эти концепции отчасти перекрываются, а для ссылок на них часто используются имена приложений. Например, при запуске CriminalIntent из NerdLauncher ОС создает процесс CriminalIntent и новую задачу, для которой CrimeListActivity является базовой активностью. В диспетчере задач эта задача снабжается меткой CriminalIntent Задача, в которой существует активность, может быть не связана с процессом, в котором она существует. Когда мы запустили контактное приложение для выбора подозреваемого в CriminalIntent, оно было запущено в задаче CriminalIntent — но при этом выполнялось в процессе контактного приложения. (активность контактного приложения)Задача CriminalIntent (Процесс приложения CriminalIntent) (Процесс контактного приложения) Рис. 23.6. Задачи и процессы Это означает, что когда пользователь нажимает кнопку Back для перехода между раз- ными активностями, он может незаметно для себя переключаться между процессами. В этой главе мы создавали задачи и переключались между ними. Как насчет унич- тожения задач или замены стандартного диспетчера задач Android? К сожалению, Android не предоставляет средств для решения любой из этих задач. Долгое на- жатие на кнопке Home жестко связано с диспетчером задач по умолчанию, а задачи уничтожаться не могут. Процессы, напротив, могут уничтожаться. Приложения, рекламируемые в магазине Google Play как уничтожители задач, в действительности являются уничтожителями процессов. Стили и включения Хотя вашей первоочередной целью является нормальное функционирование приложения, не стоит забывать и о том, как приложение смотрит- ся и насколько удобно с ним работать. Рынок при- ложений огромен, и хороший пользовательский интерфейс может выделить ваше приложение на фоне конкурентов. Даже если пользовательский интерфейс спроек- тирован специалистом, вы должны реализовать его. В этой будут рассмотрены некоторые инстру- менты, помогающие быстро создать прототип ди- зайна приложения. Работая с профессиональным дизайнером, вы будете знать, чего от него требо- вать и как использовать созданные им ресурсы. В этих двух главах мы создадим приложение, имитирующее телевизионный пульт дистанцион- ного управления. Впрочем, ничем управлять оно не будет; это всего лишь тренажер для отработки навыков проектирования дизайна. В этой главе мы используем стили и включения для создания пульта, изображенного на рис. 24.1. Приложение RemoteControl содержит только одну активность, изображенную на рис. 24.1. В верхней части пульта выводится текущий канал. Ниже находится область, в которой новый канал отображается в процессе ввода. Нажатие кнопки Delete очищает область ввода канала. Кнопка Enter изменяет канал с обновлением области текущего канала и очисткой области ввода. Рис. 24.1. Приложение RemoteControl 24
Создание проекта RemoteControl 395 Создание проекта RemoteControl Создайте новый проект Android Application и настройте его параметры, как показано на рис. 24.2. Рис. 24.2. Создание проекта RemoteControl Прикажите мастеру создать пустую активность с именем RemoteControlActivity Создание RemoteControlActivity Так как класс RemoteControlActivity будет субклассом SingleFragmentActivity , скопируйте файл SingleFragmentActivity.java из проекта CriminalIntent в пакет com. bignerdranch.android.remotecontrol . Затем скопируйте файл activity_fragment.xml в каталог res/layout проекта RemoteControl. Откройте файл RemoteControlActivity.java . Назначьте класс RemoteControlActivity субклассом SingleFragmentActivity ; он будет создавать экземпляр RemoteControl- Fragment . (Вскоре мы создадим этот класс фрагмента). Наконец, переопределите метод RemoteControlActivity.onCreate(…) , чтобы он скрывал панель действий или панель заголовка активности. Листинг 24.1. Создание класса RemoteControlActivity (RemoteControlActivity.java) public class RemoteControlActivity extends Activity SingleFragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); продолжение
|