<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>SVolf 🇷🇺</title><author><name>SVolf 🇷🇺</name></author><id>https://teletype.in/atom/snowvolf</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/snowvolf?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@snowvolf?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=snowvolf"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/snowvolf?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-05-27T19:33:45.806Z</updated><entry><id>snowvolf:rkxRTtsqm</id><link rel="alternate" type="text/html" href="https://teletype.in/@snowvolf/rkxRTtsqm?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=snowvolf"></link><title>Импорт/экспорт данных приложения. Практика</title><published>2018-10-10T14:35:30.551Z</published><updated>2018-10-10T14:35:30.551Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/06/062c3317-9c25-456a-bb72-bb3dd3a353ba.jpeg"></media:thumbnail><summary type="html">Предыдущая часть: https://teletype.in/@snowvolf/BJeE3fK5X</summary><content type="html">
  &lt;p&gt;Предыдущая часть: https://teletype.in/@snowvolf/BJeE3fK5X&lt;/p&gt;
  &lt;p&gt;Создадим приложение. Имя пакета приложения, в моём случае, &lt;code&gt;ru.SnowVolf.backupfactory&lt;/code&gt;. Учтите это, при попытке воспроизвести мой код.&lt;/p&gt;
  &lt;p&gt;Откройте файл &lt;strong&gt;AndroidManifest.xml&lt;/strong&gt; и добавьте внутри тега &lt;code&gt;&amp;lt;manifest/&amp;gt;&lt;/code&gt;:&lt;/p&gt;
  &lt;pre&gt;&amp;lt;uses-permission android:name=&amp;quot;android.permission.WRITE_EXTERNAL_STORAGE&amp;quot;/&amp;gt;
&amp;lt;uses-permission android:name=&amp;quot;android.permission.READ_EXTERNAL_STORAGE&amp;quot;/&amp;gt;
&lt;/pre&gt;
  &lt;p&gt;(Разрешение на запись и чтение памяти соответственно)&lt;/p&gt;
  &lt;p&gt;Внутри тега &lt;code&gt;&amp;lt;application/&amp;gt;&lt;/code&gt; добавьте обьявление новой активности:&lt;/p&gt;
  &lt;pre&gt;&amp;lt;activity android:name=&amp;quot;.BackupActivity&amp;quot;/&amp;gt;
&lt;/pre&gt;
  &lt;p&gt;Проигнорируйте ошибку IDE, мы исправим её позже.&lt;/p&gt;
  &lt;p&gt;Полный листинг&lt;strong&gt; AndroidManifest.xml&lt;/strong&gt; приведён ниже:&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Листинг 1. AndroidManifest.xml&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;manifest xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;
  package=&amp;quot;ru.SnowVolf.backupfactory&amp;quot;&amp;gt;
  &amp;lt;uses-permission android:name=&amp;quot;android.permission.WRITE_EXTERNAL_STORAGE&amp;quot;/&amp;gt;
  &amp;lt;uses-permission android:name=&amp;quot;android.permission.READ_EXTERNAL_STORAGE&amp;quot;/&amp;gt;
  &amp;lt;application
    android:name=&amp;quot;.App&amp;quot;
    android:allowBackup=&amp;quot;true&amp;quot;
    android:icon=&amp;quot;@mipmap/ic_launcher&amp;quot;
    android:label=&amp;quot;@string/app_name&amp;quot;
    android:roundIcon=&amp;quot;@mipmap/ic_launcher_round&amp;quot;
    android:supportsRtl=&amp;quot;true&amp;quot;
    android:theme=&amp;quot;@style/AppTheme&amp;quot;&amp;gt;
    &amp;lt;activity android:name=&amp;quot;.BackupActivity&amp;quot;/&amp;gt;
    &amp;lt;activity android:name=&amp;quot;.MainActivity&amp;quot;&amp;gt;
      &amp;lt;intent-filter&amp;gt;
        &amp;lt;action android:name=&amp;quot;android.intent.action.MAIN&amp;quot; /&amp;gt;

        &amp;lt;category android:name=&amp;quot;android.intent.category.LAUNCHER&amp;quot; /&amp;gt;
      &amp;lt;/intent-filter&amp;gt;
    &amp;lt;/activity&amp;gt;
  &amp;lt;/application&amp;gt;

&amp;lt;/manifest&amp;gt;
&lt;/pre&gt;
  &lt;p&gt;Создайте класс &lt;strong&gt;App.java&lt;/strong&gt; и напишите следующий код:&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Листинг 2. Синглтон Application.&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;package ru.SnowVolf.backupfactory;


import android.app.Application;

public class App extends Application {
  private static App instance = null;
  private static SharedPreferences preferences;

  /**
   * Called when the application is starting, before any activity, service,
   * or receiver objects (excluding content providers) have been created.
   * Implementations should be as quick as possible (for example using
   * lazy initialization of state) since the time spent in this function
   * directly impacts the performance of starting the first activity,
   * service, or receiver in a process.
   * If you override this method, be sure to call super.onCreate().
   */
  @Override
  public void onCreate() {
    super.onCreate();
    instance = this;
  }

  public static App get(){
    if (instance == null){
      instance = new App();
    }
    return instance;
  }
}
&lt;/pre&gt;
  &lt;p&gt;Вернитесь к манифесту. Одна ошибка должна исчезнуть.&lt;/p&gt;
  &lt;p&gt;Перейдите в каталог ресурсов вашего проекта, и откройте файл разметки главной активности (в моём случае - &lt;strong&gt;activity_main.xml&lt;/strong&gt;) и сверстайте следующую разметку:&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Листинг 3. Разметка activity_main.xml.&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;RelativeLayout xmlns:tools=&amp;quot;http://schemas.android.com/tools&amp;quot;
  android:layout_width=&amp;quot;match_parent&amp;quot;
  android:layout_height=&amp;quot;match_parent&amp;quot;
  xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot; &amp;gt;

  &amp;lt;TextView
    android:id=&amp;quot;@+id/textView&amp;quot;
    android:layout_width=&amp;quot;wrap_content&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;
    android:layout_alignParentStart=&amp;quot;true&amp;quot;
    android:layout_alignParentTop=&amp;quot;true&amp;quot;
    android:layout_marginEnd=&amp;quot;16dp&amp;quot;
    android:layout_marginStart=&amp;quot;16dp&amp;quot;
    android:layout_marginTop=&amp;quot;8dp&amp;quot;
    android:text=&amp;quot;Settings test&amp;quot;
    android:textAppearance=&amp;quot;@android:style/TextAppearance.Material.Body2&amp;quot;/&amp;gt;

  &amp;lt;EditText
    android:id=&amp;quot;@+id/fieldName&amp;quot;
    android:layout_width=&amp;quot;match_parent&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;
    android:layout_below=&amp;quot;@+id/textView&amp;quot;
    android:layout_marginEnd=&amp;quot;16dp&amp;quot;
    android:layout_marginStart=&amp;quot;16dp&amp;quot;
    android:hint=&amp;quot;Your name&amp;quot;
    android:maxLines=&amp;quot;1&amp;quot; /&amp;gt;

  &amp;lt;Button
    android:id=&amp;quot;@+id/buttonGet&amp;quot;
    android:layout_width=&amp;quot;wrap_content&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;
    android:layout_alignEnd=&amp;quot;@+id/fieldName&amp;quot;
    android:layout_below=&amp;quot;@+id/fieldName&amp;quot;
    android:textAllCaps=&amp;quot;false&amp;quot;
    android:text=&amp;quot;Get&amp;quot; /&amp;gt;

  &amp;lt;Button
    android:id=&amp;quot;@+id/buttonPut&amp;quot;
    android:layout_width=&amp;quot;wrap_content&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;
    android:layout_below=&amp;quot;@+id/fieldName&amp;quot;
    android:layout_toStartOf=&amp;quot;@+id/buttonGet&amp;quot;
    android:textAllCaps=&amp;quot;false&amp;quot;
    android:text=&amp;quot;Set&amp;quot; /&amp;gt;

  &amp;lt;TextView
    android:id=&amp;quot;@+id/textView2&amp;quot;
    android:layout_width=&amp;quot;wrap_content&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;
    android:layout_alignStart=&amp;quot;@+id/fieldName&amp;quot;
    android:layout_below=&amp;quot;@+id/buttonPut&amp;quot;
    android:text=&amp;quot;Database Test&amp;quot;
    android:textAppearance=&amp;quot;@android:style/TextAppearance.Material.Body2&amp;quot; /&amp;gt;

  &amp;lt;TextView
    android:id=&amp;quot;@+id/txtDB&amp;quot;
    android:layout_width=&amp;quot;wrap_content&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;
    android:layout_below=&amp;quot;@+id/textView2&amp;quot;
    android:layout_marginEnd=&amp;quot;16dp&amp;quot;
    android:layout_marginStart=&amp;quot;16dp&amp;quot;
    android:layout_marginTop=&amp;quot;8dp&amp;quot; /&amp;gt;

  &amp;lt;EditText
    android:id=&amp;quot;@+id/fieldInsertCount&amp;quot;
    android:layout_width=&amp;quot;wrap_content&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;
    android:layout_alignStart=&amp;quot;@+id/txtDB&amp;quot;
    android:layout_below=&amp;quot;@+id/txtDB&amp;quot;
    android:layout_toStartOf=&amp;quot;@+id/btnInsert&amp;quot;
    android:maxEms=&amp;quot;2&amp;quot;
    android:hint=&amp;quot;Insert count&amp;quot;
    android:inputType=&amp;quot;phone&amp;quot;
    android:maxLines=&amp;quot;1&amp;quot; /&amp;gt;

  &amp;lt;Button
    android:id=&amp;quot;@+id/btnInsert&amp;quot;
    android:layout_width=&amp;quot;wrap_content&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;
    android:layout_alignBottom=&amp;quot;@+id/fieldInsertCount&amp;quot;
    android:layout_alignEnd=&amp;quot;@+id/buttonGet&amp;quot;
    android:text=&amp;quot;Insert&amp;quot;
    android:textAllCaps=&amp;quot;false&amp;quot;/&amp;gt;

&amp;lt;/RelativeLayout&amp;gt;
&lt;/pre&gt;
  &lt;p&gt;Создайте новый класс &lt;strong&gt;BackupFactory.java&lt;/strong&gt; и поместите туда код из прошлой статьи. Я продублировал его здесь для удобства:&lt;/p&gt;
  &lt;pre&gt;/*
 * Copyright (c) 2017 Deletescape.
 * Copyright (c) 2018 Snow Volf (Artem Zhiganov).
 * Licensed under the Apache License, Version 2.0 (the &amp;quot;License&amp;quot;);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &amp;quot;AS IS&amp;quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ru.SnowVolf.backupfactory;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;


/**
 * Created by Snow Volf on 05.08.2018, 12:12
 *
 * Класс экспорта/импорта БД и настроек
 *
 * Основано на: https://github.com/Deletescape/Lawnchair/app/src/main/java/ch/deletescape/lawnchair/DumbImportExportTask.java
 */

public class BackupFactory {
  // Экспорт БД
  public static void exportDb(Activity activity, String dbFiles) {
    ContextWrapper contextWrapper = new ContextWrapper(activity);
    String[] dbs = dbFiles.split(&amp;quot;,&amp;quot;);
    for (String db : dbs) {
      File expDb = contextWrapper.getDatabasePath(db);
      exportFile(expDb, activity);
    }
  }

  //Импорт БД
  public static void importDb(Activity activity, String dbFiles) {
    ContextWrapper contextWrapper = new ContextWrapper(activity);
    String[] dbs = dbFiles.split(&amp;quot;,&amp;quot;);
    for (String db : dbs) {
      File expDb = contextWrapper.getDatabasePath(db);
      importFile(expDb, activity);
    }
  }

  // Экспорт файла настроек
  public static void exportPrefs(Activity activity) {
    ApplicationInfo info = activity.getApplicationInfo();
    String dir = new ContextWrapper(activity).getCacheDir().getParent();
    File prefs = new File(dir, &amp;quot;shared_prefs/&amp;quot; + info.packageName + &amp;quot;_preferences.xml&amp;quot;);
    exportFile(prefs, activity);
  }

  // Импорт файла настроек
  public static void importPrefs(Activity activity) {
    ApplicationInfo info = activity.getApplicationInfo();
    String dir = new ContextWrapper(activity).getCacheDir().getParent();
    File prefs = new File(dir, &amp;quot;shared_prefs/&amp;quot; + info.packageName + &amp;quot;_preferences.xml&amp;quot;);

    importFile(prefs, activity);
  }

