Як перевірити, чи активність на передньому плані чи на видимому тлі?


104

У мене на таймері сплеск екрану. Моя проблема полягає в тому, що перед початком finish()своєї діяльності мені потрібно перевірити, чи почалася наступна діяльність, тому що з'являється системне вікно діалогу, і я хочу лише цього finish(); як тільки користувач обрав варіант із діалогового вікна?

Я знаю, що є багато запитань щодо того, як дізнатись, чи є ваша активність на першому плані, але я не знаю, чи це дозволяє діалогові вікна і над активністю.

Ось проблема, червона - це моя діяльність, яка знаходиться на задньому плані, тоді як діалог на першому плані:

червоний - це моя діяльність, яка знаходиться на задньому плані, тоді як діалог на першому плані

EDIT: Я намагався просто не використовувати, finish()але потім моя активність може бути повернута до того, що я намагаюся уникати.


Може бути доречним: stackoverflow.com/questions/4414171 / ...
jarmod

Щоб уточнити, ви хочете запустити інструмент вибору намірів і чекати, коли ваша програма закінчиться (), поки користувач не натисне один із варіантів? Здається, що вам потрібні Intent.createChooser () та startActivityForResult () з подальшим завершенням (), коли результат буде отриманий.
alanv


ProcessLifecycleOwner - це найновіше рішення
SR

Відповіді:


189

Ось що рекомендується як правильне рішення:

Правильне рішення (кредити надаються Dan, CommonsWare та NeTeInStEiN) самостійно відстежуйте видимість вашої програми за допомогою методів Activity.onPause, Activity.onResume. Зберігайте статус "видимості" в якомусь іншому класі. Хороший вибір - це ваша власна реалізація Додатку чи Служби (також існує кілька варіантів цього рішення, якщо ви хочете перевірити видимість діяльності від служби).

Приклад Реалізація користувацького класу додатків (зверніть увагу на статичний метод isActivityVisible ()):

public class MyApplication extends Application {

  public static boolean isActivityVisible() {
    return activityVisible;
  }  

  public static void activityResumed() {
    activityVisible = true;
  }

  public static void activityPaused() {
    activityVisible = false;
  }

  private static boolean activityVisible;
}

Зареєструйте свій клас заявки в AndroidManifest.xml:

<application
    android:name="your.app.package.MyApplication"
    android:icon="@drawable/icon"
    android:label="@string/app_name" >

Додайте OnPause та onResume до кожної діяльності в проекті (ви можете створити спільного предка для вашої діяльності, якщо хочете, але якщо ваша діяльність вже розширена від MapActivity / ListActivity тощо, вам все одно потрібно написати вручну) :

@Override
protected void onResume() {
  super.onResume();
  MyApplication.activityResumed();
}

@Override
protected void onPause() {
  super.onPause();
  MyApplication.activityPaused();
}

У своєму finish()методі ви хочете скористатися, isActivityVisible()щоб перевірити, видимість діяльності чи ні. Там ви також можете перевірити, чи вибрав користувач варіант чи ні. Продовжуйте, коли виконані обидві умови.

Джерело також згадує два неправильних рішення ... тому уникайте цього робити.

Джерело: stackoverflow


Між фінішною і стартовою діяльністю є один маленький момент, і мені тонко потрібно додати трохи затримки та лічильника
sagus_helgy

28
Це не працює надійно. У вас може виникнути така ситуація: Відновіть резюме B Пауза А. Тепер активність Visis false, тоді як програма видима. Можливо, ви використовуєте лічильник видимості: visibleCounter ++ в onResume і видимийCounter - в onPause.
Йоріс Веймар

4
Погодився з Йоріс Веймар, що це не дурне рішення. Один з сценаріїв не є користувач розібрали панель повідомлень, а потім ні onPause, onStop, ні onResumeподія називається. То що робити тоді, якщо жодна з цих подій не буде звільнена ?!

1
Насправді, жоден з інших відповідей також не працює на 100%.

2
Якщо у додатку є кілька заходів, ця схема не працюватиме. Замініть лічильники принаймні
ruX

