12

質問:

ターゲットが構成 (画面サイズ、向きなど) に依存する可能性がある場合に、何を起動するかを決定する方法Activity。s?Notificationを使用する場合によくあることです。Fragment


詳細:

s を使用して、複数の画面サイズと向きで適切に再生されるアプリを作成する方法を示すNewsReader サンプルについて考えてみましょう。Fragmentこのアプリは次のように構成されています。

  • A. HeadlinesFragment_
  • アンArticleFragment
  • 「メイン」アクティビティ ( NewsReaderActivity)。デュアル ペイン モードでは、このアクティビティには両方のフラグメントが含まれます。単一ペイン モードでは、HeadlinesFragment.
  • アンArticleActivity。このアクティビティは、シングル ペイン モードでのみ使用されます。が含まれていArticleFragmentます。

ここで、このアプリを拡張してService、ニュースの更新をリッスンし、新しいニュース項目があるたびにステータス バー通知を介してユーザーに通知するバックグラウンドを追加するとします。合理的な要件リストは次のようになります。

  1. 複数のニュース更新がある場合、通知をクリックすると、ユーザーは常にヘッドライン リストに移動する必要があります。
  2. 更新が 1 つしかない場合は、通知をクリックすると、最新のニュース記事が開きます。

これらの要件は、現在の構成に応じて異なるターゲット アクティビティに変換されることに注意してください。特に、

  1. 要件 (1) いずれかのモードで = NewsReaderActivity.
  2. 要件 (2) デュアルペイン モード = NewsReaderActivity.
  3. 単一ペイン モードでの要件 (2) = ArticleActivity

上記の (2) と (3) を達成するエレガントな方法は何でしょうか? 現在の構成をService調査して、PendingIntent.

私が考えた 1 つの解決策は、(2) をスキップして (3) を常に実行することでした。つまり、ArticleActivityニュース更新が 1 つしかない場合は常に起動します。ArticleActivity からのこのスニペットは有望に見えました:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //...
    //...
    // If we are in two-pane layout mode, this activity is no longer necessary
    if (getResources().getBoolean(R.bool.has_two_panes)) {
        finish();
        return;
    }

    //...
    //...
}

ArticleActivityこのコードは、 を表示している場合に、不要になった構成に切り替えることを保証します (たとえば、縦向きから横向きへ)。その後、アクティビティは単純に閉じます。

ただし、インテントにはFLAG_ACTIVITY_NEW_TASK フラグが設定されているため、これは私たちの場合には機能しません。新しいタスクを作成し、スタックに「前の」アクティビティはありません。したがって、呼び出しfinish()はスタック全体をクリアするだけです。

では、起動するアクティビティが画面構成に依存する場合、通知から起動するアクティビティをどのように決定するのでしょうか?

4

3 に答える 3

7

これは本当に良い質問です!

PendingIntent で対象とするアクティビティを決定するために、サービスが現在の構成をプローブする可能性を安全に除外できると思います。

UI レイヤーで UI の決定を保持することが望ましいことに同意しますが、サービスに決定をさせることは確かに適切な選択です。UI レイヤー クラスで静的メソッドを使用して、決定コードを技術的にサービスの外部に保持することができます (たとえば、サービスが を構築するために使用する静的createArticlePendingIntent()メソッド)。NewsReaderActivityNotification

では、起動するアクティビティが画面構成に依存する場合、通知から起動するアクティビティをどのように決定するのでしょうか?

この「記事を表示する」シナリオであることを認識できる十分なエクストラを使用して、getActivity() PendingIntentで forを使用NewsReaderActivityします。を呼び出す前に、 が正しい答えかどうかを判断させます。その場合は、 launchを呼び出してから、それ自体を取り除くために呼び出します (または、記事から BACK に移動したい場合はそうではありません)。NotificationNewsReaderActivitysetContentView()ArticleActivityNewsReaderActivitystartActivity()ArticleActivityfinish()NewsReaderActivity

または、 でgetActivity() PendingIntentforを使用ICanHazArticleActivityしますNotificationICanHazArticleActivityTheme.NoDisplayあるため、UI はありません。NewsReaderActivityまたはを起動するかどうかを決定し、正しい答えをArticleActivity呼び出してから、 を呼び出します。前のソリューションよりも優れている点は、最終目的地が の場合に短いフラッシュがないことです。startActivity()finish()NewsReaderActivityArticleActivity

または、createArticlePendingIntent()回答の最初の段落で述べたオプションを使用してください。

他にも選択肢があるかもしれませんが、思いつくのはこれらです。

于 2012-05-28T11:57:24.563 に答える
5

アプリでデュアル ペイン/シングル ペイン アプローチを使用している場合、使用するアクティビティは 1 つだけです。この場合は、ArticleActivity を取り除くことを意味します。代わりに、次の方法で続行できます。

まず、さまざまな構成に XML レイアウトを使用して、フラグメントの外観を制御します。たとえば、次のようになります。

単一ペイン (解像度/レイアウト):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >

        <FrameLayout
                android:id="@+id/mainFragment"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent" />