  // Экспорт файла
  private static void exportFile(File file, Activity activity) {
    // Если нет разрешения на запись
    if (!isExternalStorageWritable() || !canWriteStorage(activity)) {
      Toast.makeText(activity, &amp;quot;No write read/permission!&amp;quot;, Toast.LENGTH_SHORT).show();
      return;
    }
    File backup = new File(getFolder(), file.getName());
    if (backup.exists()) {
      backup.delete();
    }
    if (copy(file, backup)) {
      Toast.makeText(activity, &amp;quot;Successful!&amp;quot;, Toast.LENGTH_SHORT).show();
    } else {
      Toast.makeText(activity, &amp;quot;Error!&amp;quot;, Toast.LENGTH_SHORT).show();
    }
  }

  // Импорт файла
  private static void importFile(File file, Activity activity) {
    // Если нет разрешения на запись
    if (!isExternalStorageReadable() || !canWriteStorage(activity)) {
      Toast.makeText(activity, &amp;quot;No write read/permission!&amp;quot;, Toast.LENGTH_SHORT).show();
      return;
    }
    File backup = new File(getFolder(), file.getName());
    if (!backup.exists()) {
      Toast.makeText(activity, String.format(&amp;quot;Backups not found in %s&amp;quot;, getFolder().getAbsolutePath()), Toast.LENGTH_LONG).show();
      return;
    }
    if (file.exists()) {
      file.delete();
    }
    if (copy(backup, file)) {
      Toast.makeText(activity, &amp;quot;Successful!&amp;quot;, Toast.LENGTH_SHORT).show();
    } else {
      Toast.makeText(activity, &amp;quot;Error!&amp;quot;, Toast.LENGTH_SHORT).show();
    }
  }

  // Получение пути к папке бэкапов
  // [SDCARD]/Documents/backup/

  private static File getFolder() {
    File folder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), &amp;quot;backup/&amp;quot;);
    if (!folder.exists()) {
      folder.mkdirs();
    }
    return folder;
  }

  // Проверка на возможность записи
  private static boolean canWriteStorage(Activity activity) {
    return Build.VERSION.SDK_INT &amp;lt; Build.VERSION_CODES.M ||
        activity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            == PackageManager.PERMISSION_GRANTED;
  }

  // Копирование файлов тудым-сюдым
  private static boolean copy(File inFile, File outFile) {
    FileInputStream in;
    FileOutputStream out;
    try {
      in = new FileInputStream(inFile);
      out = new FileOutputStream(outFile);
      byte[] buffer = new byte[1024];
      int read;
      while ((read = in.read(buffer)) != -1) {
        out.write(buffer, 0, read);
      }
      in.close();

      // write the output file
      out.flush();
      out.close();
      return true;
    } catch (Exception e) {
      Log.e(&amp;quot;BackupFactory&amp;quot;, e.getMessage());
      e.printStackTrace();
    }
    return false;
  }

  /* Checks if external storage is available for read and write */
  private static boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    return Environment.MEDIA_MOUNTED.equals(state);
  }

  /* Checks if external storage is available to at least read */
  private static boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    return Environment.MEDIA_MOUNTED.equals(state) ||
 Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
  }
}
&lt;/pre&gt;
  &lt;p&gt;Создадим класс &lt;strong&gt;BackupActivity.java&lt;/strong&gt; и напишем следующий код:&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Листинг 4. Активность восстановления.&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;package ru.SnowVolf.backupfactory;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Process;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;

public class BackupActivity extends Activity {
  private Activity mActivity;
  private String namesOfDb = &amp;quot;test.db&amp;quot;;
  private String[] options = {&amp;quot;Import databases&amp;quot;, &amp;quot;Export databases&amp;quot;, &amp;quot;Import preferences&amp;quot;,
 &amp;quot;Export preferences&amp;quot;, &amp;quot;Apply changes and restart&amp;quot;};


  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
		mActivity = this;
    setContentView(createView());
  }

  private View createView(){
    LinearLayout content = new LinearLayout(this);
    LinearLayout.LayoutParams paramsMatch = new LinearLayout.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
        );

    content.setLayoutParams(paramsMatch);
		
		return content;
	}
	
	@Override
  protected void onDestroy() {
    mActivity = null;
    namesOfDb = null;
    options = null;
    super.onDestroy();
  }
}
&lt;/pre&gt;
  &lt;p&gt;Разметка для этого экрана будет создана полностью из кода (для более удобного инжекта в другие программы при помощи smali). На данный момент мы создали только корневое представление View, т.е. пустой экран.&lt;/p&gt;
  &lt;p&gt;Заполним его списком с прикреплёнными действиями.&lt;/p&gt;
  &lt;p&gt;Допишем следующий код в метод &lt;code&gt;createView()&lt;/code&gt;:&lt;/p&gt;
  &lt;pre&gt;		// Создаём список
		ListView list = new ListView(this);
    list.setLayoutParams(paramsMatch);
		// Создаём адаптер для списка. В качестве разметки используем встроенный в framework ресурс.
		// В качестве заголовков для пунктов используем элементы массива options.
    ArrayAdapter&amp;lt;String&amp;gt; adapter = new ArrayAdapter&amp;lt;&amp;gt;(this, android.R.layout.simple_list_item_1, options);
		// Присваиваем адаптер
    list.setAdapter(adapter);
		// Присваиваем реакцию на нажатия в зависимости от позиции в списке
    list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView&amp;lt;?&amp;gt; parent, View view, int position, long id) {
        switch (position){
          case 0:
            BackupFactory.importDb(mActivity, namesOfDb);
            break;
          case 1:
            BackupFactory.exportDb(mActivity, namesOfDb);
            break;
          case 2:
            BackupFactory.importPrefs(mActivity);
            break;
          case 3:
            BackupFactory.exportPrefs(mActivity);
            break;
          case 4:
            restartApp();
            break;
          default:
            break;
        }
      }
    });

    content.addView(list);
&lt;/pre&gt;
  &lt;p&gt;В итоге, ваш метод &lt;code&gt;createView()&lt;/code&gt; должен выглядеть так:&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Листинг 5. Метод createView().&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;private View createView(){
    LinearLayout content = new LinearLayout(this);
    LinearLayout.LayoutParams paramsMatch = new LinearLayout.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
        );
    ListView list = new ListView(this);
    list.setLayoutParams(paramsMatch);
    ArrayAdapter&amp;lt;String&amp;gt; adapter = new ArrayAdapter&amp;lt;&amp;gt;(this, android.R.layout.simple_list_item_1, options);
    list.setAdapter(adapter);
    list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView&amp;lt;?&amp;gt; parent, View view, int position, long id) {
        switch (position){
          case 0:
            BackupFactory.importDb(mActivity, namesOfDb);
            break;
          case 1:
            BackupFactory.exportDb(mActivity, namesOfDb);
            break;
          case 2:
            BackupFactory.importPrefs(mActivity);
            break;
          case 3:
            BackupFactory.exportPrefs(mActivity);
            break;
          case 4:
            restartApp();
            break;
          default:
            break;
        }
      }
    });

    content.addView(list);

    return content;
  }
&lt;/pre&gt;
  &lt;p&gt;Особо наблюдательные могли заметить отсутствие метода &lt;code&gt;restartApp()&lt;/code&gt;. Давайте напишем его.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Листинг 6. Метод restartApp().&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;private void restartApp(){
    Intent mStartActivity = new Intent(this, MainActivity.class);
    mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    int mIntentPendingId = 6;
    PendingIntent mPendingIntent = PendingIntent.getActivity(this, mIntentPendingId,
 mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
    AlarmManager manager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
    manager.set(AlarmManager.RTC, System.currentTimeMillis() + 300, mPendingIntent);
    Process.killProcess(Process.myPid());
    System.exit(0);
  }
&lt;/pre&gt;
  &lt;p&gt;Полный листинг активности приведён ниже:&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Листинг 7. Класс BackupActivity.java&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;package ru.SnowVolf.backupfactory;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Process;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;

public class BackupActivity extends Activity {
  private Activity mActivity;
  private String namesOfDb = &amp;quot;test.db&amp;quot;;
  private String[] options = {&amp;quot;Import databases&amp;quot;, &amp;quot;Export databases&amp;quot;, &amp;quot;Import preferences&amp;quot;,
 &amp;quot;Export preferences&amp;quot;, &amp;quot;Apply changes then restart&amp;quot;};

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mActivity = this;
    setContentView(createView());
  }

  private View createView(){
    LinearLayout content = new LinearLayout(this);
    LinearLayout.LayoutParams paramsMatch = new LinearLayout.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
        );
    ListView list = new ListView(this);
    list.setLayoutParams(paramsMatch);
    ArrayAdapter&amp;lt;String&amp;gt; adapter = new ArrayAdapter&amp;lt;&amp;gt;(this, android.R.layout.simple_list_item_1, options);
    list.setAdapter(adapter);
    list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView&amp;lt;?&amp;gt; parent, View view, int position, long id) {
        switch (position){
          case 0:
            BackupFactory.importDb(mActivity, namesOfDb);
            break;
          case 1:
            BackupFactory.exportDb(mActivity, namesOfDb);
            break;
          case 2:
            BackupFactory.importPrefs(mActivity);
            break;
          case 3:
            BackupFactory.exportPrefs(mActivity);
            break;
          case 4:
            restartApp();
            break;
          default:
            break;
        }
      }
    });

    content.addView(list);

    return content;
  }

  private void restartApp(){
    Intent mStartActivity = new Intent(this, MainActivity.class);
    mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    int mIntentPendingId = 6;
    PendingIntent mPendingIntent = PendingIntent.getActivity(this, mIntentPendingId,
 mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
    AlarmManager manager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
    manager.set(AlarmManager.RTC, System.currentTimeMillis() + 300, mPendingIntent);
    Process.killProcess(Process.myPid());
    System.exit(0);
  }
	
	@Override
  protected void onDestroy() {
    mActivity = null;
    namesOfDb = null;
    options = null;
    super.onDestroy();
  }
}
&lt;/pre&gt;
  &lt;p&gt;Всё. С этим закончили. Код можно свободно инжектить в любую программу.&lt;/p&gt;
  &lt;h2&gt;Создадим пример&lt;/h2&gt;
  &lt;p&gt;Вернитесь к &lt;strong&gt;MainActivity.java&lt;/strong&gt; и напишите такой код:&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Листинг 8. Основная активность&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;package ru.SnowVolf.backupfactory;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.util.Locale;

public class MainActivity extends Activity {
  private TextView txtDb;
  private EditText mPrefValue, mNumOfItems;
  private Button btnGetPref, btnSetPref, btnAddItems;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    txtDb = findViewById(R.id.txtDB);

    mPrefValue = findViewById(R.id.fieldName);
    mNumOfItems = findViewById(R.id.fieldInsertCount);

    btnAddItems = findViewById(R.id.btnInsert);
    btnGetPref = findViewById(R.id.buttonGet);
    btnSetPref = findViewById(R.id.buttonPut);

    final TestDB db = new TestDB(getApplicationContext());

    txtDb.setText(String.format(Locale.ENGLISH, &amp;quot;Number of items in abstract DB: %d&amp;quot;, db.getAllItems().size()));

    btnAddItems.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        int count = Integer.parseInt(mNumOfItems.getText().toString());
        if ( count &amp;gt; 0 &amp;amp;&amp;amp; count &amp;lt;= 99){

          for (int i = 0; i &amp;lt; count; i++) {
            db.addItem(new TestDbItem(System.currentTimeMillis()));
          }
        } else {
          Toast.makeText(MainActivity.this, &amp;quot;Very big number&amp;quot;, Toast.LENGTH_SHORT).show();
        }
      }
    });
    btnSetPref.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        App.get().getPreferences().edit().putString(&amp;quot;name&amp;quot;, mPrefValue.getText().toString()).apply();
      }
    });

    btnGetPref.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        mPrefValue.setText(App.get().getPreferences().getString(&amp;quot;name&amp;quot;, &amp;quot;empty&amp;quot;));
      }
    });
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();
    if (id == R.id.action_settings){
      startActivity(new Intent(this, BackupActivity.class));
      return true;
    }
    return super.onOptionsItemSelected(item);
  }
}
&lt;/pre&gt;
  &lt;p&gt;Активность ссылается на 2 несущесуществующих класса. Создадим их.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Листинг 9. TestDbItem.java&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;/*
 * Copyright (c) 2017 Snow Volf (Artem Zhiganov).
 * Licensed under the Apache License, Version 2.0 (the &amp;quot;License&amp;quot;);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &amp;quot;AS IS&amp;quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ru.SnowVolf.backupfactory;

/**
 * Created by Snow Volf on 04.06.2017, 22:44
 */

public class TestDbItem {
  private long id = -1L;