70

Якщо орієнтувати API рівня 14 або вище, можна використовувати android.app.Application.ActivityLifecycleCallbacks

public class MyApplication extends Application implements ActivityLifecycleCallbacks {
    private static boolean isInterestingActivityVisible;

    @Override
    public void onCreate() {
        super.onCreate();

        // Register to be notified of activity state changes
        registerActivityLifecycleCallbacks(this);
        ....
    }

    public boolean isInterestingActivityVisible() {
        return isInterestingActivityVisible;
    }

    @Override
    public void onActivityResumed(Activity activity) {
        if (activity instanceof MyInterestingActivity) {
             isInterestingActivityVisible = true;
        }
    }

    @Override
    public void onActivityStopped(Activity activity) {
        if (activity instanceof MyInterestingActivity) {
             isInterestingActivityVisible = false;
        }
    }

    // Other state change callback stubs
    ....
}

18
Ви можете просто зробити це у зворотній зворотній зв'язок життєвого циклу активності (onResume (), onStop ()), я б також сказав.
Даніель Вілсон

5
@DanielWilson Я думаю, що справа не в тому, щоб побудувати систему для того, щоб щось робити там, де вже є. ІМХО, це має бути прийнятою відповіддю.
Джефрі Блатман

26

UPD : оновлено до стану Lifecycle.State.RESUMED. Завдяки @htafoya за це.

У 2019 році за допомогою нової бібліотеки підтримки 28+або AndroidX ви можете просто використовувати:

val isActivityInForeground = activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)

Ви можете прочитати більше в документації, щоб зрозуміти, що сталося під капотом.


2
Не дуже, напевно, це краще розмістити activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) або НАЧАЛО. INITIALIZEDне гарантує, що вона на першому плані.
хтафоя

11

Діяльність :: hasWindowFocus () повертає вам потрібні булі .

public class ActivityForegroundChecker extends TimerTask
{
    private static final long FOREGROUND_CHECK_PERIOD = 5000;
    private static final long FIRST_DELAY             = 3000;

    private Activity m_activity;
    private Timer    m_timer;

    public ActivityForegroundChecker (Activity p_activity)
    {
        m_activity = p_activity;
    }

    @Override
    public void run()
    {
        if (m_activity.hasWindowFocus() == true) {
            // Activity is on foreground
            return;
        }
        // Activity is on background.
    }

    public void start ()
    {
        if (m_timer != null) {
            return;
        }
        m_timer = new Timer();
        m_timer.schedule(this, FIRST_DELAY, FOREGROUND_CHECK_PERIOD);
    }

    public void stop ()
    {
        if (m_timer == null) {
            return;
        }
        m_timer.cancel();
        m_timer.purge();
        m_timer = null;
    }
}

Ось приклад класу, щоб перевірити видимість вашої активності з будь-якого місця.

Пам’ятайте, що якщо ви показуєте діалогове вікно , результат буде помилковим, оскільки діалог матиме основну увагу. Крім цього, це дійсно зручно і надійніше, ніж пропоновані рішення.


1
Дякую за те, що внесли зміни до відповіді на @Burak Day, це насправді відповідь зараз
Нік

Це не працює, я б скоріше скористався бульовим властивістю у класі, встановленому на true у OnResume, і встановив би значення false у OnPause ();
Чандлер

@Chandler Яка точна проблема у вас із цим кодом? Також яка версія?
Бурак День

@Chandler також, що робити, якщо у вас немає доступу до методів життєвого циклу діяльності. Подумайте, ви просто перевіряєте видимість діяльності з бібліотеки.
Бурак День

Справжня проблема цієї відповіді полягає в тому, що вона НЕ працює, Activity.hasWindowFocus є правдою, не може гарантувати, що активність знаходиться між станом onResume та onPause. Я б скоріше рекомендував додати властивість bool isResumed у цій діяльності, встановити значення вручну та додати метод get.
Чандлер

10

Саме ця різниця між подіями onPauseта onStopподіями діяльності описана в документації про клас діяльності .

