4

アップデート:

別のデバイスでアプリをテストしたところ、Android 4.4.2 を実行している Nexus 4 ではエラーが発生することがわかりましたが、Android 4.0.4 を実行している Desire Sでは発生しません。両方とも、API を使用するために必要な現在の YouTube アプリ (5.3.32) がインストールされています。

質問: ServiceConnectionLeaked メッセージが表示されるのはなぜですか? (以下の Logcat を参照)

説明:

YouTube Android Player API 1.0.0 ( https://developers.google.com/youtube/android/player/ ) を使用して、次のアダプター コードを使用して ListView にビデオ サムネイルを読み込みます。

private final Map<View, YouTubeThumbnailLoader> mThumbnailViewToLoaderMap;

public ListViewAdapter(Activity activity, int layoutId, List<Suggestion> suggestions) {
    super(activity, layoutId, suggestions);     
    mThumbnailViewToLoaderMap = new HashMap<View, YouTubeThumbnailLoader>();
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    ViewHolder holder;

    String videoId = getYoutubeId();

    // There are three cases here...
    if (convertView == null) {
        convertView = LayoutInflater.from(mActivity).inflate(R.layout.row_suggestion, parent, false);

        holder = new ViewHolder();
        holder.thumbnailView = (YouTubeThumbnailView) convertView.findViewById(R.id.youtubeThumbnail);

        // ... case 1: The youtube view has not yet been created - we need to initialize the YouTubeThumbnailView.
        holder.thumbnailView.setLayoutParams(mThumbnailLayoutParams);
        holder.thumbnailView.setTag(videoId);
        holder.thumbnailView.initialize(DeveloperKey.DEVELOPER_KEY, this);

        convertView.setTag(holder);

    } else {

        holder = (ViewHolder) convertView.getTag();

        // ... case 2 & 3 The view is already created and...
        YouTubeThumbnailLoader loader = mThumbnailViewToLoaderMap.get(holder.thumbnailView);

        // ... is currently being initialized. We store the current videoId in the tag.
        if (loader == null) {
            holder.thumbnailView.setTag(videoId);

        // ... already initialized. Simply set the right videoId on the loader.
        } else {
            loader.setVideo(videoId);
        }
    }

    holder.thumbnailView.setImageResource(R.drawable.thumbnail_loading);

    return convertView;
}


@Override
public void onInitializationSuccess(YouTubeThumbnailView youTubeThumbnailView, YouTubeThumbnailLoader youTubeThumbnailLoader) {
    String videoId = (String) youTubeThumbnailView.getTag();
    mThumbnailViewToLoaderMap.put(youTubeThumbnailView, youTubeThumbnailLoader);
    youTubeThumbnailLoader.setOnThumbnailLoadedListener(this);
    youTubeThumbnailLoader.setVideo(videoId);
}

public void releaseLoaders() {
    for (YouTubeThumbnailLoader loader : mThumbnailViewToLoaderMap.values()) {
        loader.release();
    }
}

この listView を保持する Fragment には、次のコードがあります。

private ListViewAdapter listViewAdapter;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setRetainInstance(true);
}

Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    rootView = (ViewGroup) inflater.inflate(R.layout.fragment_search_results, container, false);
    listView = (ListView) rootView.findViewById(R.id.suggestion_list);

    // see if there is already an adapter
    // and use it as our adapter (e.g. after device rotation)
    if (listViewAdapter != null) {
        listView.setAdapter(listViewAdapter);
    }

    return rootView;
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    mListViewAdapter.releaseLoaders();
}

何が起こるかは次のとおりです。

  • フラグメントが作成され、YouTube サムネイルが読み込まれ、正しく表示されます。

ここで、初めてデバイスを回転させます(横向きにしましょう)。

  • Fragment が作成され、既存のアダプターが listView に接続されますが、現在表示されているすべてのリスト項目のサムネイルは読み込まれません。
  • 下にスクロールすると、新しいリスト項目が画面に入ります。
    • このような新しいリスト項目が新しい ViewHolder を使用する場合、そのサムネイルは正しく読み込まれます。
    • そのような新しいリスト アイテムが最初に表示されるリスト アイテムの ViewHolder を使用する場合、そのサムネイルはまったく読み込まれません。

したがって、これにより、正しくロードされているサムネイルとまったくロードされていないサムネイルのリストが交互に表示されます。次に、デバイスをもう一度回転させます(縦向きに戻します)。

  • フラグメントが作成され、既存のアダプターが listView に接続され、すべてのサムネイルが正しく読み込まれます。