  public TestDbItem(long id) {
    this.id = id;
  }

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }
}
&lt;/pre&gt;
  &lt;p&gt;Последнее - класс для работы с базой данных SQLiteDatabase&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Листинг 9. TestDB.java&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;/*
 * Copyright (c) 2017 Snow Volf (Artem Zhiganov).
 * Licensed under the Apache License, Version 2.0 (the &amp;quot;License&amp;quot;);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an &amp;quot;AS IS&amp;quot; BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package ru.SnowVolf.backupfactory;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Snow Volf on 04.06.2017, 22:52
 */

public class TestDB extends SQLiteOpenHelper {

  public TestDB(Context context) {
    super(context, &amp;quot;test.db&amp;quot;, null, 1);
  }

  @Override
  public void onCreate(SQLiteDatabase db) {
    db.execSQL(&amp;quot;CREATE TABLE test_db (id INTEGER)&amp;quot;);
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    // Update this when db table will be changed
  }

  public void addItem(TestDbItem item) {
    if (item.getId() == -1) {
      item.setId(System.currentTimeMillis());
    }

    ContentValues values = new ContentValues();
    values.put(&amp;quot;id&amp;quot;, System.currentTimeMillis());

    SQLiteDatabase db = getWritableDatabase();
    db.insert(&amp;quot;test_db&amp;quot;, null, values);
    db.close();
  }

  public List&amp;lt;TestDbItem&amp;gt; getAllItems() {
    List&amp;lt;TestDbItem&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
    SQLiteDatabase db = getReadableDatabase();
    Cursor cursor = db.rawQuery(&amp;quot;SELECT * FROM &amp;quot; + &amp;quot;test_db&amp;quot;, null);

    if (cursor.moveToFirst()) {
      do {
        list.add(new TestDbItem((cursor.getLong(0))));
      } while (cursor.moveToNext());
    }
    cursor.close();
    db.close();
    return list;
  }

  public void deleteAll() {
    SQLiteDatabase db = getWritableDatabase();
    db.delete(&amp;quot;test_db&amp;quot;, &amp;quot;id&amp;quot; + &amp;quot;&amp;gt;=?&amp;quot;, new String[]{&amp;quot;0&amp;quot;});
    db.close();
  }
}
&lt;/pre&gt;
  &lt;p&gt;Проверьте код на ошибки, их не должно быть. Скомпилируйте приложение.&lt;/p&gt;

</content></entry><entry><id>snowvolf:BJeE3fK5X</id><link rel="alternate" type="text/html" href="https://teletype.in/@snowvolf/BJeE3fK5X?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=snowvolf"></link><title>Импорт и экспорт данных приложения. Теория.</title><published>2018-10-08T18:04:23.552Z</published><updated>2018-10-08T18:04:23.552Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/94/94e87190-36bf-4b0c-8a84-75f309b44c0a.png"></media:thumbnail><summary type="html">@VolfsChannel</summary><content type="html">
  &lt;p&gt;&lt;a href=&quot;http://t.me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;@VolfsChannel&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;Порой нам бывает необходимо восстановить некоторые настройки приложения. Это просто делается вручную, но что если в приложении десятки, а то и сотни независимых настроек? На многих устройствах нет рута, с adb не каждый разберется. Так что же делать? Сейчас узнаем.&lt;/p&gt;
  &lt;p&gt;Данный код будет работать при 2-х условиях:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Наличие разрешения на запись&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre&gt;&amp;lt;uses-permission android:name=&amp;quot;android.permission.WRITE_EXTERNAL_STORAGE&amp;quot;/&amp;gt;
&amp;lt;uses-permission android:name=&amp;quot;android.permission.READ_EXTERNAL_STORAGE&amp;quot;/&amp;gt;

&lt;/pre&gt;
  &lt;ul&gt;
    &lt;li&gt;В настройках системы тоже должен быть предоставлен доступ&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Создадим класс &lt;code&gt;BackupFactory.java:&lt;/code&gt;&lt;/p&gt;
  &lt;pre&gt;import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;


public class BackupFactory {
    // Экспорт БД
    public static void exportDb(Activity activity, String dbFiles) {
        ContextWrapper contextWrapper = new ContextWrapper(activity);
		// Преобразование строки в массив
        String[] dbs = dbFiles.split(&amp;quot;,&amp;quot;);
		// Экспорт каждого отдельного файла
        for (String db : dbs) {
            File expDb = contextWrapper.getDatabasePath(db);
            exportFile(expDb, activity);
        }
    }

    //Импорт БД
    public static void importDb(Activity activity,  String dbFiles) {
        ContextWrapper contextWrapper = new ContextWrapper(activity);
		// Преобразование строки в массив
        String[] dbs = dbFiles.split(&amp;quot;,&amp;quot;);
		// Импорт каждого отдельного файла
        for (String db : dbs) {
            File expDb = contextWrapper.getDatabasePath(db);
            importFile(expDb, activity);
        }
    }

    // Экспорт файла настроек
    public static void exportPrefs(Activity activity) {
        ApplicationInfo info = activity.getApplicationInfo();
        String dir = new ContextWrapper(activity).getCacheDir().getParent();
        File prefs = new File(dir, &amp;quot;shared_prefs/&amp;quot; + info.packageName + &amp;quot;_preferences.xml&amp;quot;);
        exportFile(prefs, activity);
    }

    // Импорт файла настроек
    public static void importPrefs(Activity activity) {
        ApplicationInfo info = activity.getApplicationInfo();
        String dir = new ContextWrapper(activity).getCacheDir().getParent();
        File prefs = new File(dir, &amp;quot;shared_prefs/&amp;quot; + info.packageName + &amp;quot;_preferences.xml&amp;quot;);
        importFile(prefs, activity);
    }

    // Экспорт файла
    private static void exportFile(File file, Activity activity) {
        // Если нет разрешения на запись
        if (!isExternalStorageWritable() || !canWriteStorage(activity)) {
            Toast.makeText(activity, &amp;quot;Нет разрешения на чтение/запись!&amp;quot;, Toast.LENGTH_SHORT).show();
            return;
        }
        File backup = new File(getFolder(), file.getName());
		// Перезаписываем существующий файл
        if (backup.exists()) {
            backup.delete();
        }
        if (copy(file, backup)) {
            Toast.makeText(activity, &amp;quot;Успешно!&amp;quot;, Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(activity, &amp;quot;Ошибка!&amp;quot;, Toast.LENGTH_SHORT).show();
        }
    }

    // Импорт файла
    private static void importFile(File file, Activity activity) {
        // Если нет разрешения на запись
        if (!isExternalStorageReadable() || !canWriteStorage(activity)) {
            Toast.makeText(activity, &amp;quot;Нет разрешения на чтение/запись!&amp;quot;, Toast.LENGTH_SHORT).show();
            return;
        }
        File backup = new File(getFolder(), file.getName());
        if (!backup.exists()) {
            Toast.makeText(activity, &amp;quot;Файлы бэкапа отсутствуют!&amp;quot;, Toast.LENGTH_LONG).show();
            return;
        }
        if (file.exists()) {
            file.delete();
        }
		// Всё хорошо
        if (copy(backup, file)) {
            Toast.makeText(activity, &amp;quot;Успешно!&amp;quot;, Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(activity, &amp;quot;Ошибка!&amp;quot;, Toast.LENGTH_SHORT).show();
        }
    }

    // Получение пути к папке бэкапов
    // [SDCARD]/Documents/backup/

    private static File getFolder() {
        File folder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), &amp;quot;backup&amp;quot;);
        if (!folder.exists()) {
            folder.mkdirs();
        }
        return folder;
    }

    // Проверка на возможность записи
    private static boolean canWriteStorage(Activity activity) {
        return Build.VERSION.SDK_INT &amp;lt; Build.VERSION_CODES.M ||
                activity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        == PackageManager.PERMISSION_GRANTED;
    }

    // Копирование файлов тудым-сюдым
    private static boolean copy(File inFile, File outFile) {
        FileInputStream in;
        FileOutputStream out;
        try {
            in = new FileInputStream(inFile);
            out = new FileOutputStream(outFile);
            byte[] buffer = new byte[1024];
            int read;
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
            in.close();

            // write the output file
            out.flush();
            out.close();
            return true;
        } catch (Exception e) {
            Log.e(&amp;quot;BackupFactory&amp;quot;, e.getMessage());
        }
        return false;
    }

    /* Checks if external storage is available for read and write */
    private static boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        return Environment.MEDIA_MOUNTED.equals(state);
    }

    /* Checks if external storage is available to at least read */
    private static boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        return Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
    }
	
&lt;/pre&gt;
  &lt;p&gt;Я добавил комментарии к коду. Основных метода 4 - импорт/экспорт БД, импорт/экспорт настроек. Файлов БД может быть несколько, указывать их надо через запятую &lt;code&gt;&amp;quot;test.db,sec.db&amp;quot;&lt;/code&gt; etc.&lt;/p&gt;
  &lt;p&gt;Теперь попробуем написать...[Продолжение следует]&lt;/p&gt;