Якщо я вас правильно зрозумів, то, що ви хочете зробити, - це закликати finish()вашу діяльність onStopприпинити її. Дивіться додане зображення демо-програми для життєвого циклу діяльності . Ось так виглядає, коли активність B запускається з діяльності А. Порядок подій знаходиться знизу вгору, щоб ви могли бачити, що активність A onStopвикликається після того, як активність B onResumeвже була викликана.

Демонстрація життєвого циклу діяльності

У випадку, якщо відображається діалогове вікно, ваша активність затьмарюється у фоновому режимі і лише onPauseвикликається.


7

Два можливих рішення:

1) Відкликання викликів LifeCycle

Використовуйте додаток, який реалізує ActivityLifecycleCallbacks, і використовуйте його для відстеження подій життєвого циклу діяльності у вашому додатку. Зауважте, що ActivityLifecycleCallbacks призначений для Android api> = 14. Для попереднього Android api вам потрібно буде реалізувати його самостійно у всіх своїх заходах ;-)

Використовуйте програму тоді, коли вам потрібно ділитися / зберігати штати в межах всієї діяльності.

2) Перевірте наявність відомостей про процес

Ви можете перевірити стан запущеного процесу за допомогою цього класу RunningAppProcessInfo

Отримайте список запущених процесів за допомогою ActivityManager.getRunningAppProcess () і відфільтруйте список результатів, щоб перевірити бажаний RunningAppProcessInfo і перевірити його "важливість"



3

Використовуйте часовий проміжок між паузою та відновленням з фонового режиму, щоб визначити, чи вона прокидається з фону

У користувацькому додатку

private static boolean isInBackground;
private static boolean isAwakeFromBackground;
private static final int backgroundAllowance = 10000;

public static void activityPaused() {
    isInBackground = true;
    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            if (isInBackground) {
                isAwakeFromBackground = true;
            }
        }
    }, backgroundAllowance);
    Log.v("activity status", "activityPaused");
}

public static void activityResumed() {
    isInBackground = false;
    if(isAwakeFromBackground){
        // do something when awake from background
        Log.v("activity status", "isAwakeFromBackground");
    }
    isAwakeFromBackground = false;
    Log.v("activity status", "activityResumed");
}

У класі BaseActivity

@Override
protected void onResume() {
  super.onResume();
  MyApplication.activityResumed();
}

@Override
protected void onPause() {
  super.onPause();
  MyApplication.activityPaused();
}

3

Я думаю, що в мене є краще рішення. Тому що ви можете вбудувати просто MyApplication.activityResumed (); до кожної діяльності на одне розширення.

По-перше, ви повинні створити (наприклад, CyberneticTwerkGuruOrc)

public class MyApplication extends Application {

  public static boolean isActivityVisible() {
    return activityVisible;
  }  

  public static void activityResumed() {
    activityVisible = true;
  }

  public static void activityPaused() {
    activityVisible = false;
  }

  private static boolean activityVisible;
}

Далі вам потрібно додати клас програми до AndroidManifest.xml

<application
    android:name="your.app.package.MyApplication"
    android:icon="@drawable/icon"
    android:label="@string/app_name" >

Потім створіть клас ActivityBase

public class ActivityBase extends Activity {

    @Override
    protected void onPause() {
        super.onPause();
        MyApplication.activityPaused();
    }

    @Override
    protected void onResume() {
        super.onResume();
        MyApplication.activityResumed();
    }
}

Нарешті, коли ви створюєте нову діяльність, ви можете просто розширити її на ActivityBase замість Activity.

public class Main extends ActivityBase {
    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }
}

Для мене це кращий метод, тому що вам доведеться просто пам’ятати про розширення від ActivityBase. Крім того, ви можете розширити свою базову функцію в майбутньому. У моєму випадку я додав приймачі для своєї послуги та попередження про мережу в одному класі.

Якщо ви хочете перевірити видимість свого додатка, ви можете просто зателефонувати

MyApplication.isActivityVisible()

Що робити, якщо мені потрібні заходи для розширення AppCombatActivity?
winklerrr

2

