1

これが私のアプリケーションの基本的なライフサイクルです。デバイスでAndroid2.3.3を実行しているため、SDKバージョン8をターゲットにしています。

  • アプリケーションが起動し、onResume()呼び出されます。
    メソッドshow()は、キャッシュされたデータを表示するために呼び出されます。
  • データをダウンロードして保存するバックグラウンドサービスが開始されます。AsyncTaskインスタンスを使用して作業を実行します。
  • タスクの1つは、ダウンロードしたデータをSQLiteデータベースに保存します。
  • onPostExecute()保存タスクが終了すると、ブロードキャストインテントが送信されます。
  • MapActivityインテントを受け取り、それを処理します。
    このメソッドshow()は、キャッシュされた新しいデータを表示するために呼び出されます。

メソッド内では、オーバーレイが追加された後、show()マップビューが無効になります。show()MapActivity自体から呼び出された場合、これは正常に機能します。ただし、非同期タスクがメソッド呼び出しのソースである場合(間接的に)、例外が発生します。

私が理解している限り、どちらの場合もトリガーするとUIスレッドにいます。show()これは本当ですか?

    public class CustomMapActivity extends MapChangeActivity {

        private boolean showIsActive = false;

        private BroadcastReceiver mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent.getAction().equals(IntentActions.FINISHED_STORING)) {
                    onFinishedStoring(intent);
                }
            }
        };

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            registerReceiver(mReceiver, new IntentFilter(IntentActions.FINISHED_STORING));
        }

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

        @Override
        protected void onMapZoomPan() {
            loadData();
            show();
        }

        @Override
        protected void onMapPan() {
            loadData();
            show();
        }

        @Override
        protected void onMapZoom() {
            loadData();
            show();
        }

        private void onFinishedStoring(Intent intent) {
            Bundle extras = intent.getExtras();
            if (extras != null) {
                boolean success = extras.getBoolean(BundleKeys.STORING_STATE);
                if (success) {
                    show();
                }
        }

        private void loadData() {
            // Downloads data in a AsyncTask
            // Stores data in AsyncTask
        }

        private void show() {
            if (showIsActive) {
                return;
            }
            showIsActive = true;
            Uri uri = UriHelper.getUri();
            if (uri == null) {
                showIsActive = false;
                return;
            }
            Cursor cursor = getContentResolver().query(uri, null, null, null, null);
            if (cursor != null && cursor.moveToFirst()) {
                List<Overlay> mapOverlays = mapView.getOverlays();
                CustomItemizedOverlay overlay = ItemizedOverlayFactory.getCustomizedOverlay(this, cursor);
                if (overlay != null) {
                    mapOverlays.clear();
                    mapOverlays.add(overlay);
                }
            }
            cursor.close();
            mapView.invalidate(); // throws CalledFromWrongThreadException
            showIsActive = false;
        }

    }

これがスタックトレースです...

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
    at android.view.ViewRoot.checkThread(ViewRoot.java:3020)
    at android.view.ViewRoot.invalidateChild(ViewRoot.java:647)
    at android.view.ViewRoot.invalidateChildInParent(ViewRoot.java:673)
    at android.view.ViewGroup.invalidateChild(ViewGroup.java:2511)
    at android.view.View.invalidate(View.java:5332)
    at info.metadude.trees.activities.CustomMapActivity.showTrees(CustomMapActivity.java:278)
    at info.metadude.trees.activities.CustomMapActivity.onMapPan(CustomMapActivity.java:126)
    at info.metadude.trees.activities.MapChangeActivity$MapViewChangeListener.onChange(MapChangeActivity.java:50)
    at com.bricolsoftconsulting.mapchange.MyMapView$1.run(MyMapView.java:131)
    at java.util.Timer$TimerImpl.run(Timer.java:284)

注:マップイベントに関する通知を受信するために、 MapChangeプロジェクトを使用しています。


編集:

AsyncTaskに関するドキュメント(少し下にスクロール)で読んだことから、正しい方法で使用しているかどうかはわかりません。前述のように、クラスAsyncTask内からインスタンスを開始します。Service逆に、ドキュメントには...

AsyncTaskを使用すると、ユーザーインターフェイスで非同期作業を実行できます。ワーカースレッドでブロック操作を実行し、スレッドやハンドラーを自分で処理しなくても、UIスレッドで結果を公開します。

...これは、 ?!内ではなく内AsyncTaskでのみ使用する必要があるように聞こえます。ActivityService

4

2 に答える 2

1

クラッシュの理由は、使用している MapChange ライブラリの実装方法が原因です。内部では、このライブラリはTimerおよびTimerTask実装を使用して、変更イベントの発生を遅らせ、アプリケーションが取得する呼び出しの数を減らしますonMapChanged()Timerただし、ドキュメントから、作成されたスレッドでタスクを実行することがわかります。

各タイマーには、タスクが順次実行される 1 つのスレッドがあります。このスレッドがタスクの実行でビジー状態になると、実行可能なタスクが遅延する可能性があります。

MapChange ライブラリは、コールバックがメイン スレッドでアプリケーションに送信されることを保証するものではないため (特に Android では重大なバグ IMO)、このリスナーの結果として呼び出すコードを保護する必要があります。MyMapActivityこれは、ライブラリにバンドルされている例で確認できます。そのコールバックからのすべてがHandler、呼び出しをメイン スレッドに戻す を介して送られます。

アプリケーションでは、内部のコードonMapPan()とその後のコードshowTrees()がバックグラウンド スレッドで呼び出されるため、そこで UI を操作するのは安全ではありません。aHandlerまたはrunOnUiThread()from yourを使用Activityすると、コードが適切な場所で呼び出されることが保証されます。

に関する 2 番目の質問に関してはAsyncTaskActivity. これは「バックグラウンド」コンポーネントですが、デフォルトでServiceはメインスレッドでも実行されているため、AsyncTask長期的な処理を一時的に別のスレッドにオフロードする必要があります。

于 2012-07-16T16:54:05.863 に答える
1

間違ったスレッドで呼び出されている場合は、UI スレッドではない可能性があります。これを試しましたか:

runOnUiThread(new Runnable() {
    public void run() {
        mapView.invalidate();
    }});
于 2012-07-12T22:23:49.103 に答える