</content></entry><entry><id>snowvolf:B1NXMSW5Q</id><link rel="alternate" type="text/html" href="https://teletype.in/@snowvolf/B1NXMSW5Q?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=snowvolf"></link><title>С чего начать изучение smali?</title><published>2018-10-02T19:08:11.522Z</published><updated>2018-10-02T19:10:36.143Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/8c/8c6ac84d-2dbc-496a-a785-efdade929502.png"></media:thumbnail><summary type="html">@VolfsChannel</summary><content type="html">
  &lt;p&gt;&lt;a href=&quot;http://t.me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;@VolfsChanne&lt;/a&gt;l&lt;/p&gt;
  &lt;p&gt;Я часто слышу что-то типа:&lt;/p&gt;
  &lt;blockquote&gt;Я нуб, как мне познакомиться со smali?&lt;/blockquote&gt;
  &lt;p&gt;Общих инструкций не существует. Я лишь могу подтолкнуть в верном направлении.&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Желательно чтобы вы уже знали какой-либо язык программирования или имели дело с Web разработкой. Имея необходимую усидчивость и навык поиска информации, вы сможете легко пойти по пути модификации.&lt;/li&gt;
    &lt;li&gt;Сравнивайте моды. Берите оригинал и мод, Декомпилируйте и сравните через &lt;a href=&quot;https://www.google.com/url?sa=t&amp;source=web&amp;rct=j&amp;url=https://www.scootersoftware.com/download.php&amp;ved=2ahUKEwjInf36uejdAhWmp4sKHZ2aAXEQFjAAegQIBBAD&amp;usg=AOvVaw3AnJcSlT29pPt_ALRqnLrn&quot; target=&quot;_blank&quot;&gt;Beyound Compare&lt;/a&gt; для Windows.&lt;/li&gt;
    &lt;li&gt;Читайте мануалы. Большое количество мануалов находится в открытом доступе на форуме 4PDA. Если вы не знаете русский - используйте Google Translate&lt;/li&gt;
    &lt;li&gt;Не прыгайте выше головы. Сперва научитесь изменять простые (boolean) значения. Станьте спецом в const/4 v0, 0x1.&lt;/li&gt;
    &lt;li&gt;Используйте автоматические патчи, только когда научитесь делать то же самое вручную. Помните: патчи - это лишь автоматизация рутинной работы. Сравнивайте оригинал и патченный Apk, и поймите что сделал патч в каждом конкретном случае.&lt;/li&gt;
    &lt;li&gt;Google - ваш лучший друг. При возникновении вопросов используйте Google. Очень маловероятно, что вы первый кто столкнулся с проблемой. Используйте информацию во благо.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;hr /&gt;
  &lt;h2&gt;Полезные ссылки&lt;/h2&gt;
  &lt;p&gt;&lt;strong&gt;Комплект инструкций на 4PDA:&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;The biggest Mod Apk Glossary (use google translate if don&amp;#x27;t understand)&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;https://4pda.ru/forum/index.php?s=&amp;showtopic=461675&amp;view=findpost&amp;p=44979438&quot; target=&quot;_blank&quot;&gt;https://4pda.ru/forum/index.php?s=&amp;amp;showtopic=461675&amp;amp;view=findpost&amp;amp;p=44979438&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Another&lt;/strong&gt;:&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;https://4pda.ru/forum/index.php?s=&amp;showtopic=461675&amp;view=findpost&amp;p=21970396&quot; target=&quot;_blank&quot;&gt;https://4pda.ru/forum/index.php?s=&amp;amp;showtopic=461675&amp;amp;view=findpost&amp;amp;p=21970396&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;XDA Apk Modding:&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;https://forum.xda-developers.com/showthread.php?t=2295002&quot; target=&quot;_blank&quot;&gt;https://forum.xda-developers.com/showthread.php?t=2295002&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;and another link:&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;https://forum.xda-developers.com/showthread.php?t=1624757&quot; target=&quot;_blank&quot;&gt;https://forum.xda-developers.com/showthread.php?t=1624757&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Other links&lt;/strong&gt;: &lt;a href=&quot;https://hackerbot.net/tutorials/330-mod-apk&quot; target=&quot;_blank&quot;&gt;https://hackerbot.net/tutorials/330-mod-apk&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>snowvolf:rkACyQKYm</id><link rel="alternate" type="text/html" href="https://teletype.in/@snowvolf/rkACyQKYm?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=snowvolf"></link><title>shell команды на Android за ~100 строк кода</title><published>2018-09-26T15:13:29.559Z</published><updated>2018-09-26T15:22:21.794Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/ae/ae160e66-83ad-4014-ba2c-80a1aea5de29.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://teletype.in/files/b6/b67bfebf-642c-45ee-9069-2bd94f96e0ab.png&quot;&gt;Мой канал: @VolfsChannel</summary><content type="html">
  &lt;p&gt;Мой канал: &lt;a href=&quot;http://t.me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;@VolfsChannel&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;Сегодня мы попробуем создать мини-приложение терминал, рассмотрим основные методы работы с shell-командами на Android.&lt;/p&gt;
  &lt;p&gt;Android основан на ядре Linux. Также как и на Linux в нем можно выполнять команды через терминал. Терминал в Android по своей сути просто является нескучной обоиной для выполнения функций рантайма. Чтож, заглянем глубже. Создадим простейшее приложение которое будет отдавать shell-команды и получать результат в виде строки. Ко всему прочему наше приложение сможет использовать Root.&lt;/p&gt;
  &lt;h2&gt;100 строчек кода...&lt;/h2&gt;
  &lt;p&gt;Создадим новый класс, я назвал его CommandExecutor.java и напишите следующий код:&lt;/p&gt;
  &lt;pre&gt;import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

public class CommandExecutor {

		// Модель для получения результата
		public static class ResultData {
				// Код результата
				private int resultCode;
				// Результат отработки команды
				private String resultData;
				// Поле для записи ошибок
				private String resultError;

				// Конструктор
				ResultData(int code, String info, String err) {
						resultCode = code;
						resultData = info == null ? &amp;quot;&amp;quot; : info;
						resultError = err == null ? &amp;quot;&amp;quot; : err;
					}
				// Геттеры полей
				public int getResultCode() {
						return resultCode;
					}
				public String getResultData() {
						return resultData;
					}
				public String getResultError() {
						return resultError;
					}
				public String getResult() {
						return toString();
					}
				public String toString() {
						return resultData + &amp;quot;\n&amp;quot; + resultError;
					}
			}
		// Выполнение простой sh-команды
		public static ResultData execute(String command) {
				return execute(false, command);
			}
		// То же что и выше, но из под рута
		public static ResultData executeSu(String command) {
				return execute(true, command);
			}
		// Если нужно изменять значение su динамически
		public static ResultData execute(boolean su, String command) {
				return execPool(su, rmSlashN(command).split(&amp;quot;\n&amp;quot;));
			}
		// Выполняем массив команд
		private static ResultData execPool(boolean su, String[] commands) {
				// Обьявление переменных
				Process exec = null;
				InputStream execIn = null;
				InputStream execErr = null;
				OutputStream execOs = null;

				ResultData resultData;
				try {
						// В зависимости от значения su меняем параметр запуска команды
						// От рута или же от юзера
						exec = Runtime.getRuntime().exec(su ? &amp;quot;su&amp;quot; : &amp;quot;sh&amp;quot;);
						execIn = exec.getInputStream();
						execErr = exec.getErrorStream();
						execOs = exec.getOutputStream();
						DataOutputStream dos = new DataOutputStream(execOs);
						// В цикле проходимся по всем отосланным командам
						for (String com : commands)
							if (!com.isEmpty()) {
									dos.writeBytes(com + &amp;quot;\n&amp;quot;);
									dos.flush();
								}
						dos.close();
						// Посылаем команду и ждём ответа от оболочки
						resultData = new ResultData(exec.waitFor(), inputStream2String(execIn, &amp;quot;utf-8&amp;quot;), inputStream2String(execErr, &amp;quot;utf-8&amp;quot;));
					} catch (Exception e) {
						resultData = new ResultData(-1, &amp;quot;&amp;quot;, e.toString());
					} finally {
						try {
								// Не забываем закрыть все стримы
								if (execIn != null) execIn.close();
								if (execErr != null) execErr.close();
								if (execOs != null) execOs.close();
								// Завершаем процесс выполнения
								if (exec != null) exec.destroy();
							} catch (Exception ignored) {}
					}
				return resultData;
			}

		// Преобразование стрима в строку
		public static String inputStream2String(InputStream in, String encoding) throws Exception {
				StringBuilder out = new StringBuilder();
				InputStreamReader inread = new InputStreamReader(in, encoding);
				char[] b = new char[1024];
				int n;
				while ((n = inread.read(b)) != -1) {
						String s = new String(b, 0, n);
						out.append(s);
					}
				return out.toString();
			}

		// Чтение стрима в строку
		public static String read(InputStream is) {
				try {
						ByteArrayOutputStream baos = new ByteArrayOutputStream();
						byte[] buffer = new byte[1024];
						int length;
						while ((length = is.read(buffer)) != -1)
							baos.write(buffer, 0, length);
						return baos.toString(&amp;quot;UTF-8&amp;quot;);
					} catch (Exception e) { e.printStackTrace(); return e.toString(); }
			}

		// Удаляем лишние переносы строки
		public static String rmSlashN(String text) {
				while (text.contains(&amp;quot;\n\n&amp;quot;)) text = text.replace(&amp;quot;\n\n&amp;quot;, &amp;quot;\n&amp;quot;);
				if (text.startsWith(&amp;quot;\n&amp;quot;))
					text = text.substring(1);
				if (text.endsWith(&amp;quot;\n&amp;quot;))
					text = text.substring(0, text.length() - 1);

				return text;
			}
}
&lt;/pre&gt;
  &lt;p&gt;Я добавил комментарии к коду. Если у вас возникнут вопросы - пишите их в бота.&lt;/p&gt;
  &lt;p&gt;Грубо говоря: мы шлём команду, ждём пока система ответит, и выполняем другие команды дальше.&lt;/p&gt;
  &lt;p&gt;В примере приложения я использовал метод &lt;code&gt;execute(boolean su, String command)&lt;/code&gt; т.к. значение &lt;code&gt;su&lt;/code&gt; у меня зависит от чекбокса.&lt;/p&gt;
  &lt;h2&gt;Пример вызова&lt;/h2&gt;
  &lt;pre&gt;String out = CommandExecutor.execute(true, &amp;quot;magiskhide --ls&amp;quot;).getResult();
&lt;/pre&gt;
  &lt;p&gt;Выведет список приложений от которых скрывается рут в &lt;code&gt;MagiskHide&lt;/code&gt;.&lt;/p&gt;
  &lt;pre&gt;Log.d(&amp;quot;SU-TEST&amp;quot;, out);
&lt;/pre&gt;
  &lt;h2&gt;Создаем приложение&lt;/h2&gt;
  &lt;p&gt;Создайте новый проект в IDE. Откройте MainActivity.java и напишите следующий код:&lt;/p&gt;
  &lt;pre&gt;import android.app.Activity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.CheckBox;
import android.widget.TextView;
import android.view.View.OnClickListener;
import android.view.View;

public class MainActivity extends Activity {
	private Button btnExec;
	private EditText fieldCommand;
	private CheckBox boxSu;
	private TextView textOut;
	
		@Override
		protected void onCreate(Bundle savedInstanceState) {
				super.onCreate(savedInstanceState);
				setContentView(R.layout.main);
				
				fieldCommand = findViewById(R.id.field_exec);
				boxSu = findViewById(R.id.checbox_su);
				btnExec = findViewById(R.id.btn_execute);
				textOut = findViewById(R.id.command_output);
				
				btnExec.setOnClickListener(new OnClickListener(){

							@Override
							public void onClick(View p1) {
									String out = CommandExecutor.execute(boxSu.isChecked(), fieldCommand.getText().toString()).getResult();
									textOut.setText(out);
								}
							
					
				});
			}
	}
&lt;/pre&gt;
  &lt;p&gt;Измените разметку главного экрана (в моем случае &lt;strong&gt;main.xml&lt;/strong&gt;)&lt;/p&gt;
  &lt;pre&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;LinearLayout
	xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;
	android:layout_width=&amp;quot;match_parent&amp;quot;
	android:layout_height=&amp;quot;match_parent&amp;quot;
	android:orientation=&amp;quot;vertical&amp;quot;
	android:padding=&amp;quot;16dp&amp;quot;&amp;gt;

	&amp;lt;EditText
		android:layout_width=&amp;quot;match_parent&amp;quot;
		android:maxLines=&amp;quot;2&amp;quot;
		android:layout_height=&amp;quot;wrap_content&amp;quot;
		android:hint=&amp;quot;command&amp;quot;
		android:id=&amp;quot;@+id/field_exec&amp;quot;/&amp;gt;

	&amp;lt;CheckBox
		android:layout_marginTop=&amp;quot;16dp&amp;quot;
		android:text=&amp;quot;Super User&amp;quot;
		android:layout_width=&amp;quot;wrap_content&amp;quot;
		android:layout_height=&amp;quot;wrap_content&amp;quot;
		android:id=&amp;quot;@+id/checbox_su&amp;quot;/&amp;gt;

	&amp;lt;Button
		android:layout_width=&amp;quot;wrap_content&amp;quot;
		android:layout_height=&amp;quot;wrap_content&amp;quot;
		android:text=&amp;quot;Execute&amp;quot;
		android:textAllCaps=&amp;quot;false&amp;quot;
		android:layout_gravity=&amp;quot;end&amp;quot;
		android:id=&amp;quot;@+id/btn_execute&amp;quot;/&amp;gt;

	&amp;lt;ScrollView
		android:layout_width=&amp;quot;wrap_content&amp;quot;
		android:layout_height=&amp;quot;wrap_content&amp;quot;&amp;gt;

		&amp;lt;TextView
			android:layout_width=&amp;quot;wrap_content&amp;quot;
			android:layout_height=&amp;quot;wrap_content&amp;quot;
			android:typeface=&amp;quot;monospace&amp;quot;
			android:textSize=&amp;quot;12sp&amp;quot;
			android:id=&amp;quot;@+id/command_output&amp;quot;/&amp;gt;

	&amp;lt;/ScrollView&amp;gt;

&amp;lt;/LinearLayout&amp;gt;
&lt;/pre&gt;
  &lt;p&gt;Скопируйте класс &lt;strong&gt;CommandExecutor.java&lt;/strong&gt; к себе в проект.&lt;/p&gt;
  &lt;p&gt;Все ошибки должны пропасть. Запустите приложение и попробуйте выполнить любую shell-команду.&lt;/p&gt;
  &lt;h2&gt;Результат&lt;/h2&gt;
  &lt;p&gt;Выполнение команды без рута&lt;/p&gt;
  &lt;figure class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/b6/b67bfebf-642c-45ee-9069-2bd94f96e0ab.png&quot; width=&quot;1080&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;То же самое с рутом&lt;/p&gt;
  &lt;figure class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/20/204b0f77-655f-4e60-8b8f-5be2559f2a4a.png&quot; width=&quot;1080&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Приложение и его исходный код можно найти на канале: &lt;a href=&quot;http://t.me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;@VolfsChannel&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>snowvolf:HkMgNWxYQ</id><link rel="alternate" type="text/html" href="https://teletype.in/@snowvolf/HkMgNWxYQ?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=snowvolf"></link><title>Аутентификация с помощью отпечатков пальцев в Android</title><published>2018-09-19T17:14:17.638Z</published><updated>2018-09-19T17:14:17.638Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/13/13ecede5-73f6-4692-b6fd-8feae7c32c2d.jpeg"></media:thumbnail><summary type="html">&lt;img src=&quot;https://teletype.in/files/1c/1c38b1b9-a435-4470-945b-c6a713918e8c.png&quot;&gt;Мой канал: @VolfsChannel</summary><content type="html">
  &lt;p&gt;Мой канал: &lt;a href=&quot;http://t.me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;@VolfsChannel&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;Биометрия... Ваши отпечатки пальцев в смартфонах... Ещё 5-7 лет назад данный вид аутентификации казался баловством. Я что, храню коды запуска ракет? Зачем мне этот сканер? Да графический ключ удобнее и быстрее! Знакомые фразы? Может быть вы и сами так думали, но сейчас глупо отрицать - такого рода биометрическая аутентификация является лидирующей на портативных устройствах. Сказывается дешевизна ($3-5 за модуль сканера) и относительная безопасность.&lt;/p&gt;
  &lt;p&gt;Сканеры отпечатков пальцев в смартфонах стали появляться ещё во времена Android 4.4. Только тогда, из-за отсутствия API, данный сканер мог работать только в приложениях от производителя, и был скорее забавной игрушкой, чем методом авторизации. Всё изменилось с релизом Android 6.0. Было представлено Fingerprint Manager API, с помощью которого разработчики могут реализовать авторизацию по фингерпринту.&lt;/p&gt;
  &lt;p&gt;Почитав пару статей на Хабре, я так ничего и не понял. Что? Как? Зачем? Путаные инструкции и нуль конкретики.&lt;/p&gt;
  &lt;p&gt;Сегодня я хочу разобраться в API, а поможет мне в этом специальный класс-хелпер который является модификацией одноименного класса из Magisk Manager.&lt;/p&gt;
  &lt;p&gt;Подготовка&lt;/p&gt;
  &lt;p&gt;Для использования сканера отпечатков ваше приложение должно удовлетворять следующим требованиям:&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Целевая версия SDK 23 или выше&lt;/li&gt;
    &lt;li&gt;Разрешение &amp;lt;uses-permission android:name=&amp;quot;android.permission.USE_FINGERPRINT&amp;quot; /&amp;gt; в манифесте&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h2&gt;Реализация&lt;/h2&gt;
  &lt;p&gt;Желательно завести объект-наследник класса android.app.Application, чтобы без труда получить контекст.&lt;/p&gt;
  &lt;pre&gt;import android.app.Application;

public class Girl extends Application {
	private static Girl instance;
	
	public Girl(){
		instance = this;
		}

	public static Girl get() {
			return instance;
		}

	@Override
	public void onCreate() {
			// TODO: Implement this method
			super.onCreate();
		}
	
	
}
&lt;/pre&gt;
  &lt;p&gt;Зарегистрируйте класс в файле AndroidManifest.xml&lt;/p&gt;
  &lt;pre&gt;&amp;lt;application
    android:name=&amp;quot;.Girl&amp;quot;
    ...
    ...
    ...&amp;gt;
...
...
...
&amp;lt;/application&amp;gt;
&lt;/pre&gt;
  &lt;p&gt;Создайте класс &lt;strong&gt;FingerprintHelper.java&lt;/strong&gt;, который и будет отвечать за работу со сканером:&lt;/p&gt;
  &lt;pre&gt;import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.CancellationSignal;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;

import java.security.KeyStore;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

@TargetApi(Build.VERSION_CODES.M)
public abstract class FingerprintHelper {

  private FingerprintManager manager;
  private Cipher cipher;
  private CancellationSignal cancel;
	
	private String keystoreTag = &amp;quot;GirlTag&amp;quot;;
	
	// Проверяем наличие сканера
  public static boolean canUseFingerprint() {
    if (Build.VERSION.SDK_INT &amp;lt; Build.VERSION_CODES.M)
      return false;
    Girl girl = Girl.get();
    KeyguardManager km = girl.getSystemService(KeyguardManager.class);
    FingerprintManager fm = girl.getSystemService(FingerprintManager.class);
    return km.isKeyguardSecure() &amp;amp;&amp;amp; fm != null &amp;amp;&amp;amp; fm.isHardwareDetected() &amp;amp;&amp;amp; fm.hasEnrolledFingerprints();
  }
	
	// Мы не можем просто так взять и использовать отпечаток без пароля
	// Нам необходимо создать хранилище ключей, по которому будет
	// будет производиться авторизация
  protected FingerprintHelper() throws Exception {
		// Получение контекста
		Girl girl = Girl.get();
		// Создание экземпляра хранилища ключей
    KeyStore keyStore = KeyStore.getInstance(&amp;quot;AndroidKeyStore&amp;quot;);
		// Стучимся к сервису распознавания отпечатков
    manager = girl.getSystemService(FingerprintManager.class);
		// Создание экземпляра шифровальщика
    cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + &amp;quot;/&amp;quot;
        + KeyProperties.BLOCK_MODE_CBC + &amp;quot;/&amp;quot;
        + KeyProperties.ENCRYPTION_PADDING_PKCS7);
		// Загрузка хранилища ключей
    keyStore.load(null);
		// Получаем секретный ключ из загруженного хранилища
		// если отсутствует - создаём
    SecretKey key = (SecretKey) keyStore.getKey(keystoreTag, null);
    if (key == null) {
      key = generateKey();
    }
		// Расшифровка
    try {
      cipher.init(Cipher.ENCRYPT_MODE, key);
    } catch (KeyPermanentlyInvalidatedException e) {
      // Фикс бага Android Marshmallow
      key = generateKey();
      cipher.init(Cipher.ENCRYPT_MODE, key);
    }
  }
	
	// Если в процессе авторизации произошла ошибка
  public abstract void onAuthenticationError(int errorCode, CharSequence errString);
	
	// Получение статуса авторизации
  public abstract void onAuthenticationHelp(int helpCode, CharSequence helpString);
	
	// Успешная авторизация
  public abstract void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result);
	// Ошибка
  public abstract void onAuthenticationFailed();
	
	// Запуск авторизации
  public void startAuth() {
    cancel = new CancellationSignal();
    FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
    manager.authenticate(cryptoObject, cancel, 0, new FingerprintManager.AuthenticationCallback() {
      @Override
      public void onAuthenticationError(int errorCode, CharSequence errString) {
        FingerprintHelper.this.onAuthenticationError(errorCode, errString);
      }

      @Override
      public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
        FingerprintHelper.this.onAuthenticationHelp(helpCode, helpString);
      }

      @Override
      public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
        FingerprintHelper.this.onAuthenticationSucceeded(result);
      }

      @Override
      public void onAuthenticationFailed() {
        FingerprintHelper.this.onAuthenticationFailed();
      }
    }, null);
  }
	
	// Если пользователь отменил операцию
  public void cancel() {
    if (cancel != null)
      cancel.cancel();
  }
	
	// Создание ключа авторизации
  private SecretKey generateKey() throws Exception {
    KeyGenerator keygen = KeyGenerator
        .getInstance(KeyProperties.KEY_ALGORITHM_AES, &amp;quot;AndroidKeyStore&amp;quot;);
    KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
        keystoreTag,
        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
        .setUserAuthenticationRequired(true)
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);

    if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.N) {
      builder.setInvalidatedByBiometricEnrollment(false);
    }
    keygen.init(builder.build());
    return keygen.generateKey();
  }
}
&lt;/pre&gt;
  &lt;p&gt;Я добавил комментарии к коду, так что вопросов возникнуть не должно.&lt;/p&gt;
  &lt;p&gt;Пример реализации в Activity:&lt;/p&gt;
  &lt;pre&gt;private FingerprintHelper fingerprint;