</LinearLayout>

デュアル ペイン (res/layout-xlarge-land):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >

        <FrameLayout
                android:id="@+id/mainFragment"
                android:layout_weight="2"
                android:layout_width="0dp"
                android:layout_height="fill_parent" />

        <FrameLayout
                android:id="@+id/detailsFragment"
                android:layout_weight="1"
                android:layout_width="0dp"
                android:layout_height="fill_parent" />

</LinearLayout>

NewsReaderActivity では、レイアウトに存在するフラグメントを確認するだけで済みます。

boolean isMainFragment = (findViewById(R.id.mainFragment) != null);
if (isMainFragment) {
    mainFragment = new ListFragment();
}

boolean isDetailFragment = (findViewById(R.id.detailsFragment) != null);
if (isDetailFragment) {
    detailFragment = new DetailsFragment();
}

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (isMainFragment ) {
    transaction.add(R.id.mainFragment, mainFragment);
}
if (isDetailFragment ) {
    transaction.add(R.id.detailFragment, detaislFragment);
}  
transaction.commit();

次に、シングル ペイン モード (detailsFragment が存在しない) で詳細画面を表示したい場合は、同じアクティビティをもう一度起動しますが、どのコンテンツが必要かを伝えるインテントにパラメーターを含めます。

void onViewDetails() {
    Intent i = new Intent(this, NewsReaderActivity.class);
    i.putExtra("showDetails", true);
    startActivity(i);
}

アクティビティの onCreate() で、このパラメーターに応じてフラグメントを選択します。

boolean isDetailFragment = (findViewById(R.id.detailsFragment) != null);
if (!isDetailFragment) {
    boolean showDetails = getIntent().getBooleanExtra("showDetails", false);
    if (showDetails) {
       mainFragment = new DetailsFragment();
    }
    else {
       mainFragment = new ListFragment();
    }
}

最終的に通知からアクティビティを起動すると、現在の画面の向きは気にしなくなります。

インテントに「showDetails」フラグを含めて、適切に設定するだけonViewDetails()です。デュアル ペイン モードの場合 (「showDetails」が true の場合は特別な動作を実行できます)、またはシングル ペイン モードを必要とする構成の場合は、アクティビティに両方のフラグメントが表示されます。 「showDetails」フラグが表示されます。

これが役に立ち、このアプローチについてよく理解できることを願っています。

于 2012-06-04T09:12:26.807 に答える
1

受け入れられた回答で言及されている UI アクティビティなしのアプローチは、私が決めたものです。別のオプションを試しましたが、うまくいきませんでした。私が試したのはこれでした:

  1. で、 forをスタックの一番下に、forを一番上にしてスタックをService構築します。IntentIntentNewsReaderActivityArticleActivity
  2. PendingIntent.getActivities()ステップ 1で作成したスタックを使用して渡しPendingIntent、完全なスタックを表すものを取得します。
  3. にはArticleActivity、次のコードがあります。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //...
        //...
        // If we are in two-pane layout mode, this activity is no longer necessary
        if (getResources().getBoolean(R.bool.has_two_panes)) {
            finish();
            return;
        }
    
        //...
        //...
    }
    

したがって、最初にターゲットArticleActivityを設定します。これは、現在の構成で役立つかどうかを判断するために、一種の自己内省を行います。そうでない場合は、 で邪魔にならないように移動しfinish()ます。はバックスタックのNewsReaderActivity「前」に既に存在するため、これにより に移動します。ArticleActivityNewsReaderActivity

これは完璧な解決策のように思えました - 私が見落としていた 1 つのポイントを除いて: PendingIntent.getActivities()API 11 以降でのみ動作します。サポート ライブラリに大まかに相当するものがあります: TaskStackBuilder. を使用してスタックにインテントを追加し続けaddNextIntent()、最終的に呼び出して(私が推測する)にgetPendingIntent()似たものを達成することができます。PendingIntent.getActivities()

ただし、HC 以前のデバイスでは、これにより、スタックの最上位 Activityのみが新しいタスクで開始されます (強調鉱山)。

Android 3.0 以降を実行しているデバイスでは、startActivities() メソッドを呼び出すか、getPendingIntent(int, int) によって生成された PendingIntent を送信すると、規定どおりに合成バック スタックが構築されます。古いバージョンのプラットフォームを実行しているデバイスでは、これらの同じ呼び出しにより、提供されたスタックの最上位のアクティビティが呼び出され、残りの合成スタックは無視され、戻るキーで前のタスクに戻ることができます

そのため、HC 以前のデバイスでは、ArticleActivity を押し戻すと、以前に実行されていたタスクに戻ります。これは私たちが望んでいるものではありません。

近いうちにプロジェクトを共有する予定です。また、アプリ内通知(記事を読んでいるときに「新着記事」の通知など)を行っていても、新鮮なタスクを開始することについても懸念していました。別の質問として投稿したいと思います。

于 2012-06-05T14:55:13.010 に答える