ここで、デバイスを3回回転させます(以前は間違っていた横向きに戻りました)。

  • フラグメントが作成され、既存のアダプターが listView に接続され、すべてのサムネイルが正しく読み込まれます。

そのため、デバイスが最初に回転した後にのみ何かが発生するため、表示されているサムネイルはリスト項目に読み込まれません。

logcat は、最初のローテーションの後、サムネイル リクエストごとに次のメッセージを表示します。

    03-09 17:43:08.770 30446-30446/com.mypackage.name E/ActivityThread: アクティビティ com.mypackage.name.activities.SearchActivity が ServiceConnection com.google.android.youtube.player.internal.r$e@ を漏らしましたもともとここにバインドされていた 41b0a6c0
    android.app.ServiceConnectionLeaked: アクティビティ com.mypackage.name.activities.SearchActivity が ServiceConnection com.google.android.youtube.player.internal.r$e@41b0a6c0 をリークしました。これはもともとここにバインドされていました
            android.app.LoadedApk$ServiceDispatcher.(LoadedApk.java:1041) で
            android.app.LoadedApk.getServiceDispatcher (LoadedApk.java:935) で
            android.app.ContextImpl.bindServiceAsUser(ContextImpl.java:1692) で
            android.app.ContextImpl.bindService(ContextImpl.java:1680) で
            android.content.ContextWrapper.bindService(ContextWrapper.java:496) で
            com.google.android.youtube.player.internal.re (提供元不明)
            com.google.android.youtube.player.YouTubeThumbnailView.initialize で (不明なソース)
            com.mypackage.name.adapters.ListViewAdapter.getView(ListViewAdapter.java:94) で
            com.mypackage.name.views.ListView.obtainView (ListView.java:1285) で
            com.mypackage.name.views.ListView.fillDown (ListView.java:1046) で
            com.mypackage.name.views.ListView.populate (ListView.java:720) で
            com.mypackage.name.views.ListView.onLayout (ListView.java:677) で
            android.view.View.layout(View.java:14520)で
            android.view.ViewGroup.layout(ViewGroup.java:4604) で
            android.widget.RelativeLayout.onLayout(RelativeLayout.java:1077) で
            android.view.View.layout(View.java:14520)で
            android.view.ViewGroup.layout(ViewGroup.java:4604) で
            android.widget.FrameLayout.onLayout(FrameLayout.java:448) で
            ...

これらの ServiceConnectionLeaked メッセージが表示されるのはなぜですか?

私はすでに周りを検索しましたが、ほとんどの場合、アクティビティ コンテキストの代わりにアプリケーション コンテキストを提供する必要がある場合、ある種の API 初期化が必要です。しかし、YouTubePlayer Android API では、そのような初期化はありません (少なくともコードの私の部分では)。

4

3 に答える 3

12

私は最終的に解決策を見つけました。すべてのバイとフラグメントが最初に作成されたときに発生していたすべてのステップをトレースし、最初のローテーション中に発生したことと比較しました。作成および再利用されたすべてのオブジェクトをメモすると、フラグメントの onCreateView-Method 中に listView を再度開始するが、古いアダプターをそれに設定することがわかりました。アダプタがこの時点で保持しているすべての情報を再利用できるため、これはこれまでのところ問題ではなく、良い習慣です (間違っている場合は修正してください)。

この問題は、アダプターの以前の初期化に存在していました。アダプターは最初のローテーションの前に初期化され、現在のアクティビティをコンテキストとして取得します。したがって、ローテーション後、新しく作成されたランドスケープ アクティビティに転がっていたにもかかわらず、コンテキストとして古いポートレート アクティビティが残っていました。=> クラシック アクティビティのリーク。

これに関するヒントやドキュメントはありませんが、YoutubeThumbnail-initializer はこのコンテキストを使用しているようです。そのため、アプリケーション コンテキストを使用してアダプターを初期化すると、このアプリ コンテキストが YouTubeThumbnails に使用され、リークは発生しません。

于 2014-03-13T10:42:11.660 に答える
3

このエラーは、向きの変更が原因で発生します。したがって、簡単な解決策は、アクティビティ コンテキストが失われないようにアクティビティ状態を保存することです。その結果、ServiceConnectionLeaked エラーが発生します。したがって、マニフェスト ファイルのアクティビティ宣言タグにこの行を追加するだけです。

    android:configChanges="orientation|screenSize"
于 2018-01-06T20:57:38.410 に答える