private TextView warning;

private void init() {
				if (FingerprintHelper.canUseFingerprint()) {
						try {
								fingerprint = new FingerprintHelper() {
										@Override
										public void onAuthenticationError(int errorCode, CharSequence errString) {
												warning.setText(errString);
											}

										@Override
										public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
												warning.setText(helpString);
											}

										@Override
										public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
												warning.setText(&amp;quot;Success!&amp;quot;);
											}

										@Override
										public void onAuthenticationFailed() {
												warning.setText(&amp;quot;Failed!&amp;quot;);
											}
									};
								fingerprint.startAuth();
							} catch (Exception e) {
								e.printStackTrace();

							}
					}
			}

		@Override
		public void finish() {

				if (fingerprint != null)
					fingerprint.cancel();
				super.finish();
			}

&lt;/pre&gt;
  &lt;h2&gt;А наглядно?&lt;/h2&gt;
  &lt;p&gt;Конечно. Пример приложения и его исходный код вы найдете на канале.&lt;/p&gt;
  &lt;figure class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/1c/1c38b1b9-a435-4470-945b-c6a713918e8c.png&quot; width=&quot;1080&quot; /&gt;
  &lt;/figure&gt;
  &lt;hr /&gt;
  &lt;figure class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/7d/7d5bb02a-6637-435d-9db0-0a5c8aa02924.png&quot; width=&quot;1080&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;&lt;a href=&quot;http://t.me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;@VolfsChannel&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>snowvolf:BkTpAQTOX</id><link rel="alternate" type="text/html" href="https://teletype.in/@snowvolf/BkTpAQTOX?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=snowvolf"></link><title>Простейшая проверка подписи на Java</title><published>2018-09-17T13:40:21.382Z</published><updated>2018-09-17T13:40:21.382Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/bc/bc6aa5c7-b713-4970-9f62-37d134913590.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://teletype.in/files/59/5946107f-ed07-43b0-949c-ddd5d8d4bcf4.png&quot;&gt;Мой канал: @VolfsChannel</summary><content type="html">
  &lt;p&gt;Мой канал: &lt;a href=&quot;http://t.me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;@VolfsChannel&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;Данная проверка является т.н. &amp;quot;классикой жанра&amp;quot; и первым (иногда и последним) рубежом защиты. Данный пример до безобразия прост, но тем и привлекателен. Никаких заумных и запутанных действий, только рабочий код.&lt;/p&gt;
  &lt;p&gt;Весь код проверки умещается в одном методе. Имена переменных изменены специально.&lt;/p&gt;
  &lt;h2&gt;Реализация&lt;/h2&gt;
  &lt;p&gt;Объявляем поля:&lt;/p&gt;
  &lt;pre&gt;private static PackageManager pm;
private static String name;
private static String s;
private static byte[] bs;
private static Signature[] sa;
&lt;/pre&gt;
  &lt;p&gt;Проверяем подпись:&lt;/p&gt;
  &lt;pre&gt;@SuppressLint(&amp;quot;PackageManagerGetSignatures&amp;quot;)
		public boolean b(String st) {
				try {
						pm = getApplicationContext().getPackageManager();
						name = getApplicationContext().getPackageName();
						sa = pm.getPackageInfo(name, PackageManager.GET_SIGNATURES).signatures;
						//Log.w(Constants.TAG, &amp;quot;Signature[] :: &amp;quot; + Arrays.toString(sa));
						for (Signature a$a : sa) {
								//Log.w(Constants.TAG, &amp;quot;byte[] :: &amp;quot; + Arrays.toString(bs));
								bs = a$a.toByteArray();
								//Log.w(Constants.TAG, &amp;quot;new byte[] :: &amp;quot; + Arrays.toString(bs));
								bs = CertificateFactory.getInstance(&amp;quot;X509&amp;quot;).generateCertificate(
									new ByteArrayInputStream(bs)).getEncoded();
								//Log.w(Constants.TAG, &amp;quot;new byte[] encoded :: &amp;quot; + Arrays.toString(bs));
								s = new String(Base64.encode(MessageDigest.getInstance(&amp;quot;MD5&amp;quot;).digest(bs), 19));
								//Log.w(Constants.TAG, &amp;quot;result string :: &amp;quot; + s);
								return Objects.equals(s, st);
							}
					} catch (Exception e) {
						return false;
					}
				return false;
			}
&lt;/pre&gt;
  &lt;p&gt;Теперь пройдемся по коду, хотя, те кому надо и так уже поняли...&lt;/p&gt;
  &lt;p&gt;На вход поступает &lt;code&gt;String &lt;strong&gt;st&lt;/strong&gt;.&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Следующие 3 строчки - инициализация объектa &lt;code&gt;PackageManager&lt;/code&gt;, у которого мы узнаем наше имя пакета. С помощью этого имени мы получаем массив сигнатур - ту самую подпись.&lt;/p&gt;
  &lt;p&gt;Преобразуем нашу подпись в массив байт. Из полученного массива мы создаём сертификат &lt;code&gt;X509&lt;/code&gt;. Шифруем MD5 нашего сертификата с помощью &lt;code&gt;Base64&lt;/code&gt;, и сравниваем получившийся String с переменной &lt;code&gt;&lt;strong&gt;st&lt;/strong&gt;&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;Что такое &lt;code&gt;&lt;strong&gt;st&lt;/strong&gt;&lt;/code&gt;? Как его получить? Создать новый метод с возвращением String, и вернуть &lt;code&gt;&lt;strong&gt;s&lt;/strong&gt;&lt;/code&gt;&lt;/p&gt;
  &lt;h2&gt;А наглядно можно?&lt;/h2&gt;
  &lt;p&gt;Конечно. Чтобы продемонстрировать работу кода, я создал простое приложение. Чтобы узнать хэш вашей подписи, просто переподпишите его вашей подписью.&lt;/p&gt;
  &lt;p&gt;Подписи совпадают:&lt;/p&gt;
  &lt;figure class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/59/5946107f-ed07-43b0-949c-ddd5d8d4bcf4.png&quot; width=&quot;1080&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Подписи не совпадают:&lt;/p&gt;
  &lt;figure class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/5c/5c8ab5a2-371c-4612-a056-bce2ddb97951.png&quot; width=&quot;1080&quot; /&gt;
  &lt;/figure&gt;
  &lt;p&gt;Скачать данное приложение и его исходный код можно на канале.&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;http://t me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;@VolfsChannel&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>snowvolf:HJ5gODr_X</id><link rel="alternate" type="text/html" href="https://teletype.in/@snowvolf/HJ5gODr_X?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=snowvolf"></link><title>Обход запрета на использование reflection в Android Pie</title><published>2018-09-11T16:06:10.185Z</published><updated>2018-09-11T16:06:10.185Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/f0/f07ee61a-30fe-4926-a4d0-6ffe34b702d3.png"></media:thumbnail><summary type="html">Мой канал @VolfsChannel</summary><content type="html">
  &lt;p&gt;Мой канал &lt;a href=&quot;http://t.me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;@VolfsChannel&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;Для того чтобы понять, что вообще тут происходит, прочитайте предыдущую статью на канале.&lt;/p&gt;
  &lt;h2&gt;Как я могу отключить данные ограничения?&lt;/h2&gt;
  &lt;p&gt;Данные ограничения можно отключить на 1-м конкретном устройстве с помощью команд ADB:&lt;/p&gt;
  &lt;pre&gt;adb shell settings put global hidden_api_policy_pre_p_apps 1
 adb shell settings put global hidden_api_policy_p_apps 1
