6

私のアプリケーションは、Web サービス呼び出しを介してリモート データベースとデータを同期します。バックグラウンドで実行できるように、IntentService でこれらの呼び出しを行います (これを SyncService と呼びます)。

IntentService を起動するコードは次のようになります。

Intent intent = new Intent();
intent.setClass(appContext, SyncService.class);

// place additional values in intent
intent.putExtra("data_type", SyncService.ITEM_TRACKING);
intent.putExtra("user_id", intUserId);

// call SyncService
appContext.startService(intent);

これは、通常、見栄えがします。しかし、私のアプリのユーザーでもある私の友人の 1 人は、データが同期されず、私たちの Web サイトに表示されないとよく言います。彼のデバイスは、私がそばにいる間にたまたま症状を示していました。彼のデバイスを自分のコンピューターに接続したところ、次のような結果が得られました。

  • SyncService を起動するコード (つまり、上記のコード) がヒットしました。
  • IntentService の onHandleIntent メソッド内にブレークポイントがあり、ヒットすることはありません。
  • 彼のデバイスの実行中のサービスのリストを確認したところ、SyncService がそこにあり、実行されていました。興味深いことに、それは約 20 分間実行されていました。私は、処理するインテントがすべてなくなったときに IntentService が自分自身を殺したという印象を受けました。
  • (アプリではなく) SyncService を強制停止したところ、突然、onHandleIntent が何度もヒットし始めました。すべてのインテントがデバイスのどこかにキューに入れられ、SyncService でスローされているようでした。

何が問題なのかについて誰か考えがありますか? 私のアプリの問題だと思いますか?アンドロイドで?

繰り返しになりますが、「この IntentService を開始するか、既に実行中の IntentService にメッセージを送信してください」というメッセージを Android に渡しています。その時点で、私はコントロールできません。メッセージが IntentService に到達することはありません。アプリを強制終了すると、メッセージが IntentService に送信され、その仕事が行われます。

更新: このコードは問題ないと思いますが、多くの人が見たいと思うかもしれないので掲載します。

IntentService に着信するすべてのインテントには、自分に対する呼び出しの「タイプ」を示す Extra があります (つまり、このWeb サービスを呼び出すか、そのWeb サービスを呼び出すかなど)。IntentService に Intent が来ると、「type」をチェックし、そのタイプの Intent がキューに既にある場合は、それに「skip」という Extra を追加します。検索を実行します (基本的に、IntentService は多くの Intent を構築することができ、この Web サービスが 20 秒前に呼び出されたときにこの Web サービスを呼び出すことは意味がありませ)。基本的に、アプリをウェブサイトへのスパムから保護します。

いずれにせよ (問題が発生し始めると)、このコードはヒットしないことに注意することが重要です。onStartCommand は、アプリが強制終了されるまで呼び出されません

    @Override
    public int onStartCommand (Intent intent, int flags, int startId) {
        // here be dragons
        // overriding this method and adding your own code is dangerous. i've wrapped
        // my code in a try/catch because it is essential that the super method be called
        // every time this method is entered. any errors in my code should not prevent this
        // or the app will explode.
        try {
            if (flags == 0 && intent != null && intent.hasExtra("data_type")) {
                Integer intDataType = intent.getExtras().getInt("data_type");

                    if (!mCurrentTypes.containsKey(intDataType)
                            || !mCurrentTypes.get(intDataType)) {
                        mCurrentTypes.put(intDataType, true);  // put this type in the list and move on
                    }
                    else {
                        intent.putExtra("skip", true);  // mark this Intent to be skipped
                    }
            }
        }
        catch (Exception e) {
            // Log.e("Error onStartCommand", "error: " + e);
        }

        return super.onStartCommand(intent, flags, startId);
    }


private void processIntent(Intent intent) {
        // do stuff if no "skip" Extra
        mCurrentTypes.put(intDataType, false);
    }
4

4 に答える 4

4

