- Как запустить Power Manager всех производителей Android, чтобы включить фоновое и push-уведомление?
- 2 ответа
- Русские Блоги
- Android Power Management
- Эволюция планировщиков задач
- Хронология развития событий
- AlarmManager, Handler, Service
- JobScheduler
- GCM Network Manager
- WakefulBroadcastReceiver
- DozeMode: сон на ходу
- AppStandby: неактивные приложения
- Background Optimizations. Svelte
- FirebaseJobDispatcher
- Android Job by Evernote
- Background Execution Limits
- WorkManager
- Если подводить итоги
Как запустить Power Manager всех производителей Android, чтобы включить фоновое и push-уведомление?
Некоторые устройства Android из-за пользовательских настроек Android выполняются производителями, у которых есть политика в отношении управления питанием, которая нарушает некоторые функции, такие как push-уведомления.
- Huawei — только Pre-EMUI 5.0 / Android 7 — перейдите в «Настройки»> «Защищенные приложения», проверьте свое приложение.
- Sony — коснитесь значка батареи. Перейдите в Управление питанием> Режим STAMINA> Приложения, активные в режиме ожидания> Добавить приложение.
- Asus — проверьте свое приложение в Диспетчере автозапуска.
- Xiaomi — Безопасность (приложение)> Разрешения> Автозапуск — Включите приложение
- * Новый Xiaomi — Настройки> Параметры разработчика. Отключить «оптимизацию памяти». Чтобы включить параметры разработчика, перейдите в «Настройки»> «О программе». Тапаем по MIUI 8 раз.
- Oppo — перейдите в «Настройки»> «Настройки безопасности»> «Сохранение данных» и включите свое приложение.
- Samsung — отключить оптимизацию использования батареи
Я хочу собрать намерения запустить соответствующие инструменты, но нашел только для Huawei и Xiaomi.
Мне нужна помощь всех остальных продюсеров, т.к.
2 ответа
Я собрал некоторые намерения из разных сообщений:
После согласия пользователя
Швы, новые выпуски требуют этого разрешения
Я хочу собрать все намерения открыть диспетчер мощности, если кто-то обнаружил ошибку или хочет что-то улучшить, прокомментируйте здесь
Источник
Русские Блоги
Android Power Management
в целом Android Управление питанием относительно просто , В основном через блокировку и таймер для переключения состояния системы , Минимизировать энергопотребление системы , Схема архитектуры управления питанием всей системы выглядит следующим образом : ( Обратите внимание, что картина исходит от Steve Guo)
Далее мы начнем с Java Уровень применения , Android framework Уровни , Linux Подробные обсуждения на уровне ядра :
Использование прикладного уровня :
Android Обеспечивает готовые android.os.PowerManager категория , Этот класс используется для управления переключением состояния питания устройства .
Этот класс имеет три интерфейсные функции :
void goToSleep(long time); // Принудительное устройство ввода Sleep состояние
Попробуйте вызвать эту функцию на прикладном уровне , Но не может добиться успеха , Ошибка, кажется, недостаточно прав , Но в Framework следующий Service Можно позвонить .
newWakeLock(int flags, String tag);// Получить соответствующий уровень блокировки
flags Описание параметра :
PARTIAL_WAKE_LOCK: Screen off, keyboard light off
SCREEN_DIM_WAKE_LOCK: screen dim, keyboard light off
SCREEN_BRIGHT_WAKE_LOCK: screen bright, keyboard light off
FULL_WAKE_LOCK: screen bright, keyboard bright
ACQUIRE_CAUSES_WAKEUP: Принудительно открывать после блокировки запроса Screen и keyboard light
ON_AFTER_RELEASE: При снятии блокировки reset activity timer
Если применяется partial wakelock, Тогда даже если вы нажмете Power облигация , Система не войдет Sleep, в качестве Music При игре
Если вы подали заявку на другие wakelocks, пресс Power облигация , Система все равно войдет Sleep
void userActivity(long when, boolean noChangeLights);//User activity Событие происходит , Устройство будет переключено на Full on Состояние , В то же время Reset Screen off timer.
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock (PowerManager.SCREEN_DIM_WAKE_LOCK, “My Tag”);
1. В приложениях, которые используют вышеуказанные функции , Должно быть Manifest.xml Добавьте следующие разрешения в файл :
2. Все замки должны использоваться в парах , Если заявка на участие и не была выпущена вовремя, это приведет к сбою системы . Если применяется partial wakelock, Без своевременного выпуска , Тогда система никогда не сможет войти Sleep режим .
Android Framework Уровни :
Основной файл кода выглядит следующим образом :
среди PowerManagerService.java Это ядро , Power.java Обеспечить интерфейс функции низкого уровня , и JNI Взаимодействие слоев , JNI Код слоя находится в основном в файле android_os_Power.cpp в , и Linux kernel Взаимодействие через Power.c Для достижения , Andriod с Kernel Взаимодействие в основном через sys Путь документа , Для получения подробной информации, пожалуйста, обратитесь к Kernel Введение слоя .
Функции этого слоя относительно сложны , Такие как переключение состояния системы , Регулировка подсветки и выключатель ,Wake Lock Заявка и выпуск и т. Д. , Но этот слой не имеет ничего общего с аппаратной платформой , И по Google Ответственный за обслуживание , Будет меньше проблем , Заинтересованные друзья могут просматривать связанные коды самостоятельно .
Основной код находится в следующем месте :
Это право Kernel Предоставленные функции интерфейса
EXPORT_SYMBOL(android_init_suspend_lock); // инициализация Suspend lock, Должен быть инициализирован перед использованием
EXPORT_SYMBOL(android_uninit_suspend_lock); // релиз suspend lock Связанные ресурсы
EXPORT_SYMBOL(android_lock_suspend); // приложение lock, Должен позвонить в соответствующий unlock Чтобы выпустить это
EXPORT_SYMBOL(android_lock_suspend_auto_expire);// приложение partial wakelock, Это будет автоматически выпущено после таймера
EXPORT_SYMBOL(android_unlock_suspend); // релиз lock
EXPORT_SYMBOL(android_power_wakeup); // Разбудить систему, чтобы on
EXPORT_SYMBOL(android_register_early_suspend); // Зарегистрироваться early suspend Вождение
EXPORT_SYMBOL(android_unregister_early_suspend); // Отменить уже зарегистрирован early suspend Вождение
Обеспечить Android Framework Слои proc Файл выглядит следующим образом :
«/sys/android_power/acquire_partial_wake_lock» // приложение partial wake lock
«/sys/android_power/acquire_full_wake_lock» // приложение full wake lock
«/sys/android_power/release_wake_lock» // Отпустить соответствующий wake lock
«/sys/android_power/request_state» // Запрос на изменение состояния системы , вводить standby И обратно wakeup Два состояния
«/sys/android_power/state» // Указывает текущее состояние системы
Android Из управления питанием в основном через Wake lock Для достижения , На самом низком уровне управление достигается через следующие три очереди :
Все инициализировано lock Будет вставлен в g_inactive_locks В очереди , И в настоящее время активный partial wake lock Будет вставлен в g_active_partial_wake_locks В очереди , деятельность full wake lock Был вставлен в g_active_full_wake_locks В очереди , любой partial wake lock и full wake lock После истечения срока или unlock Будет перемещен в inactive очередь , В ожидании следующего звонка .
в Kernel Использование слоя wake lock Шаги следующие :
1. Функция вызова android_init_suspend_lock Инициализировать один wake lock
2. Приложение, связанное с вызовом lock Функция android_lock_suspend или android_lock_suspend_auto_expire запрос lock, Вы можете подать заявку только здесь partial wake lock, Если вы хотите подать заявку Full wake lock, Вам нужно вызвать функцию android_lock_partial_suspend_auto_expire( Функция не EXPORT Выходи ), Это название немного странно , Не следуй за фронтом android_lock_suspend_auto_expire Confused .
3. Если да auto expire из wake lock Вы можете игнорировать , В противном случае, связанные wake lock Освобожденный , В противном случае это приведет к длительной работе системы в состоянии высокого энергопотребления. .
4. Удалить или больше не использовать драйвер Wake lock Не забудьте позвонить вовремя android_uninit_suspend_lock Освободите ресурсы .
USER_AWAKE, //Full on status
USER_NOTIFICATION, //Early suspended driver but CPU keep on
USER_SLEEP // CPU enter sleep mode
Принципиальная схема его состояния переключения выглядит следующим образом :
После нормального запуска системы введите AWAKE состояние , Backlight Будет медленно настраиваться от ярчайшего до яркости, установленной пользователем , система screen off timer(settings->sound & display-> Display settings -> Screen timeout) Время начала , До истечения таймера , Если есть activity Событие происходит , в качестве Touch click, keyboard pressed Ждать событий , воли Reset screen off timer, Система остается в AWAKE состояние . Если заявка подается в течение этого времени Full wake lock, Тогда система также останется на AWAKE состояние , Если пользователь не нажимает power key. в AWAKE Если заряд батареи низкий или используется AC Блок питания screen off timer Время истекло и выбрано Keep screen on while pluged in опции ,backlight Будет вынужден DIM Состояние .
если Screen off timer Время истекло, а не Full wake lock Или пользователь нажал power key, Тогда состояние системы будет переключено на NOTIFICATION, И позвоните всем зарегистрированным g_early_suspend_handlers функция , Обычно ставят LCD и Backlight Регистрация водителя early suspend тип , При необходимости вы можете зарегистрировать другие драйверы как early suspend, Это будет закрыто на первом этапе . Далее система определит, есть ли partial wake lock acquired, Если есть, дождитесь его выпуска , Если есть user activity Событие происходит , Система немедленно возвращается AWAKE состояние ; Если нет partial wake lock acquired, Система немедленно вызовет функцию pm_suspend Закройте другие связанные драйверы , Сделать CPU Иди спать .
Система в Sleep Если какой-либо из них обнаружен во время статуса Wakeup source, тогда CPU Это будет от Sleep Государство пробуждено , И позвоните соответствующему водителю resume функция , Тогда сразу звоните предварительно зарегистрированным early suspend управляемый resume функция , Наконец, статус системы возвращается AWAKE состояние . Проблема здесь в том, что все зарегистрированные early suspend Функция S Suspend Первый этап называется понятным , Но в resume время , Linux Сначала позвоню всем водителям resume функция , А затем позвоните предварительно зарегистрированным early suspend управляемый resume Что означает функция ? Лично чувствую android Из этого early suspend и late resume Функции должны быть объединены Linux следующий suspend и resume Использовать вместе , Вместо использования единой очереди для управления .
Поскольку я долгое время не изучал Android, возможно, некоторые из них не правильно поняты или даже неправильно, пожалуйста, поймите. Если вы обнаружите какие-либо вопросы, вы также можете обсудить их, если вам интересно.
Источник
Эволюция планировщиков задач
Приложение iFunny, над которым мы работаем, доступно в сторах уже более пяти лет. За это время мобильной команде пришлось пережить множество разных подходов и миграций между инструментами, а год назад появилось время перейти с самописного решения и посмотреть в сторону чего-то более «модного» и распространённого. Эта статья — небольшая выжимка о том, что было изучено, на какие решения смотрели и к чему в итоге пришли.
Зачем нам это всё?
Давайте сразу определимся, в честь чего эта статья и почему эта тема оказалась важной для команды Android-разработки:
- есть множество сценариев, когда необходимо запускать задачи вне рамок активного пользовательского интерфейса;
- система накладывает большое количество ограничений на запуск подобных задач;
- выбрать между существующими решениями оказалось довольно сложно, так как каждый инструмент имеет свои плюсы и минусы.
Хронология развития событий
AlarmManager, Handler, Service
Изначально были реализованы свои решения для запуска бэкграунд-задач на основе сервисов. Также был механизм, который привязывал задачи к жизненному циклу и умел отменять и восстанавливать их. Команду это долгое время устраивало, так как никаких ограничений платформа к таким задачам не предъявляла.
В Google же советовали это делать, опираясь на следующую диаграмму:
В конце 2018 года разбираться в этом уже нет смысла, достаточно оценить масштабы бедствия.
Фактически никого не заботило, как много работы происходит в фоне. Приложения делали что хотели и когда хотели.
Плюсы:
доступно везде;
доступно для всех.
Минусы:
система всячески ограничивает работу;
нет запусков по условию;
API минимальное и нужно писать много кода.
Android 5. Lollipop
JobScheduler
Спустя 5(!) лет, ближе к 2015 году в Google заметили, что задачи запускаются неэффективно. Пользователи стали регулярно жаловаться, что их телефоны разряжаются, просто лёжа на столе или в кармане.
С выходом Android 5 появился такой инструмент, как JobScheduler. Это механизм, с чьей помощью можно в фоне выполнять различную работу, начало выполнения которой оптимизировалось и упрощалось за счёт централизованной системы запуска этих задач и возможности задавать условия для этого самого запуска.
В коде всё это выглядит достаточно просто: объявляется сервис, в который приходят события старта и конца работы.
Из нюансов: если вы хотите выполнить работу асинхронно, то из onStartJob нужно запустить поток; главное не забыть вызвать метод jobFinished по окончанию работы, иначе система не отпустит WakeLock, ваша задача не будет считаться выполненной и утечёт.
Из любого места в приложении вы можете инициировать выполнение этой работы. Задачи выполняются в нашем процессе, но инициируются на уровне IPC. Есть централизованный механизм, который управляет их выполнением и будит приложение только в необходимые для этого моменты. Также можно задавать различные условия запуска и передавать данные через Bundle.
В общем, по сравнению с ничем это было уже кое-что. Но этот механизм доступен только с API 21, и на момент выхода Android 5.0 было бы странно перестать поддерживать все старые девайсы (прошло 3 года, а мы до сих пор поддерживаем четвёрки).
Плюсы:
API простое;
условия для запуска.
Минусы: доступно начиная с API 21;
фактически только с API 23;
легко ошибиться.
Android 5. Lollipop
GCM Network Manager
Также был представлен аналог JobScheduler — GCM Network Manager. Это библиотека, которая предоставляла схожий функционал, но работала уже с API 9. Правда, взамен требовала наличие Google Play Services. Видимо, функционал, необходимый для работы JobScheduler, стали поставлять не только через версию Android, но и на уровне GPS. Надо отметить, что разработчики фреймворка очень быстро одумались и решили не связывать своё будущее с GPS. Спасибо им за это.
Выглядит всё абсолютно идентично. Такой же сервис:
Такой же запуск задач:
Такая похожесть архитектуры диктовалась унаследованным функционалом и желанием получить простую миграцию между инструментами.
Плюсы:
API, аналогичное JobScheduler;
доступно начиная с API 9.
Минусы:
необходимо иметь Google Play Services;
легко ошибиться.
Android 5. Lollipop
WakefulBroadcastReceiver
Далее напишу пару слов об одном из базовых механизмов, который используется в JobScheduler и доступен разработчикам напрямую. Это WakeLock и основанный на нём WakefulBroadcastReceiver.
С помощью WakeLock можно запретить системе уходить в suspend, то есть держать девайс в активном состоянии. Это необходимо, если мы хотим выполнить какую-то важную работу.
При создании WakeLock можно указать его настройки: держать CPU, экран или клавиатуру.
На основе этого механизма работает WakefulBroadcastReceiver. Мы запускаем сервис и удерживаем WakeLock.
После того как сервис выполнил необходимую работу, мы отпускаем его через аналогичные методы.
Через 4 версии этот BroadcastReceiver станет deprecated, и на developer.android.com будут описаны следующие альтернативы:
- JobScheduler;
- SyncAdapter;
- DownloadManager;
- FLAG_KEEP_SCREEN_ON для Window.
Android 6. Marshmallow
DozeMode: сон на ходу
Далее в Google начали применять различные оптимизации для приложений, запущенных на устройстве. Но что для пользователя оптимизация, то для разработчика ограничение.
Первым делом появился DozeMode, который переводит устройство в спящий режим, если оно лежало без действий определённое время. В первых версиях это длилось час, в последующих длительность сна уменьшили до 30 минут. Периодически телефон просыпается, выполняет все отложенные задачи и снова засыпает. Окно DozeMode увеличивается экспоненциально. Все переходы между режимами можно отследить через adb.
При наступлении DozeMode на приложение накладываются следующие ограничения:
- система игнорирует все WakeLock;
- откладывается AlarmManager;
- JobScheduler не работает;
- SyncAdapter не работает;
- доступ в сеть ограничен.
Также вы можете добавить ваше приложение в whitelist, чтобы оно не попадало под ограничения DozeMode, но как минимум Samsung полностью игнорировал этот список.
Android 6. Marshmallow
AppStandby: неактивные приложения
Система определяет приложения, которые являются неактивными, и накладывает на них все те же ограничения, что и в рамках DozeMode.
Приложение отправляется в изоляцию, если:
- не имеет процесса на переднем плане;
- не имеет активной нотификации;
- не добавлено в список исключений.
Android 7. Nougat
Background Optimizations. Svelte
Svelte — это проект, в рамках которого Google пытается оптимизировать потребление оперативной памяти приложениями и самой системой.
В Android 7 в рамках этого проекта было решено, что неявные бродкасты не очень эффективны, так как их слушает огромное количество приложений и система тратит большое количество ресурсов при наступлении этих событий. Поэтому следующие типы событий были запрещены для объявления в манифесте:
- CONNECTIVITY_ACTION;
- ACTION_NEW_PICTURE;
- ACTION_NEW_VIDEO.
Android 7. Nougat
FirebaseJobDispatcher
В это же время была опубликована новая версия фреймворка для запуска задач — FirebaseJobDispatcher. На самом деле это был дописанный GCM NetworkManager, который немного привели в порядок и сделали чуть более гибким.
Визуально всё выглядело точно так же. Такой же сервис:
Единственное чем он отличался, так это возможностью установки своего драйвера. Драйвер — это класс, который отвечал за стратегию запуска задач.
Сам же запуск задач с течением времени не изменился.
Плюсы:
API, аналогичное JobScheduler;
доступно начиная с API 9.
Минусы:
необходимо иметь Google Play Services;
легко ошибиться.
Вселяла надежду возможность установки своего драйвера, чтобы отвязаться от GPS. Мы даже поискали, но в итоге нашли следующее:
Google знает об этом, но эти задачи несколько лет остаются открытыми.
Android 7. Nougat
Android Job by Evernote
В итоге сообщество не выдержало, и появилось самописное решение в виде библиотеки от Evernote. Оно было не единственное, но именно решение от Evernote смогло зарекомендовать себя и «выбилось в люди».
В архитектурном плане эта библиотека была удобнее своих предшественников.
Появилась сущность, отвечающая за создание задач. В случае с JobScheduler они создавались через reflection.
Имеется отдельный класс, который является самой задачей. В JobScheduler это всё было свалено в switch внутри onStartJob.
Запуск задач идентичен, но кроме унаследованных событий Evernote ещё добавил и свои, такие как запуск ежедневных задач, уникальные задачи, запуск в рамках окна.
Плюсы:
удобное API;
поддерживается на всех версиях;
не нужны Google Play Services.
Минусы:
стороннее решение.
Ребята активно поддерживали свою библиотеку. Хотя было довольно много критичных проблем, она работала на всех версиях и на всех девайсах. В итоге в прошлом году наша Android-команда выбрала решение именно от Evernote, так как библиотеки от Google срезают большой пласт девайсов, которые они не могут поддержать.
Внутри себя же она работала на решениях от Google, в крайних случаях — с AlarmManager.
Background Execution Limits
Вернёмся к нашим ограничениям. С приходом нового Android пришли и новые оптимизации. Ребята из Google нашли другую проблему. В этот раз всё дело оказалось в сервисах и бродкастах (да, ничего нового).
startService если приложения в фонеimplicit broadcast в манифесте
Во-первых, было запрещено запускать сервисы из фона. В «рамках закона» остались только foreground services. Сервисы теперь, можно сказать, deprecated.
Второе ограничение — всё те же бродкасты. В этот раз стала запрещена регистрация ВСЕХ неявных бродкастов в манифесте. Неявный бродкаст — это бродкаст, который предназначается не только нашему приложению. Например, есть Action ACTION_PACKAGE_REPLACED, а есть ACTION_MY_PACKAGE_REPLACED. Так вот, первый — это неявный.
Но любой бродкаст по-прежнему можно зарегистрировать через Context.registerBroadcast.
WorkManager
На этом оптимизации пока прекратились. Возможно, устройства стали работать быстро и бережно в плане энергопотребления; возможно, пользователи стали меньше жаловаться на это.
В Android 9 разработчики фреймворка основательно подошли к инструменту для запуска задач. В попытке решить все насущные проблемы, на Google I/O была представлена библиотека для запуска бэкграунд-задач WorkManager.
Google последнее время пытается сформировать своё видение архитектуры Android-приложения и даёт разработчикам инструменты, необходимые для этого. Так появились архитектурные компоненты с LiveData, ViewModel и Room. WorkManager выглядит как разумное дополнение их подхода и парадигмы.
Если же говорить про то, как устроен WorkManager внутри, то никакого технологического прорыва в нём нет. По сути это обёртка уже существующих решений: JobScheduler, FirebaseJobDispatcher и AlarmManager.
Код выбора довольно прост. Но надо заметить, что JobScheduler доступен начиная с API 21, но используют его только с API 23, так как первые версии были довольно нестабильные.
Если версия ниже 23, то через reflection пробуем найти FirebaseJobDispatcher, в противном случае используем AlarmManager.
Стоит отметить, обёртка вышла достаточно гибкой. В этот раз разработчики всё разбили на отдельные сущности, и архитектурно это выглядит удобно:
- Worker — логика работы;
- WorkRequest — логика запуска задачи;
- WorkRequest.Builder — параметры;
- Constrains — условия;
- WorkManager — менеджер, который управляет задачами;
- WorkStatus — статус задачи.
Условия для запуска наследовались от JobScheduler.
Можно отметить, что триггер на изменение URI появился только с API 23. К тому же можно подписаться на изменение не только определённого URI, но и всех вложенных в него с помощью флага в методе.
Если говорить о нас, то ещё на этапе альфы было решено перейти на WorkManager.
Причин для этого несколько. В Evernote есть пара критичных багов, которые разработчики библиотеки обещают поправить с переходом на версию с интегрированным WorkManager. Да и сами они соглашаются, что решение от Google сводит на нет плюсы Evernote. К тому же это решение хорошо вписывается в нашу архитектуру, так как мы используем Architecture Components.
Далее хотелось бы на простом примере показать, в каком виде мы стараемся использовать этот подход. При этом не сильно критично, WorkManager у вас или JobScheduler.
Посмотрим на пример с очень простым кейсом: клик по republish или like.
Сейчас все приложения стараются уйти от блокирующих запросов в сеть, так как это нервирует пользователя и заставляет его ждать, хотя в это время он может делать покупки внутри приложения или смотреть рекламу.
В таких случаях сначала изменяются локальные данные — пользователь сразу видит результат своего действия. Затем в фоне идёт запрос на сервер, при неудаче которого данные сбрасываются в начальное состояние.
Далее покажу пример того, как это выглядит у нас.
JobRunner содержит логику запуска задач. В его методах описывается конфигурация задач и передаются параметры.
Сама задача в рамках WorkManager выглядит следующим образом: берём id из параметров и вызываем метод на сервере, чтобы поставить лайк на этот контент.
У нас есть базовый класс, который содержит следующую логику:
Во-первых, он позволяет немного уйти от явного знания о Worker. Также он содержит логику внедрения зависимостей через WorkerInjector.
Он просто проксирует вызовы в Dagger, но это нам помогает при тестировании: мы подменяем реализации инжектора и внедряем в задачи необходимое окружение.
Interactor — это сущность, которую дёргает ViewController, чтобы инициировать прохождение сценария (в данном случае —поставить лайк). Мы отмечаем локально контент как «залайканный» и отправляем задачу на выполнение. Если задача происходит неуспешно, то лайк снимается.
Мы используем Architecture Components от Google: ViewModel и LiveData. Так выглядит наша ViewModel. Здесь мы связываем обновление объекта в DAO со статусом лайка.
ViewController, с одной стороны, подписывается на изменение статуса лайка, с другой — инициирует прохождение нужного нам сценария.
И это практически весь код, необходимый нам. Осталось дописать поведение самой View с лайком и реализацию вашего DAO; если вы используете Room, то просто прописать поля в объекте. Выглядит довольно просто и эффективно.
Если подводить итоги
JobScheduler, GCM Network Manager, FirebaseJobDispatcher:
- не используйте их
- больше не читайте статьи про них
- не смотрите доклады
- не думайте, что из них выбрать.
Android Job by Evernote:
- внутри будут использовать WorkManager;
- критичные баги размываются между решениями.
WorkManager:
- API LEVEL 9+;
- не зависит от Google Play Services;
- Chaining/InputMergers;
- реактивный подход;
- поддержка от Google (хочется в это верить).
Источник