Цього можна досягти ефективним шляхом, використовуючи Application.ActivityLifecycleCallbacks

Наприклад, давайте взяти ім’я класу Activity як ProfileActivity дозволяє виявити, чи знаходиться він на передньому плані чи на задньому плані

спершу нам потрібно створити наш клас додатків, розширивши Application Application

який реалізує

Application.ActivityLifecycleCallbacks

Дозволяє бути моїм додатком клас наступним чином

Клас застосування

public class AppController extends Application implements Application.ActivityLifecycleCallbacks {


private boolean activityInForeground;

@Override
public void onCreate() {
    super.onCreate();

//register ActivityLifecycleCallbacks  

    registerActivityLifecycleCallbacks(this);

}



public static boolean isActivityVisible() {
    return activityVisible;
}

public static void activityResumed() {
    activityVisible = true;
}

public static void activityPaused() {
    activityVisible = false;
}

private static boolean activityVisible;

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

}

@Override
public void onActivityStarted(Activity activity) {

}

@Override
public void onActivityResumed(Activity activity) {
    //Here you can add all Activity class you need to check whether its on screen or not

    activityInForeground = activity instanceof ProfileActivity;
}

@Override
public void onActivityPaused(Activity activity) {

}

@Override
public void onActivityStopped(Activity activity) {

}

@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

}

@Override
public void onActivityDestroyed(Activity activity) {

}

public boolean isActivityInForeground() {
    return activityInForeground;
}
}

у вищевказаному класі є перекриття метрики onActivityResumed of ActivityLifecycleCallbacks

 @Override
public void onActivityResumed(Activity activity) {
    //Here you can add all Activity class you need to check whether its on screen or not

    activityInForeground = activity instanceof ProfileActivity;
}

де можна знайти весь екземпляр активності, який наразі відображається на екрані, просто перевірте, чи Ваша активність відображається на екрані чи ні за вказаним вище способом.

Зареєструйте свій клас програми у manifest.xml

<application
    android:name=".AppController" />

Щоб перевірити погоду Активність - це передній план або фон відповідно до вищезазначеного рішення, виберіть наступний метод у місцях, які вам потрібно перевірити

AppController applicationControl = (AppController) getApplicationContext();
    if(applicationControl.isActivityInForeground()){
     Log.d("TAG","Activity is in foreground")
    }
    else
    {
      Log.d("TAG","Activity is in background")
    }

1

Якщо ви хочете дізнатися, чи відображається якась активність вашої програми на екрані, ви можете зробити щось подібне:

public class MyAppActivityCallbacks implements Application.ActivityLifecycleCallbacks {
private Set<Class<Activity>> visibleActivities = new HashSet<>();

@Override
public void onActivityResumed(Activity activity) {
    visibleActivities.add((Class<Activity>) activity.getClass());
}

@Override
public void onActivityStopped(Activity activity) {
     visibleActivities.remove(activity.getClass());
}

public boolean isAnyActivityVisible() {
    return !visibleActivities.isEmpty();
}

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}

@Override
public void onActivityStarted(Activity activity) {}

@Override
public void onActivityPaused(Activity activity) {}

@Override
public void onActivityDestroyed(Activity activity) {}

@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}}

Просто створіть синглтон цього класу та встановіть його в екземплярі програми, як показано нижче:

class App extends Application{
     @Override
     public void onCreate() {
         registerActivityLifecycleCallbacks(myAppActivityCallbacks);
     }
}

Тоді ви можете використовувати метод isAnyActivityVisible () вашого примірника MyAppActivityCallbacks скрізь!


0

ви намагалися не викликати закінчення, а вводити "android: noHistory =" true "в маніфест? це не дозволить активності перейти до стеку.


0

Треба сказати, що ваш робочий процес не є стандартним способом Android. В Android вам не потрібно займатися finish()своєю діяльністю, якщо ви хочете відкрити іншу діяльність від Intent. Що стосується зручності користувача, Android дозволяє користувачу користуватися клавішею "назад", щоб повернутися з активності, яку ви відкрили у вашому додатку.

