Сегодня в выпуске: скрываем приложение от троянов, боремся с утечками памяти, создаем неубиваемый сервис, работаем с сенсорами температуры, отлаживаем приложение прямо на устройстве и готовим свое приложение к ограничениям Android Q. А также: подборка первоклассных инструментов пентестера и библиотек для разработчиков.
Mobile Malware Analysis: Overlay and How to Counter it (partly) — небольшая статья о том, как скрыть имя пакета приложения от трояна.
Обычно банковские трояны и другие фишинговые приложения пытаются выудить из пользователя конфиденциальные данные, применяя две техники:
Инженеры Google знают об этой проблеме, поэтому начиная с Android 7 получить доступ к списку запущенных приложений можно только с помощью API UsageStats или сервиса Accessibility. И тот и другой требуют от пользователя перейти в настройки, активировать переключатель напротив нужного приложения и согласиться с предупреждающим сообщением. Однако на версиях Android ниже 7 (API < 24) все гораздо проще — достаточно вызвать одну функцию или прочитать синтетический файл:
runningTask.get(0).topActivity
getRunningAppProcesses().get(0).processName
/proc/pid/cmdline
и /proc/pid/stat
Автор задался вопросом, можно ли защитить пользователей Android 6 и ниже от фишинга, и в итоге пришел к следующему решению:
try { Method setter = android.os.Process.class.getMethod("setArgV0", String.class); setter.invoke(android.os.Process.class, text); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
Данный код использует рефлексию для доступа к методу Process.setArgv0()
, который меняет первый переданный процессу аргумент (в UNIX-системах он равен имени приложения, а в Android имя приложения равно имени его пакета). Способ работает на всех версиях Android до Q, где данный метод был внесен в черный список.
Everything you need to know about Memory Leaks in Android — большая статья об утечках памяти и о том, как их избежать. Автор приводит три типичных примера утечек.
Пример 1. Потоки
public class ThreadActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_async_task); new DownloadTask().start(); } private class DownloadTask extends Thread { @Override public void run() { SystemClock.sleep(2000 * 10); } } }
Данный код создает поток, который спит 20 секунд (в реальном приложении это может быть какая-либо длительная работа). Проблема здесь в том, что в Java объекты вложенных классов (в данном случае DownloadTask) хранят ссылку на внешний объект (Activity), поэтому, даже если пользователь закроет активность, сборщик мусора не сможет освободить занятую ей память до тех пор, пока метод run
не закончит свою работу и занятая объектом класса DownloadTask память не будет освобождена.
Пример 2. Синглтоны
public class SingletonManager { private static SingletonManager singleton; private Context context; private SingletonManager(Context context) { this.context = context; } public synchronized static SingletonManager getInstance(Context context) { if (singleton == null) { singleton = new SingletonManager(context); } return singleton; } }
В данном случае проблема кроется не в самом коде, а в способе его использования:
public class LoginActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { //... SingletonManager.getInstance(this); } }
В этом примере в качестве аргумента Context при создании синглтона передает this, то есть текущая активность. А это значит, что синглтон будет хранить ссылку на активность на протяжении всей жизни приложения. Чтобы избежать этой проблемы, вместо ссылки на активность следует использовать ссылку на контекст всего приложения:
SingletonManager.getInstance(getApplicationContext());
Пример 3. Листенеры
public class LoginActivity extends Activity implements LocationListener { @Override public void onLocationUpdated(Location location){ // Do something } @Override protected void onStart(){ LocationManager.getInstance().register(this); } @Override protected void onStop(){ LocationManager.getInstance().unregister(this); } }
Данный код регистрирует листенер для получения информации о текущем местоположении при старте активности и отключает его при корректном завершении. Однако, если система экстренно завершит работу активности, метод onStop
не будет вызван и LocationManager
продолжит хранить ссылку на активность.
Решить эту проблему можно так:
protected void onDestroy() { LocationManager.getInstance().unregister(this); super.onDestroy; }
Автор статьи создал проект avoid-memory-leak-android с демонстрацией этих и других типов утечек памяти, а также исправленным кодом. Для поиска утечек в своем приложении можно использовать LeakCanary.
Building an Android service that never stops running — статья о создании сервиса, который никогда не умирает. Автор приводит пример, когда необходимо заставить приложение выполнять какую-то работу в фоне с жестко заданной периодичностью.
Разработчики Android настоятельно рекомендуют использовать для этого JobScheduler (или более современный WorkManager), но его проблема в том, что энергосберегающие механизмы Android (Doze и App Standby) могут откладывать выполнение работы на неопределенный срок. Классические сервисы (Service) тоже не подходят для этой задачи, так как, кроме энергосберегающих механизмов, на их работу влияют ограничения Android 8 — сервис будет завершен вскоре после ухода приложения в фон.
Решение проблемы состоит в том, чтобы использовать Foreground Service, который будет получать частичный (partial) wakelock, чтобы не быть остановленным энергосберегающими механизмами. Полный код сервиса есть в оригинальной статье, а здесь приведем только самую мякотку — код, ответственный за получение вейклока и выполнение задачи. Запускать его следует из метода startCommand сервиса:
private fun startService() { if (isServiceStarted) return log("Starting the foreground service task") Toast.makeText(this, "Service starting its task", Toast.LENGTH_SHORT).show() isServiceStarted = true setServiceState(this, ServiceState.STARTED) // We need this lock, so our service gets not affected by Doze Mode wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run { newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EndlessService::lock").apply { acquire() } } // We’re starting a loop in a coroutine GlobalScope.launch(Dispatchers.IO) { while (isServiceStarted) { launch(Dispatchers.IO) { pingFakeServer() } delay(1 * 60 * 1000) } log("End of the loop for the service") } }
Keeping cool in Android Q with the Thermal API — краткая заметка о новом API Android Q, позволяющем получать информацию о текущем уровне нагрева устройства и о переходе в состояние тротлинга (когда система искусственно занижает производительность устройства).
Сам API крайне прост. Получить текущий уровень нагрева можно с помощью всего двух строк (Kotlin):
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager val currentStatus = powerManager.currentThermalStatus
Значение currentStatus будет одним из следующих:
THERMAL_STATUS_NONE = 0; THERMAL_STATUS_LIGHT = 1; THERMAL_STATUS_MODERATE = 2; THERMAL_STATUS_SEVERE = 3; THERMAL_STATUS_CRITICAL = 4; THERMAL_STATUS_EMERGENCY = 5; THERMAL_STATUS_SHUTDOWN = 6;
Значение 0 говорит о том, что все в полном порядке, 6 — критический уровень нагрева, за которым последует принудительное отключение устройства. Как разработчик можешь использовать приведенный код, чтобы проверить текущий уровень нагрева перед запуском интенсивных вычислительных операций. К примеру, если текущий уровень нагрева равен 3, то следует повременить с тяжелыми вычислениями либо вывести на экран предупреждающее сообщение.
API также позволяет следить за изменением уровня нагрева в реальном времени с помощью листенера с интерфейсом OnThermalStatusChangedListener:
powerManager.addThermalStatusListener { // Проверяем текущий статус }
Android debug tools — обзор нескольких инструментов отладки, включая встроенные в Android Studio отладчик и профайлер, уже несколько раз упоминавшийся нами инструмент Facebook Stetho, сервис AppSpector и библиотеку DebugDrawer.
В контексте этого дайджеста нам наиболее интересен DebugDrawer. В отличие от других инструментов, эта библиотека позволяет отлаживать и профилировать приложение прямо на устройстве с помощью выдвигаемой с одной из сторон приложения панели (drawer).
Подключить библиотеку к проекту очень просто:
debugImplementation 'io.palaima.debugdrawer:debugdrawer:0.8.0'
Далее в метод onCreate основной активности приложения добавляем такой код:
SwitchAction switchAction = new SwitchAction("Test switch", new SwitchAction.Listener() { @Override public void onCheckedChanged(boolean value) { Toast.makeText(MainActivity.this, "Switch checked", Toast.LENGTH_LONG).show(); } }); ButtonAction buttonAction = new ButtonAction("Test button", new ButtonAction.Listener() { @Override public void onClick() { Toast.makeText(MainActivity.this, "Button clicked", Toast.LENGTH_LONG).show(); } }); SpinnerAction < String > spinnerAction = new SpinnerAction < > ( Arrays.asList("First", "Second", "Third"), new SpinnerAction.OnItemSelectedListener < String > () { @Override public void onItemSelected(String value) { Toast.makeText(MainActivity.this, "Spinner item selected - " + value, Toast.LENGTH_LONG).show(); } } ); debugDrawer = new DebugDrawer.Builder(this).modules( new ActionsModule(switchAction, buttonAction, spinnerAction), new FpsModule(Takt.stock(getApplication())), new LocationModule(this), new ScalpelModule(this), new TimberModule(), new OkHttp3Module(okHttpClient), new PicassoModule(picasso), new GlideModule(Glide.get(getContext())), new DeviceModule(this), new BuildModule(this), new NetworkModule(this), new SettingsModule(this) ).build();
Возможности DebugDrawer хорошо видны в коде. Модули можно подключать и отключать независимо друг от друга. Геолокационный модуль умеет спуфить местоположение. Сетевой модуль позволяет вылавливать отдельные сетевые запросы с помощью библиотеки Chuck. Недостаток: нет модуля для инспекции баз данных.
Pair and Triple in Kotlin — статья о специальных классах Kotlin, позволяющих возвращать два или три значения из функции. Это может пригодиться, например, для возврата кода ошибки или в случаях, когда необходимо вернуть несколько значений разных типов.
Декларация Pair (два значения) выглядит так:
Pair ("Hello", "Kotlin") Pair ("Kotlin", 1) Pair (2, 20)
Получить значения первого и второго элемента можно следующим образом:
println(variableName.first) println(variableName.second)
Для создания Pair можно использовать инфиксную функцию to
:
fun getWebsite() : Pair<String, String> { return "www.mindorks.com" to "the Website is" }
А для получения значений — деструктивный оператор:
val (url: String, website: String) = getWebsite()
Pair можно превратить в строку с помощью метода toString()
:
val variableName = Pair (variable1, variable2) print(variableName.toString())
Или в список из двух элементов с помощью метода toList()
:
val variableName = Pair (variable1, variable2) val list = variableName.toList()
Класс Triple полностью аналогичен Pair за исключением того, что он может хранить три значения вместо двух:
val variable1 = "string1" val variable2 = 1 val variable3 = "string2" val variableName = Triple (variable1, variable2, variable3) println(variableName.first) println(variableName.second) println(variableName.third)
Preparing your app for Android Q — статья об изменениях в Android Q, к которым необходимо быть готовым.
setFullScreenIntent()
в Notification.Builder
).View.setSystemGestureExclusionRects()
.<style name="AppTheme" parent="Theme.AppCompat.DayNight">
. Также необходимо создать отдельные файлы ресурсов для ночной темы, в частности для цветов: values-night/colors.xml
.Читайте также
Последние новости