Подтвердить что ты не робот

Правильно запустите Activity from Notification независимо от состояния приложения

У меня есть приложение с заставкой Activity Activity, за которой следует основное действие. Заставка запускает материал (базу данных и т.д.) Перед началом основной операции. Из этого основного действия пользователь может перейти к нескольким другим дочерним действиям и обратно. Некоторые дочерние действия запускаются с использованием startActivityForResult(), другие - только startActivity().

Иерархия активности показана ниже.

|                    Child A (startActivityForResult)
|                   /
|--> Splash --> Main -- Child B (startActivityForResult)
|      ^            \
|      |             Child C (startActivity)
|       \
|        This Activity is currently skipped if a Notification is started
|        while the app is not running or in the background.

При нажатии кнопки Notification мне нужно выполнить следующее поведение :

  • Состояние в Управлении должно поддерживаться, так как пользователь выбрал несколько рецептов для создания списка покупок. Если новая активность запущена, я считаю, что состояние будет потеряно.
  • Если приложение находится в главной операции, принесите это на передний план и сообщите мне в коде, который я получил из Уведомления.
  • Если приложение находится в дочернем режиме. Активность, начинающаяся с startActivityForResult(), мне нужно добавить данные в намерение, прежде чем возвращаться к основной активности, чтобы она могла правильно поймать результат.
  • Если приложение находится в дочернем режиме. Активность, начинающаяся с startActivity(), мне просто нужно вернуться, поскольку больше нечего делать (в настоящее время это работает).
  • Если приложение не находится в фоновом режиме или переднем плане (то есть оно не работает), я должен запустить Main Activity, а также знать, что я пришел из Notification, чтобы я мог настроить вещи, которые не настроены тем не менее, поскольку в этом случае функция Splash Activity пропускается в моей текущей настройке.

Я пробовал много различных предложений здесь, в SO и в других местах, но я не смог успешно получить описанное выше поведение. Я также пробовал читать документацию, но не стал намного мудрее, немного. Моя текущая ситуация для вышеуказанных случаев при нажатии на мое уведомление:

  • Я прихожу в основную деятельность в onNewIntent(). Я не прихожу сюда, если приложение не работает (или в фоновом режиме). Это кажется ожидаемым и желаемым.
  • Я не могу понять, что я исхожу из уведомления в любом дочернем мероприятии, поэтому я не могу правильно называть setResult() в этих действиях. Как мне это сделать?
  • В настоящее время это работает, поскольку уведомление просто закрывает дочернюю активность, что нормально.
  • Я могу получить намерение уведомления в onCreate() с помощью getIntent() и Intent.getBooleanExtra() с булевым набором в уведомлении. Поэтому я должен был бы заставить его работать, но я не уверен, что это лучший способ. Каков предпочтительный способ сделать это?

Текущий код

Создание уведомления:

Уведомление создается, когда HTTP-запрос внутри Сервиса возвращает некоторые данные.

NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
        .setSmallIcon(getNotificationIcon())
        .setAutoCancel(true)
        .setColor(ContextCompat.getColor(context, R.color.my_brown))
        .setContentTitle(getNotificationTitle(newRecipeNames))
        .setContentText(getContentText(newRecipeNames))
        .setStyle(new NotificationCompat.BigTextStyle().bigText("foo"));

Intent notifyIntent = new Intent(context, MainActivity.class);
notifyIntent.setAction(Intent.ACTION_MAIN);
notifyIntent.addCategory(Intent.CATEGORY_LAUNCHER);

notifyIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);

/* Add a thing to let MainActivity know that we came from a Notification. */
notifyIntent.putExtra("intent_bool", true);