&lt;/pre&gt;
  &lt;p&gt;Команды принимают следующие входные параметры:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;0&lt;/strong&gt;: отключение обнаружения использования non-SDK API. Это также отключит ведение журнала, а также нарушит строгий режим API, detectNonSdkApiUsage(). Не рекомендуется для повседневного использования.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;1&lt;/strong&gt;: &amp;quot;только предупреждение&amp;quot; - разрешить доступ ко всем API, не связанным с SDK, но сохранять предупреждения в журнале. Строгий режим API будет продолжать работать.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;2&lt;/strong&gt;: запретить использование темно-серого и черного списка API.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;3&lt;/strong&gt;: запретить использование черного списка API, но разрешить использование темно-серого списка&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Если у вас нет ПК, то вы можете прописать эти команды в эмуляторе терминала (потребуются права Root):&lt;/p&gt;
  &lt;pre&gt;su
settings put global hidden_api_policy_pre_p_apps 1
settings put global hidden_api_policy_p_apps 1
&lt;/pre&gt;
  &lt;p&gt;Со стороны ПО вы можете использовать вариант выше через &lt;code&gt;Runtime.getRuntime().exec(&amp;quot;command&amp;quot;)&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Или же создать класс Application, и поместить вызов метода в onCreate():&lt;/p&gt;
  &lt;pre&gt;public void hackVmPolicy() {
 
		if (Build.VERSION.SDK_INT &amp;gt;= 28) {
 
			StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
 
			StrictMode.setVmPolicy(builder.build());
 
		} else {
 
			Log.i(&amp;quot;Lower android version &amp;quot; + Build.VERSION.SDK_INT);
 
		}
 
	}
&lt;/pre&gt;
  &lt;p&gt;Суть этого метода заключается в переопределении политики VM для конкретного приложения. Мы создаём новый (пустой) инстанс политики, и применяем его. Т.к. за рефлексию отвечает метод detectNonSdkApiUsage(), мы можем играть не по правилам.&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;http://t.me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;@VolfsChannel&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>snowvolf:BJKIcr0P7</id><link rel="alternate" type="text/html" href="https://teletype.in/@snowvolf/BJKIcr0P7?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=snowvolf"></link><title>Reflection в Android Pie</title><published>2018-09-10T01:55:05.308Z</published><updated>2018-09-10T01:55:05.308Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/f0/f07ee61a-30fe-4926-a4d0-6ffe34b702d3.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://teletype.in/files/61/61f73adb-b3e9-48de-ad63-bdedbc6e4b47.png&quot;&gt;Мой канал: VolfsChannel</summary><content type="html">
  &lt;p&gt;Мой канал: &lt;a href=&quot;http://t.me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;VolfsChannel&lt;/a&gt;&lt;/p&gt;
  &lt;p&gt;С каждым годом Android становится всё более закрытой системой. Хотя платформа всё также имеет открытый исходный код, играть не по правилам становится всё труднее. Но у разработчиков есть один полезный инструмент, доставшийся благодаря Java. Его имя - reflection (отражение). Или по просту говоря - прямой доступ даже к приватным методам и классам. Рефлексия позволяет добраться до скрытых API, и извлечь выгоду.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2&gt;Беда не приходит одна...&lt;/h2&gt;
  &lt;p&gt;Вероятно у вас возникли следующие вопросы:&lt;/p&gt;
  &lt;blockquote&gt;Так что же всё таки произошло?&lt;/blockquote&gt;
  &lt;blockquote&gt;Ничего непонятно. Я использую рефлексию, но моё приложение прекрасно работает на Android Pie, где подвох?&lt;/blockquote&gt;
  &lt;p&gt;Не так быстро, сейчас мы во всём разберемся...&lt;/p&gt;
  &lt;p&gt;Гугл давно стал беспокоиться о совместимости приложений с различными версиями Android. Были созданы библиотеки android-support, которые приносят более новые API в старые версии Android. Помимо поддержки, библиотеки несут в себе расширение функциональных возможностей приложения. Но одних библиотек мало... Даже вместе с SDK и различными сторонними фреймворками, некоторым разработчикам приходится использовать скрытые возможности Android, которые не заявлены в документации. Это представляет 2 потенциальных угрозы:&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Приложение может навредить функциональности устройства, путем использования скрытых &amp;quot;хуков&amp;quot; ОС&lt;/li&gt;
    &lt;li&gt;Приложение может полностью или частично перестать работать в результате изменения алгоритма работы скрытого API в новой версии Android, либо же полного его удаления.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;В версии 7.0 Android стал ограничивать использование скрытых интерфейсов NDK с помощью &lt;strong&gt;dlopen&lt;/strong&gt;. Запрещается использование скрытых API библиотек находящиеся в &lt;code&gt;/system/lib/&lt;/code&gt; (что, впрочем, не мешает скопировать и загрузить библиотеку из другого места). В Android 9.0 Гугл ввёл похожие ограничения на использование скрытых API интерфейсов SDK (т.н. &lt;code&gt;non-SDK interfaces&lt;/code&gt;). Так, с этим разобрались. Что же дальше?&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2&gt;Быстрее, выше, сильнее&lt;/h2&gt;
  &lt;p&gt;Само собой, изменение в использовании такого важного компонента не могут пройти в одночасье. Для этого требуется время, и, что не менее важно - альтернативны скрытым API. Android 9 вводит следующую классификацию методов и полей:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Белый список - все методы и поля публичного SDK &lt;strong&gt;[74 000 методов и полей]&lt;/strong&gt;&lt;/li&gt;
    &lt;li&gt;Светло-серый список - поля и методы не являющиеся частью SDK, но тем не менее остающиеся доступными &lt;strong&gt;[11 000 методов и полей]&lt;/strong&gt;&lt;/li&gt;
    &lt;li&gt;Тёмно-серый список - для приложений с целевым SDK меньше 28, разрешает использование скрытых методов и полей из этого списка. Для приложений с целевым SDK равным или выше 28 действует аналогично чёрному списку &lt;strong&gt;[121 000 методов и полей]&lt;/strong&gt;&lt;/li&gt;
    &lt;li&gt;Чёрный список - ограничен независимо от целевого SDK. Платформа будет вести себят так как будто интерфейс отсутствует. Например, будет бросать NoSuchMethodError/NoSuchFieldException всякий раз, когда приложение пытается использовать его, и не включать его, когда приложение хочет знать список полей/методов определенного класса. &lt;strong&gt;[9 000 методов и полей]&lt;/strong&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p&gt;Всё это сделано ради нашего же блага, а как же ещё...&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2&gt;Результат использования интерфейсов не из пакета SDK (black, dark-gray lists)&lt;/h2&gt;
  &lt;p&gt;Согласно таблице на официальном сайте, использование скрытых API SDK повлечёт за собой выброс следующих исключений:&lt;/p&gt;
  &lt;figure class=&quot;m_custom&quot;&gt;
    &lt;img src=&quot;https://teletype.in/files/61/61f73adb-b3e9-48de-ad63-bdedbc6e4b47.png&quot; width=&quot;2048&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2&gt;Что же делать мне?&lt;/h2&gt;
  &lt;p&gt;Прежде всего, если ваше приложение использует скрытые API, вам необходимо найти замену в лице открытого интерфейса SDK. Если это невозможно, то вам необходимо создать запрос здесь:&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;https://issuetracker.google.com/issues/new?component=328403&amp;template=1027267&quot; target=&quot;_blank&quot;&gt;https://issuetracker.google.com/issues/new?component=328403&amp;amp;template=1027267&lt;/a&gt; и подробно описать ваш случай использования конкретного поля/метода.&lt;/p&gt;
  &lt;p&gt;Если вы используете сторонние библиотеки, и не уверены в том, что какая-либо из них не использует запрещённые API, то вы можете проверить ваш DEX с помощью утилиты veridex:&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;https://android.googlesource.com/platform/prebuilts/runtime/+/master/appcompat&quot; target=&quot;_blank&quot;&gt;https://android.googlesource.com/platform/prebuilts/runtime/+/master/appcompat&lt;/a&gt;&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2&gt;FAQ&lt;/h2&gt;
  &lt;blockquote&gt;&lt;strong&gt;Как мне определить, что приложение использует скрытые API?&lt;/strong&gt;&lt;/blockquote&gt;
  &lt;p&gt;При использовании скрытых API, в системный журнал будет печататься сообщение следующего вида:&lt;/p&gt;
  &lt;pre&gt;Accessing hidden field|method Lname/of/Class;-&amp;gt;nameOfFieldOrMethod: returnType (method list, source-caller (Java/JNI))
&lt;/pre&gt;
  &lt;p&gt;Например:&lt;/p&gt;
  &lt;pre&gt;Accessing hidden field Landroid/os/Message;-&amp;gt;flags:I (light greylist, JNI)
&lt;/pre&gt;
  &lt;blockquote&gt;&lt;strong&gt;Где я могу получить список API из черного/серого списка?&lt;/strong&gt;&lt;/blockquote&gt;
  &lt;p&gt;Они являются частью платформы Вы можете найти готовые записи здесь:&lt;/p&gt;
  &lt;p&gt;&lt;code&gt;platform/prebuilts/runtime/appcompat/ hiddenapi-light-greylist.txt&lt;/code&gt;: список светло-серых API&lt;/p&gt;
  &lt;p&gt;&lt;code&gt;platform/prebuilts/runtime/appcompat/ hiddenapi-dark-greylist.txt&lt;/code&gt;: список темно-серых API&lt;/p&gt;
  &lt;p&gt;Дополнительно, это список содержит список светло-серых API.&lt;/p&gt;
  &lt;p&gt;Мы добавили правило, создающее списки на AOSP. Это не то же самое, что и черный список в P, но перекрытие достаточно хорошее. Разработчик может скачать исходный код AOSP, а затем сгенерировать черный список с помощью следующей команды:&lt;/p&gt;
  &lt;p&gt;&lt;code&gt;make hiddenapi-aosp-blacklist&lt;/code&gt;&lt;/p&gt;
  &lt;p&gt;Файл можно будет просмотреть по пути:&lt;/p&gt;
  &lt;p&gt;&lt;code&gt;out/target/common/obj/PACKAGING/hiddenapi-aosp-blacklist.txt&lt;/code&gt;&lt;/p&gt;
  &lt;blockquote&gt;&lt;strong&gt;Каковы примерные размеры этих списков?&lt;/strong&gt;&lt;/blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;белый список (также известный как SDK) ~= 74 000 методов и полей&lt;/li&gt;
    &lt;li&gt;светло-серый ~= 11 000 методов и полей&lt;/li&gt;
    &lt;li&gt;тёмно-серый ~= 121 000 методов и полей&lt;/li&gt;
    &lt;li&gt;черный список ~= 9 000 методов и полей&lt;/li&gt;
  &lt;/ul&gt;
  &lt;blockquote&gt;&lt;strong&gt;Где я могу найти серый/чёрный список в образе системы?&lt;/strong&gt;&lt;/blockquote&gt;
  &lt;p&gt;Они кодируются в поле и битах флага доступа к методу, в файлах платформы dex. В образе системы, содержащем эти списки, нет отдельного файла.&lt;/p&gt;
  &lt;blockquote&gt;&lt;strong&gt;Серый/чёрный список является одинаковым на разных устройствах OEM с одинаковыми версиями Android?&lt;/strong&gt;&lt;/blockquote&gt;
  &lt;p&gt;Да, OEM-производители могут добавить свои собственные API в черный список, но не могут удалить методы из оригинального черного/серого списка. CDD предотвращает такие изменения, а CTS тесты гарантируют, что рантайм Android применяет данный список.&lt;/p&gt;
  &lt;blockquote&gt;&lt;strong&gt;Применяются ли ограничения интерфейсов, отличных от SDK, ко всем приложениям, включая системные и сторонние приложения, а не только к сторонним приложениям?&lt;/strong&gt;&lt;/blockquote&gt;
  &lt;p&gt;Да, однако, мы освобождаем приложения, подписанные ключом платформы, и у нас также есть белый список пакетов для некоторых приложений с системными ресурсами. Обратите внимание, что эти исключения применимы только к приложениям, которые находятся в образе системы (или обновленных приложениях образа системы). Этот список предназначен только для приложений, которые создаются на основе API-интерфейсов частной платформы, а не в SDK API (т. е. &lt;code&gt;LOCAL_PRIVATE_PLATFORM_APIS := true&lt;/code&gt;).&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h2&gt;Как я могу отключить данные ограничения?&lt;/h2&gt;
  &lt;p&gt;Данные ограничения можно отключить на 1 конкретном устройстве с помощью... [Продолжение следует]&lt;/p&gt;
  &lt;p&gt;&lt;a href=&quot;http://t.me/VolfsChannel&quot; target=&quot;_blank&quot;&gt;@VolfsChannel&lt;/a&gt;&lt;/p&gt;