Тож просто дозвольте системі зупинити вашу активність і збережіть все необхідне, коли ви активізуєтеся.


Я дійсно не знаю, чому це зменшено. Принаймні дати коментар як причину - це те, що я хочу.
Оуен Чжао

3
"Буу, це не андроїд", відповіді втомлюють і не відповідають на питання, поставлене в першу чергу. крім того, є вагомі причини закінчити (); - наприклад, можливо, що повернення до нього після того, як дія відбулася, не має жодної мети. Іншими словами, ти думаєш, що вони там повеселилися? саме перебування на стеці - це саме те, що запитувач хотів уникнути
Лассі Кіннунен

0

Збережіть прапор, якщо ви призупинили або відновили. Якщо ви поновлюєтесь, це означає, що ви на першому плані

boolean  isResumed = false;

@Override
public void onPause() {
  super.onPause();    
  isResumed = false;
}

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

private void finishIfForeground() {
  if (isResumed) {
    finish();
  }
}

0

Одним із можливих рішень може бути встановлення прапора під час показу діалогового вікна системи, а потім у методі onStop життєвого циклу діяльності перевірити прапор, якщо це правда, завершити діяльність.

Наприклад, якщо системне діалогове вікно запускається деяким клацанням кнопки, то слухач onclick може бути схожим

private OnClickListener btnClickListener = new OnClickListener() {

    @Override
    public void onClick(View v) {           
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_SEND);
        intent.setType("text/plain");
        CheckActivity.this.startActivity(Intent.createChooser(intent, "Complete action using"));
        checkFlag = true;  //flag used to check

    }
};

і при зупинці діяльності:

@Override
protected void onStop() {
    if(checkFlag){
        finish();
    }
    super.onStop();
}

0

Чому б не використовувати для цього трансляції? другий вид діяльності (той, який потрібно здійснити) може надіслати локальну трансляцію, як це:

//put this in onCreate(..) or any other lifecycle method that suits you best
//notice the string sent to the intent, it will be used to register a receiver!
Intent result = new Intent("broadcast identifier");
result.putString("some message");//this is optional
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(result);

тоді напишіть простий приймач у межах дії сплеску:

//this goes on the class level (like a class/instance variable, not in a method) of your splash activity:
private BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        //kill activity here!!!
        //mission accomplished!
    }
};

і зареєструйте новий приймач у LocalBroadcastManager, щоб прослухати трансляцію зі свого другого заняття:

//notice the string sent to the intent filter, this is where you tell the BroadcastManager which broadcasts you want to listen to!
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(receiver, new IntentFilter("broadcast identifier"));

Зверніть увагу, що ви можете використовувати константний або рядковий ресурс для рядка "ідентифікатор широкомовної передачі".


Для кращої ефективності використання реклами використовуйте LocalBroadcastManagerтут
Олександр Фарбер

0

Якщо ви використовуєте finish()лише для уникнення запуску нового додатка в стеку (завдання) програми, ви можете використовувати Intent.FLAG_ACTIVITY_NEW_TASKпрапор під час запуску нової програми і взагалі не дзвонити finish(). Згідно з документацією , це прапор, який слід використовувати для реалізації стилю поведінки "пускової".

// just add this line before you start an activity
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

0

Використовуйте ці методи всередині Activity.

isDestroyed()

Додано в Api 17
Повертає істину, якщо остаточний виклик onDestroy () був здійснений у розділі "Активність", тому цей екземпляр тепер мертвий.

isFinishing()

Додано в Api 1
Перевірте, чи закінчується ця діяльність, тому що ви викликали на ній закінчення () або хтось інший просив її завершити. Це часто використовується в OnPause (), щоб визначити, чи просто діяльність призупиняється або повністю закінчується.


З документації про витоки пам'яті

Поширена помилка з AsyncTask- це чітке посилання на хоста Activity(або Fragment):

class MyActivity extends Activity {
  private AsyncTask<Void, Void, Void> myTask = new AsyncTask<Void, Void, Void>() {
    // Don't do this! Inner classes implicitly keep a pointer to their
    // parent, which in this case is the Activity!
  }
}