PendingIntent notifyPendingIntent = PendingIntent.getActivity(context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(notifyPendingIntent);

NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(111, builder.build());

MainActivity.java:

@Override
protected void onCreate(Bundle savedInstanceState)
{
    Intent intent = getIntent();
    if (intent.getBooleanExtra("intent_bool", false))
    {
        // We arrive here if the app was not running, as described in point 4 above.
    }

    ...
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
    switch (requestCode)
    {
        case CHILD_A:
            // Intent data is null here when starting from Notification. We will thus crash and burn if using it. Normally data has values when closing CHILD_A properly.
            // This is bullet point 2 above.
            break;

        case CHILD_B:
            // Same as CHILD_A
            break;
    }

    ...
}

@Override
protected void onNewIntent(Intent intent)
{
    super.onNewIntent(intent);
    boolean arrivedFromNotification = intent.getBooleanExtra("intent_bool", false);
    // arrivedFromNotification is true, but onNewIntent is only called if the app is already running.
    // This is bullet point 1 above.
    // Do stuff with Intent.
    ... 
}

Внутри дочернего элемента Действие началось с startActivityForResult():

@Override
protected void onNewIntent(Intent intent)
{
    // This point is never reached when opening a Notification while in the child Activity.
    super.onNewIntent(intent);
}

@Override
public void onBackPressed()
{
    // This point is never reached when opening a Notification while in the child Activity.

    Intent resultIntent = getResultIntent();
    setResult(Activity.RESULT_OK, resultIntent);

    // NOTE! super.onBackPressed() *must* be called after setResult().
    super.onBackPressed();
    this.finish();
}

private Intent getResultIntent()
{
    int recipeCount = getRecipeCount();
    Recipe recipe   = getRecipe();

    Intent recipeIntent = new Intent();
    recipeIntent.putExtra(INTENT_RECIPE_COUNT, recipeCount);
    recipeIntent.putExtra(INTENT_RECIPE, recipe);

    return recipeIntent;
}

AndroidManifest.xml:

<application
    android:allowBackup="true"
    android:icon="@mipmap/my_launcher_icon"
    android:label="@string/my_app_name"
    android:theme="@style/MyTheme"
    android:name="com.mycompany.myapp.MyApplication" >

    <activity
        android:name="com.mycompany.myapp.activities.SplashActivity"
        android:screenOrientation="portrait" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <activity
        android:name="com.mycompany.myapp.activities.MainActivity"
        android:label="@string/my_app_name"
        android:screenOrientation="portrait"
        android:windowSoftInputMode="adjustPan" >
    </activity>

    <activity
        android:name="com.mycompany.myapp.activities.ChildActivityA"
        android:label="@string/foo"
        android:parentActivityName="com.mycompany.myapp.activities.MainActivity"
        android:screenOrientation="portrait"
        android:windowSoftInputMode="adjustPan" >
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.mycompany.myapp.activities.MainActivity" >
        </meta-data>
    </activity>

    <activity
        android:name="com.mycompany.myapp.activities.ChildActivityB"
        android:label="@string/foo"
        android:parentActivityName="com.mycompany.myapp.activities.MainActivity"
        android:screenOrientation="portrait" >
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.mycompany.myapp.activities.MainActivity" >
        </meta-data>
    </activity>

    ...
</manifest>
4b9b3361

Ответ 1

Такой сложный вопрос: D Вот как вы должны относиться к этой проблеме:

  • Используйте IntentService в своем уведомлении вместо Intent notifyIntent = new Intent(context, MainActivity.class);

теперь, всякий раз, когда пользователь нажимает на уведомление, будет вызван intentservice.

  1. в службе намерений, трансляция чего-то.

  2. в OnResume всей вашей желаемой активности зарегистрируйте прослушиватель вещания (для трансляции, которую вы создаете во второй фазе), и в OnPause отмените ее регистрацию

к настоящему времени всякий раз, когда вы находитесь в какой-либо деятельности, и пользователь нажимает на уведомление, вы будете проинформированы без каких-либо проблем и без какого-либо воссоздания активности.

  1. в вашем классе Application определите public Boolean. позволяет назвать его APP_IS_RUNNING = false; в вашей MainActivity, в OnPause сделать его ложным, а в OnResume сделать его истинным;

Посредством этого вы можете понять, что ваше приложение работает или нет или находится в фоновом режиме.

ПРИМЕЧАНИЕ. Если вы хотите обрабатывать большее количество состояний, например isInBackground, Running, Destroyed и т.д., вы можете использовать перечисление или что угодно,

Вы хотите делать разные вещи, когда приложение работает, правда? поэтому в службе намерений, которую вы объявили в 1-й фазе, проверьте параметр, который вы определили в своем классе приложений. (я имею в виду APP_IS_RUNNING в нашем примере), если это правда, использовать трансляцию и иначе вызвать намерение, которое открывает желаемую активность.

Ответ 2

Вы идете неправильно, приятель. onActivityResult не является решением. Просто простой ответ на этот вопрос: использовать широковещательный приемник

Объявление действия В файле манифест:

<receiver android:name="com.myapp.receiver.AudioPlayerBroadcastReceiver" >
            <intent-filter>
                <action android:name="com.myapp.receiver.ACTION_PLAY" />
<!-- add as many actions as you want here -->
</intent-filter>
        </receiver>

Создайте класс получателя:

public class AudioPlayerBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if(action.equalsIgnoreCase("com.myapp.receiver.ACTION_PLAY")){

  Myactivity.doSomething(); //access static method of your activity
// do whatever you want to do for this specific action
//do things when the button is clicked inside notification.
         }
    }
}

В вашем методе setNotification()

Notification notification = new Notification.Builder(this).
                    setWhen(System.currentTimeMillis())
                    .setSmallIcon(R.drawable.no_art).build();
RemoteView remoteview = new RemoteViews(getPackageName(), R.layout.my_notification);

notification.contentView = remoteview;

Intent playIntent = new Intent("com.myapp.receiver.ACTION_PLAY");
        PendingIntent playSwitch = PendingIntent.getBroadcast(this, 100, playIntent, 0);
        remoteview.setOnClickPendingIntent(R.id.play_button_my_notification, playSwitch); 