</content></entry><entry><id>snowvolf:S1mAs-hw7</id><link rel="alternate" type="text/html" href="https://teletype.in/@snowvolf/S1mAs-hw7?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=snowvolf"></link><title>Переключение тем в Android приложении</title><published>2018-09-04T13:42:34.566Z</published><updated>2018-09-04T13:42:34.566Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/55/55709271-1893-4ec9-a13a-eaaee493f0df.jpeg"></media:thumbnail><summary type="html">Наверное каждому в этом мире охота иметь право выбора. Даже если это лишь выбор цветовой схемы приложения. Тёмная тема легче воспринимается в вечернее время, а также положительно влияет на энергосбережение (в частности на AMOLED дисплеях)</summary><content type="html">
  &lt;p&gt;Наверное каждому в этом мире охота иметь право выбора. Даже если это лишь выбор цветовой схемы приложения. Тёмная тема легче воспринимается в вечернее время, а также положительно влияет на энергосбережение (в частности на AMOLED дисплеях)&lt;/p&gt;
  &lt;p&gt;Когда-то я хотел сделать изменение темы в приложении &amp;quot;на лету&amp;quot;, но не мог из-за своих малых знаний (от силы 2 месяца программирования). Всё приходит со временем, и, возможно, это тот самый момент, когда вы овладеете полезным навыком.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Хочу сразу предупредить:&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Пример будет предназначен для новичков, имеющих малый (менее 1 года) опыт разработки под Андроид. В проекте не будет никакой сложной архитектуры, типо MVP, которая только лишь собьёт новичка с толку.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Подразумевается, что:&lt;/strong&gt;&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Вы уже знакомы с методами жизненного цикла Activity, умеете запускать и управлять их состоянием&lt;/li&gt;
    &lt;li&gt;Вы уже знакомы с классом Fragment, умеете помещать несколько фрагментов внутри одной Activity, и управлять ими&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h2&gt;Подготовка&lt;/h2&gt;
  &lt;p&gt;Перейдите в каталог ресурсов вашего проекта, найдите файл &lt;strong&gt;styles.xml&lt;/strong&gt;, и добавьте туда новые темы для приложения.&lt;/p&gt;
  &lt;p&gt;Создайте класс &lt;strong&gt;App&lt;/strong&gt;, который будет наследоваться от &lt;code&gt;android.app.Application&lt;/code&gt;, и напишите следующий код:&lt;/p&gt;
  &lt;pre&gt;import android.app.Application;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;

public class App extends Application {
  private static App instance = null;
  private SharedPreferences preferences;

  public App() {
    super();
  }

  @Override
  public void onCreate() {
    super.onCreate();
    instance = this;
    // Если в вашем приложении есть экран настроек, замените preferences на нужное имя файла
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
  }

  public static App getInstance(){
    if (instance == null)
      instance = new App();
    return instance;
  }

  public SharedPreferences getPreferences(){
    if (preferences == null)
      preferences = PreferenceManager.getDefaultSharedPreferences(this);
    return preferences;
  }
}
&lt;/pre&gt;
  &lt;p&gt;Зарегистрируйте класс в файле &lt;strong&gt;AndroidManifest.xml&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;&amp;lt;application
    android:name=&amp;quot;.App&amp;quot;
    ...
    ...
    .../&amp;gt;
...
...
...
&amp;lt;/application&amp;gt;
&lt;/pre&gt;
  &lt;p&gt;Теперь пройдемся по методам. Мы создали экземпляр класса Application, который сам по себе является синглтоном. Переопределив метод &lt;code&gt;onCreate()&lt;/code&gt;, присвоили значение переменной &lt;code&gt;instance&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;В методе &lt;code&gt;getInstance()&lt;/code&gt; мы просто возвращаем переменную instance, которая является экземпляром класса App.&lt;/p&gt;
  &lt;p&gt;В методе &lt;code&gt;getPreferences()&lt;/code&gt; мы возвращаем экземпляр &lt;code&gt;SharedPreferences&lt;/code&gt;, необходимый для хранения настроек.&lt;/p&gt;
  &lt;p&gt;Создайте новый класс, который будет отвечать за смену темы. Я назову его &lt;strong&gt;ThemeWrapper.java&lt;/strong&gt;. напишите какой код:&lt;/p&gt;
  &lt;pre&gt;import android.app.Activity;

public abstract class ThemeWrapper {
  public enum Theme {
    LIGHT,
    DARK
  }

  public static void applyTheme(Activity ctx) {
    int theme;
    switch (Theme.values()[getThemeIndex()]) {
      case LIGHT:
        theme = R.style.AppTheme;
        break;
      case DARK:
        theme = R.style.AppTheme_Dark;
        break;
      default:
        theme = R.style.AppTheme;
        break;
    }
    ctx.setTheme(theme);
  }

  private static int getThemeIndex() {
    return Integer.parseInt(App.getInstance().getPreferences().getString(&amp;quot;ui.theme&amp;quot;, String.valueOf(ThemeWrapper.Theme.LIGHT.ordinal())));
  }
}
&lt;/pre&gt;
  &lt;p&gt;Самостоятельно импортируйте классы &lt;strong&gt;R&lt;/strong&gt; и &lt;strong&gt;App&lt;/strong&gt;.&lt;/p&gt;
  &lt;h2&gt;Внедрение кода&lt;/h2&gt;
  &lt;p&gt;Если ваше приложение использует более чем одно Activity, то лучше всего будет создать некий базовый класс, от которого всё будет наследоваться. Я назвал такой класс &lt;strong&gt;BaseActivity.java&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;



public class BaseActivity extends AppCompatActivity {
  //Theme
  private final BroadcastReceiver mThemeReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      if (SettingsActivity.class.equals(BaseActivity.this.getClass())){
        finish();
        startActivity(getIntent());
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
      } else recreate();
    }
  };

  public BaseActivity(){

  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    //Приёмник сигналов от SettingsActivity
 LocalBroadcastManager.getInstance(this).registerReceiver(mThemeReceiver, new IntentFilter(&amp;quot;ru.svolf.action.REFRESH_THEME&amp;quot;));
    ThemeWrapper.applyTheme(this);
    super.onCreate(savedInstanceState);
  }

  @Override
  public void onResume() {
    super.onResume();
  }

  @Override
  protected void onDestroy() {
    LocalBroadcastManager.getInstance(this).unregisterReceiver(mThemeReceiver);
    super.onDestroy();
  }
}
&lt;/pre&gt;
  &lt;p&gt;Импортируйте класс ThemeWrapper. Если у вас уже есть Activity настроек, то импортируйте и её, в противном случае создайте с нуля.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;/res/layout/activity_settings.xml&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;LinearLayout xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;
  android:layout_width=&amp;quot;match_parent&amp;quot;
  android:layout_height=&amp;quot;match_parent&amp;quot;
  xmlns:app=&amp;quot;http://schemas.android.com/apk/res-auto&amp;quot;
  android:orientation=&amp;quot;vertical&amp;quot;&amp;gt;
  &amp;lt;android.support.design.widget.AppBarLayout
    android:layout_width=&amp;quot;match_parent&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;
    android:theme=&amp;quot;@style/AppTheme.AppBarOverlay&amp;quot;&amp;gt;

    &amp;lt;android.support.v7.widget.Toolbar
      android:id=&amp;quot;@+id/toolbar&amp;quot;
      android:layout_width=&amp;quot;match_parent&amp;quot;
      android:layout_height=&amp;quot;?attr/actionBarSize&amp;quot;
      android:background=&amp;quot;?attr/colorPrimary&amp;quot;
      app:popupTheme=&amp;quot;?popupTheme&amp;quot; /&amp;gt;
  &amp;lt;/android.support.design.widget.AppBarLayout&amp;gt;
  &amp;lt;FrameLayout
    android:id=&amp;quot;@+id/frame_container&amp;quot;
    android:layout_width=&amp;quot;match_parent&amp;quot;
    android:layout_height=&amp;quot;match_parent&amp;quot;/&amp;gt;
&amp;lt;/LinearLayout&amp;gt;
&lt;/pre&gt;
  &lt;blockquote&gt;Возможно вам придется добавить библиотеки &lt;code&gt;AppCompat&lt;/code&gt; и &lt;code&gt;Design&lt;/code&gt;, для корректной работы&lt;/blockquote&gt;
  &lt;p&gt;&lt;strong&gt;SettingsActivity.java&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;

import ru.SnowVolf.devtheme.R;



public class SettingsActivity extends BaseActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_settings);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    getFragmentManager()
        .beginTransaction()
        .replace(R.id.frame_container, new SettingsFragment())
        .commit();
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()){
      case android.R.id.home:{
        finish();
      }
    }
    return super.onOptionsItemSelected(item);
  }
}
&lt;/pre&gt;
  &lt;p&gt;Создадим класс &lt;strong&gt;SettingsFragment.java&lt;/strong&gt; с содержимым настроек&lt;/p&gt;
  &lt;pre&gt;import android.content.Intent;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.PreferenceFragment;
import android.content.SharedPreferences;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;


public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {

  @Override
  public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.preferences);
    //Регистрация слушателя настроек
 getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
    //Ставим текущее значение как подзаголовок настройки

setCurrentValue((ListPreference) findPreference(&amp;quot;ui.theme&amp;quot;));
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    //Отключение слушателя настроек
 getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
  }

  private void setCurrentValue(ListPreference listPreference){
    listPreference.setSummary(listPreference.getEntry());
  }

  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
    switch (key) {
      case &amp;quot;ui.theme&amp;quot;: {
        setCurrentValue((ListPreference) findPreference(key));
        LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(new Intent(&amp;quot;ru.svolf.action.REFRESH_THEME&amp;quot;));
        break;
      }
    }
  }
}
&lt;/pre&gt;
  &lt;p&gt;В onCreate мы регистрируем слушателя настроек, для того чтобы данные на экране обновлялись сразу же.&lt;/p&gt;
  &lt;p&gt;В методе &lt;code&gt;onSharedPreferenceChanged&lt;/code&gt; посылаем сигнал для того, чтобы activity пересоздалась. Чтобы это было менее заметно пользователю, мы добавили метод переопределения системных анимаций в &lt;strong&gt;BaseActivity&lt;/strong&gt; (&lt;code&gt;overridePendingTransaction&lt;/code&gt;)&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;/res/xml/preferences.xml разметка для экрана настроек&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;PreferenceScreen xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;&amp;gt;

  &amp;lt;ListPreference
    android:defaultValue=&amp;quot;0&amp;quot;
    android:entries=&amp;quot;@array/theme_names&amp;quot;
    android:entryValues=&amp;quot;@array/theme_values&amp;quot;
    android:key=&amp;quot;ui.theme&amp;quot;
    android:title=&amp;quot;App theme&amp;quot; /&amp;gt;
&amp;lt;/PreferenceScreen&amp;gt;
&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;/res/values/arrays.xml Файл с названиями настроек&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;resources&amp;gt;
  &amp;lt;string-array name=&amp;quot;theme_values&amp;quot; translatable=&amp;quot;false&amp;quot;&amp;gt;
    &amp;lt;item&amp;gt;0&amp;lt;/item&amp;gt;
    &amp;lt;item&amp;gt;1&amp;lt;/item&amp;gt;
  &amp;lt;/string-array&amp;gt;
  &amp;lt;string-array name=&amp;quot;theme_names&amp;quot;&amp;gt;
    &amp;lt;item&amp;gt;Light&amp;lt;/item&amp;gt;
    &amp;lt;item&amp;gt;Dark&amp;lt;/item&amp;gt;
  &amp;lt;/string-array&amp;gt;