Це проблема, оскільки AsyncTaskможе легко пережити батьківський Activity, наприклад, якщо зміна конфігурації відбувається під час виконання завдання.

Правильний спосіб зробити це - зробити своє завдання staticкласом, який не охоплює батьківського, і має слабке посилання на хост Activity:

class MyActivity extends Activity {
  static class MyTask extends AsyncTask<Void, Void, Void> {
    // Weak references will still allow the Activity to be garbage-collected
    private final WeakReference<MyActivity> weakActivity;

    MyTask(MyActivity myActivity) {
      this.weakActivity = new WeakReference<>(myActivity);
    }

    @Override
    public Void doInBackground(Void... params) {
      // do async stuff here
    }

    @Override
    public void onPostExecute(Void result) {
      // Re-acquire a strong reference to the activity, and verify
      // that it still exists and is active.
      MyActivity activity = weakActivity.get();
      if (activity == null
          || activity.isFinishing()
          || activity.isDestroyed()) {
        // activity is no longer valid, don't do anything!
        return;
      }

      // The activity is still valid, do main-thread stuff here
    }
  }
}

0

Ось рішення за допомогою Applicationкласу.

public class AppSingleton extends Application implements Application.ActivityLifecycleCallbacks {

private WeakReference<Context> foregroundActivity;


@Override
public void onActivityResumed(Activity activity) {
    foregroundActivity=new WeakReference<Context>(activity);
}

@Override
public void onActivityPaused(Activity activity) {
    String class_name_activity=activity.getClass().getCanonicalName();
    if (foregroundActivity != null && 
            foregroundActivity.get().getClass().getCanonicalName().equals(class_name_activity)) {
        foregroundActivity = null;
    }
}

//............................

public boolean isOnForeground(@NonNull Context activity_cntxt) {
    return isOnForeground(activity_cntxt.getClass().getCanonicalName());
}

public boolean isOnForeground(@NonNull String activity_canonical_name) {
    if (foregroundActivity != null && foregroundActivity.get() != null) {
        return foregroundActivity.get().getClass().getCanonicalName().equals(activity_canonical_name);
    }
    return false;
}
}

Ви можете просто використовувати його наступним чином,

((AppSingleton)context.getApplicationContext()).isOnForeground(context_activity);

Якщо у вас є посилання на потрібну діяльність або ви використовуєте канонічну назву діяльності, ви можете дізнатися, чи є вона на передньому плані чи ні. Це рішення не може бути надійним. Тому ваші коментарі дуже вітаються.


0

Я не знаю, чому ніхто не говорив про спільніПреференції, для Діяння А, встановивши поділену подію (наприклад, у OnPause ()):

SharedPreferences pref = context.getSharedPreferences(SHARED_PREF, 0);
SharedPreferences.Editor editor = pref.edit();
editor.putBoolean("is_activity_paused_a", true);
editor.commit();

Я думаю, що це надійний спосіб відстежувати видимість діяльності.


0

Були Activity.onWindowFocusChanged(boolean hasFocus)б корисні тут? Це, плюс прапор на рівні класу, що - щось на зразок , isFocusedщоonWindowFocusChanged набори, було б простий спосіб сказати в будь-який момент у вашому діяльності , якщо вона орієнтована чи ні. Якщо читати документи, схоже, що це було б правильно встановлено "false" у будь-якій ситуації, коли діяльність не знаходиться безпосередньо на фізичному "передньому плані", наприклад, якщо відображається діалогове вікно або знімається лоток сповіщень.

Приклад:

boolean isFocused;
@Override
void onWindowFocusChanged (boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    isFocused = hasFocus;
}

void someMethod() {
    if (isFocused) {
        // The activity is the foremost object on the screen
    } else {
        // The activity is obscured or otherwise not visible
    }
}

0

Якщо ви використовуєте EventBus , це як метод, hasSubscriberForEventякий називається, який можна використовувати, щоб перевірити, чи Activityфокус є.


-3

Я колись робив таке,

якщо діяльність не на першому плані

getIntent ()

поверне нульове значення. : = P

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.