//this handle view click for the specific action for this specific ID used in broadcast receiver

Теперь, когда пользователь нажимает кнопку в уведомлении и broacast receive, r будет захватывать это событие и выполнять действие.

Ответ 3

Вот что я в итоге сделал. Это рабочее решение, и каждая ситуация состояния приложения, дочерняя активность и т.д. Тестируется. Дальнейшие комментарии высоко ценятся.

Создание уведомления

Уведомление по-прежнему создается как в исходном вопросе. Я попытался использовать IntentService с трансляцией, как это предложил @Smartiz. Это нормально работает, пока приложение работает; зарегистрированный ребенок. Деятельность получает трансляцию, и мы можем делать то, что нам нравится с этого момента, например, заботиться о государстве. Проблема, однако, в том, что приложение не работает на переднем плане. Затем мы должны использовать флаг Intent.FLAG_ACTIVITY_NEW_TASK в Intent для трансляции с IntentService (Android требует этого), поэтому мы создадим новый стек, и все начнет запутываться. Это, вероятно, можно обойти, но я думаю, что легче сохранить состояние, используя SharedPreferences или подобные вещи, как указывали другие. Это также более полезный способ хранения постоянного состояния.

Таким образом, уведомление просто создается по-прежнему:

NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
        .setSmallIcon(getNotificationIcon())
        .setAutoCancel(true)
        .setColor(ContextCompat.getColor(context, R.color.my_brown))
        .setContentTitle(getNotificationTitle(newRecipeNames))
        .setContentText(getContentText(newRecipeNames))
        .setStyle(new NotificationCompat.BigTextStyle().bigText("foo"));

Intent notifyIntent = new Intent(context, MainActivity.class);
notifyIntent.setAction(Intent.ACTION_MAIN);
notifyIntent.addCategory(Intent.CATEGORY_LAUNCHER);

notifyIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);

/* Add a thing to let MainActivity know that we came from a Notification.
Here we can add other data we desire as well. */
notifyIntent.putExtra("intent_bool", true);

PendingIntent notifyPendingIntent = PendingIntent.getActivity(context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(notifyPendingIntent);

NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(111, builder.build());

Состояние сохранения

В дочерней операции, которая должна сохранять состояние, я просто сохраняю в SharedPreferences в onPause(). Таким образом, это состояние можно повторно использовать везде, где это необходимо в более поздней точке. Это также очень полезный способ хранения состояния более общим способом. Я не знал об этом, так как думал, что SharedPreferences зарезервированы для предпочтений, но его можно использовать для чего угодно. Хотелось бы, чтобы я это осознал раньше.

Открытие уведомления

Теперь, когда вы открываете уведомление, происходят следующие события, в зависимости от состояния приложения, и какая дочерняя активность открыта/приостановлена. Помните, что используются флаги Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP.

A. Деятельность ребенка

  • Запуск в фронте: дочерняя активность закрыта, соответствующее состояние сохраняется с помощью SharedPreferences в onPause и может быть выбрано в onCreate или везде, где есть основное действие.
  • Приложение находится в фоновом режиме: такое же поведение.
  • Приложение находится в фоновом режиме, но убито ОС (проверено с помощью adb shell: на данный момент нет стека, поэтому MainActivity открывается. Однако приложение находится в грязном состоянии, поэтому я возвращаю это намерение назад на экран заставки с входящими данными и обратно на главную активность. Состояние снова сохраняется в onPause в дочерней активности, когда пользователь закрыл его, и его можно извлечь в основном действии.

В. Основная деятельность

  • Бег впереди: намерение попадает в onNewIntent, и все золото. Делайте то, что мы хотим.
  • Приложение находится в фоновом режиме: такое же поведение.
  • Приложение находится в фоновом режиме, но убито ОС (проверено с помощью adb shell: приложение находится в грязном состоянии, поэтому мы возвращаем намерение на экран заставки/загрузки и обратно в основное действие.

С. Приложение не работает вообще

Это действительно то же самое, как если бы Android убил приложение в фоновом режиме, чтобы освободить ресурсы. Просто откройте главное действие, вернитесь к экрану заставки для загрузки и вернитесь к основной операции.

D. Активность всплеска

Не очень вероятно, что пользователь может находиться в активности Activity/Load Activity при загрузке Notification, но это возможно в теории. Если пользователь делает это, StrictMode жалуется на то, что при закрытии приложения работает 2 основных действия, но я не уверен, что это совершенно правильно. Во всяком случае, это очень гипотетично, поэтому я не собираюсь тратить много времени на это в этот момент.

Я не думаю, что это идеальное решение, так как здесь требуется немного кодирования и немного кодирования там, и возвращение намерений туда и обратно, если приложение находится в грязном состоянии, но оно работает. Комментарии приветствуются.