&amp;lt;/resources&amp;gt;
&lt;/pre&gt;
  &lt;blockquote&gt;Ещё раз проверьте, всё ли Activity вы зарегистрировали в манифесте. Сделайте это, если забыли.&lt;/blockquote&gt;
  &lt;p&gt;Всё. Теперь вы можете просмотреть на плоды своих трудов, просто скомпилировав проект. Если вы всё сделали правильно, то приложение будет работать как надо.&lt;/p&gt;

</content></entry><entry><id>snowvolf:rJuyiWnwQ</id><link rel="alternate" type="text/html" href="https://teletype.in/@snowvolf/rJuyiWnwQ?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=snowvolf"></link><title>Переключение тем в Android приложении</title><published>2018-09-04T13:38:40.357Z</published><updated>2018-09-04T13:40:08.506Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://teletype.in/files/55/55709271-1893-4ec9-a13a-eaaee493f0df.jpeg"></media:thumbnail><summary type="html">Наверное каждому в этом мире охота иметь право выбора. Даже если это лишь выбор цветовой схемы приложения. Тёмная тема легче воспринимается в вечернее время, а также положительно влияет на энергосбережение (в частности на AMOLED дисплеях)</summary><content type="html">
  &lt;p&gt;Наверное каждому в этом мире охота иметь право выбора. Даже если это лишь выбор цветовой схемы приложения. Тёмная тема легче воспринимается в вечернее время, а также положительно влияет на энергосбережение (в частности на AMOLED дисплеях)&lt;/p&gt;
  &lt;p&gt;Когда-то я хотел сделать изменение темы в приложении &amp;quot;на лету&amp;quot;, но не мог из-за своих малых знаний (от силы 2 месяца программирования). Всё приходит со временем, и, возможно, это тот самый момент, когда вы овладеете полезным навыком.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Хочу сразу предупредить:&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;Пример будет предназначен для новичков, имеющих малый (менее 1 года) опыт разработки под Андроид. В проекте не будет никакой сложной архитектуры, типо MVP, которая только лишь собьёт новичка с толку.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Подразумевается, что:&lt;/strong&gt;&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Вы уже знакомы с методами жизненного цикла Activity, умеете запускать и управлять их состоянием&lt;/li&gt;
    &lt;li&gt;Вы уже знакомы с классом Fragment, умеете помещать несколько фрагментов внутри одной Activity, и управлять ими&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h2&gt;Подготовка&lt;/h2&gt;
  &lt;p&gt;Перейдите в каталог ресурсов вашего проекта, найдите файл &lt;strong&gt;styles.xml&lt;/strong&gt;, и добавьте туда новые темы для приложения.&lt;/p&gt;
  &lt;p&gt;Создайте класс &lt;strong&gt;App&lt;/strong&gt;, который будет наследоваться от &lt;code&gt;android.app.Application&lt;/code&gt;, и напишите следующий код:&lt;/p&gt;
  &lt;pre&gt;import android.app.Application;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;

public class App extends Application {
  private static App instance = null;
  private SharedPreferences preferences;

  public App() {
    super();
  }

  @Override
  public void onCreate() {
    super.onCreate();
    instance = this;
    // Если в вашем приложении есть экран настроек, замените preferences на нужное имя файла
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
  }

  public static App getInstance(){
    if (instance == null)
      instance = new App();
    return instance;
  }

  public SharedPreferences getPreferences(){
    if (preferences == null)
      preferences = PreferenceManager.getDefaultSharedPreferences(this);
    return preferences;
  }
}
&lt;/pre&gt;
  &lt;p&gt;Зарегистрируйте класс в файле &lt;strong&gt;AndroidManifest.xml&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;&amp;lt;application
    android:name=&amp;quot;.App&amp;quot;
    ...
    ...
    .../&amp;gt;
...
...
...
&amp;lt;/application&amp;gt;
&lt;/pre&gt;
  &lt;p&gt;Теперь пройдемся по методам. Мы создали экземпляр класса Application, который сам по себе является синглтоном. Переопределив метод &lt;code&gt;onCreate()&lt;/code&gt;, присвоили значение переменной &lt;code&gt;instance&lt;/code&gt;.&lt;/p&gt;
  &lt;p&gt;В методе &lt;code&gt;getInstance()&lt;/code&gt; мы просто возвращаем переменную instance, которая является экземпляром класса App.&lt;/p&gt;
  &lt;p&gt;В методе &lt;code&gt;getPreferences()&lt;/code&gt; мы возвращаем экземпляр &lt;code&gt;SharedPreferences&lt;/code&gt;, необходимый для хранения настроек.&lt;/p&gt;
  &lt;p&gt;Создайте новый класс, который будет отвечать за смену темы. Я назову его &lt;strong&gt;ThemeWrapper.java&lt;/strong&gt;. напишите какой код:&lt;/p&gt;
  &lt;pre&gt;import android.app.Activity;

public abstract class ThemeWrapper {
  public enum Theme {
    LIGHT,
    DARK
  }

  public static void applyTheme(Activity ctx) {
    int theme;
    switch (Theme.values()[getThemeIndex()]) {
      case LIGHT:
        theme = R.style.AppTheme;
        break;
      case DARK:
        theme = R.style.AppTheme_Dark;
        break;
      default:
        theme = R.style.AppTheme;
        break;
    }
    ctx.setTheme(theme);
  }

  private static int getThemeIndex() {
    return Integer.parseInt(App.getInstance().getPreferences().getString(&amp;quot;ui.theme&amp;quot;, String.valueOf(ThemeWrapper.Theme.LIGHT.ordinal())));
  }
}
&lt;/pre&gt;
  &lt;p&gt;Самостоятельно импортируйте классы &lt;strong&gt;R&lt;/strong&gt; и &lt;strong&gt;App&lt;/strong&gt;.&lt;/p&gt;
  &lt;h2&gt;Внедрение кода&lt;/h2&gt;
  &lt;p&gt;Если ваше приложение использует более чем одно Activity, то лучше всего будет создать некий базовый класс, от которого всё будет наследоваться. Я назвал такой класс &lt;strong&gt;BaseActivity.java&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;



public class BaseActivity extends AppCompatActivity {
  //Theme
  private final BroadcastReceiver mThemeReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      if (SettingsActivity.class.equals(BaseActivity.this.getClass())){
        finish();
        startActivity(getIntent());
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
      } else recreate();
    }
  };

  public BaseActivity(){

  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    //Приёмник сигналов от SettingsActivity
 LocalBroadcastManager.getInstance(this).registerReceiver(mThemeReceiver, new IntentFilter(&amp;quot;ru.svolf.action.REFRESH_THEME&amp;quot;));
    ThemeWrapper.applyTheme(this);
    super.onCreate(savedInstanceState);
  }

  @Override
  public void onResume() {
    super.onResume();
  }

  @Override
  protected void onDestroy() {
    LocalBroadcastManager.getInstance(this).unregisterReceiver(mThemeReceiver);
    super.onDestroy();
  }
}
&lt;/pre&gt;
  &lt;p&gt;Импортируйте класс ThemeWrapper. Если у вас уже есть Activity настроек, то импортируйте и её, в противном случае создайте с нуля.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;/res/layout/activity_settings.xml&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;LinearLayout xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;
  android:layout_width=&amp;quot;match_parent&amp;quot;
  android:layout_height=&amp;quot;match_parent&amp;quot;
  xmlns:app=&amp;quot;http://schemas.android.com/apk/res-auto&amp;quot;
  android:orientation=&amp;quot;vertical&amp;quot;&amp;gt;
  &amp;lt;android.support.design.widget.AppBarLayout
    android:layout_width=&amp;quot;match_parent&amp;quot;
    android:layout_height=&amp;quot;wrap_content&amp;quot;
    android:theme=&amp;quot;@style/AppTheme.AppBarOverlay&amp;quot;&amp;gt;

    &amp;lt;android.support.v7.widget.Toolbar
      android:id=&amp;quot;@+id/toolbar&amp;quot;
      android:layout_width=&amp;quot;match_parent&amp;quot;
      android:layout_height=&amp;quot;?attr/actionBarSize&amp;quot;
      android:background=&amp;quot;?attr/colorPrimary&amp;quot;
      app:popupTheme=&amp;quot;?popupTheme&amp;quot; /&amp;gt;
  &amp;lt;/android.support.design.widget.AppBarLayout&amp;gt;
  &amp;lt;FrameLayout
    android:id=&amp;quot;@+id/frame_container&amp;quot;
    android:layout_width=&amp;quot;match_parent&amp;quot;
    android:layout_height=&amp;quot;match_parent&amp;quot;/&amp;gt;
&amp;lt;/LinearLayout&amp;gt;
&lt;/pre&gt;
  &lt;blockquote&gt;Возможно вам придется добавить библиотеки &lt;code&gt;AppCompat&lt;/code&gt; и &lt;code&gt;Design&lt;/code&gt;, для корректной работы&lt;/blockquote&gt;
  &lt;p&gt;&lt;strong&gt;SettingsActivity.java&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;

import ru.SnowVolf.devtheme.R;



public class SettingsActivity extends BaseActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_settings);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    getFragmentManager()
        .beginTransaction()
        .replace(R.id.frame_container, new SettingsFragment())
        .commit();
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()){
      case android.R.id.home:{
        finish();
      }
    }
    return super.onOptionsItemSelected(item);
  }
}
&lt;/pre&gt;
  &lt;p&gt;Создадим класс &lt;strong&gt;SettingsFragment.java&lt;/strong&gt; с содержимым настроек&lt;/p&gt;
  &lt;pre&gt;import android.content.Intent;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.PreferenceFragment;
import android.content.SharedPreferences;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;


public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {

  @Override
  public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.preferences);
    //Регистрация слушателя настроек
 getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
    //Ставим текущее значение как подзаголовок настройки

setCurrentValue((ListPreference) findPreference(&amp;quot;ui.theme&amp;quot;));
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    //Отключение слушателя настроек
 getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
  }

  private void setCurrentValue(ListPreference listPreference){
    listPreference.setSummary(listPreference.getEntry());
  }

  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
    switch (key) {
      case &amp;quot;ui.theme&amp;quot;: {
        setCurrentValue((ListPreference) findPreference(key));
        LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(new Intent(&amp;quot;ru.svolf.action.REFRESH_THEME&amp;quot;));
        break;
      }
    }
  }
}
&lt;/pre&gt;
  &lt;p&gt;В onCreate мы регистрируем слушателя настроек, для того чтобы данные на экране обновлялись сразу же.&lt;/p&gt;
  &lt;p&gt;В методе &lt;code&gt;onSharedPreferenceChanged&lt;/code&gt; посылаем сигнал для того, чтобы activity пересоздалась. Чтобы это было менее заметно пользователю, мы добавили метод переопределения системных анимаций в &lt;strong&gt;BaseActivity&lt;/strong&gt; (&lt;code&gt;overridePendingTransaction&lt;/code&gt;)&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;/res/xml/preferences.xml разметка для экрана настроек&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;PreferenceScreen xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;&amp;gt;

  &amp;lt;ListPreference
    android:defaultValue=&amp;quot;0&amp;quot;
    android:entries=&amp;quot;@array/theme_names&amp;quot;
    android:entryValues=&amp;quot;@array/theme_values&amp;quot;
    android:key=&amp;quot;ui.theme&amp;quot;
    android:title=&amp;quot;App theme&amp;quot; /&amp;gt;
&amp;lt;/PreferenceScreen&amp;gt;
&lt;/pre&gt;
  &lt;p&gt;&lt;strong&gt;/res/values/arrays.xml Файл с названиями настроек&lt;/strong&gt;&lt;/p&gt;
  &lt;pre&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;resources&amp;gt;
  &amp;lt;string-array name=&amp;quot;theme_values&amp;quot; translatable=&amp;quot;false&amp;quot;&amp;gt;
    &amp;lt;item&amp;gt;0&amp;lt;/item&amp;gt;
    &amp;lt;item&amp;gt;1&amp;lt;/item&amp;gt;
  &amp;lt;/string-array&amp;gt;
  &amp;lt;string-array name=&amp;quot;theme_names&amp;quot;&amp;gt;
    &amp;lt;item&amp;gt;Light&amp;lt;/item&amp;gt;
    &amp;lt;item&amp;gt;Dark&amp;lt;/item&amp;gt;
  &amp;lt;/string-array&amp;gt;
&amp;lt;/resources&amp;gt;
&lt;/pre&gt;
  &lt;blockquote&gt;Ещё раз проверьте, всё ли Activity вы зарегистрировали в манифесте. Сделайте это, если забыли.&lt;/blockquote&gt;
  &lt;p&gt;Всё. Теперь вы можете просмотреть на плоды своих трудов, просто скомпилировав проект. Если вы всё сделали правильно, то приложение будет работать как надо.&lt;/p&gt;

</content></entry></feed>