あなたの友人のデバイスであなたのサービスを実行し続ける何かが間違いなくあります. その場合、このインテント サービスへの後続の呼び出しはすべて、現在の呼び出しが終了するまでキューに入れられます。それが終わらなければ、あなたが持っているものを手に入れることができます: 次のサービスは開始されません。

次のことを再確認する必要があります。

  • ネットワーク操作に適切なタイムアウトを与える
  • ネットワーク接続操作に適切なタイムアウトを与える
  • スレッド間に競合状態はありません。
  • サービス内で発生する可能性のある例外をログに記録します。そのような情報を失いたくありません。

その後、すべてが緑色であると思われる場合は、サービスの動作をログに記録し、バグ報告メカニズムを使用して、友人のデバイスから自動的に送信されるようにします。簡単な解決策は、バグセンスまたは同等のものを使用することです。

次に、ある種のウォッチドッグを配置します: サービスが停止するまで実行し続けるスレッド (サービスが停止したときにスレッドを停止するように指示するだけです)。制限時間の経過後、スレッドはサービスを停止する必要があります。

このウォッチドッグ スレッドは、サービス自体の内部または外部に配置できますが、配置がより複雑になる場合があります。

于 2012-12-01T10:31:37.600 に答える
1

この回答は、同様の状況で私のために働いた解決策を示唆しています。現在のコードは修正されませんが、おそらくより単純な (そしてデバッグしやすい) 別のオプションが提案されます。

  1. からのSUCCESS をリッスンBroadcastReceiverする呼び出しに を追加します。ActivityIntentsIntentService

  2. 呼び出しActivityに、いつ開始するかのロジックを含めますIntentService(IntentService には含めないでください)。ロジックは次のとおりです。

    • CANNOT_CALL へstartService()の呼び出しでフラグを呼び出して設定します。Activity
    • Activity's BroadcastReceiverが から SUCCESS ブロードキャストを受信して​​いない場合、をIntentService再度startService()呼び出すことはできません。
    • が SUCCESS インテントを受信したらActivity、フラグを CAN_CALL に設定しstartService()、タイマーが再びヒットしたときに呼び出すことができます。
  3. あなたのように、あなたのようIntentServiceに書いてください:onStartCommand()

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }
    
  4. youIntentServiceで、Web サービスの応答を受信、解析、保存したら、sendBroadcast()カスタム アクション SUCCESS を使用して Intent を呼び出します。

IntentServiceこのロジックは単なる概要であり、listeningからブロードキャストする必要がある Web サービスからのエラー メッセージ用に微調整する必要がありますActivity

お役に立てれば。

于 2012-12-06T00:56:13.817 に答える
0

もう1つのコメント。あなたの質問に対する答えではありません。ただし、サービスの全体的な動作に影響を与える可能性があります。

次のようにします。

 return super.onStartCommand(intent, flags, startId);

内部的に Service.onStartCommand() は次のようになります

public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId); 
        return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}

アプリが SDK API 7 以降をターゲットにしている場合 (ほとんどの場合)、mStartCompatibility は false です。

その結果、サービスは START_STICKY として開始されます。

ここにドキュメントの一部があります:

開始されたサービスの場合、onStartCommand() から返される値に応じて、実行を決定できる 2 つの主要な操作モードがあります。 START_STICKY は、必要に応じて明示的に開始および停止されるサービスに使用されます。送信されたコマンドを処理している間だけ実行し続ける必要があるサービスの場合。セマンティクスの詳細については、リンクされたドキュメントを参照してください。

あなたが説明したことに基づいて、「return super.onStartCommand(intent, flags, startId);」を置き換えることをお勧めします。「START_NOT_STICKYを返す」に

于 2012-12-06T20:29:07.670 に答える
0

インテントに一連のフラグを設定すると、問題が解決する可能性があるようです。

Intent intent = new Intent();
intent.setClass(appContext, SyncService.class);
// This way    
intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK|Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);

新しいタスクで上記のフラグを使用して、サービスを新しいものとして開始できます。

于 2012-12-06T05:11:48.367 に答える