在ListView中:Activity泄漏了ServiceConnection <youtube.player.internal>,最初是在这里绑定的。

4

更新:

我刚在另一台设备上测试了我的应用程序,并发现在运行Android 4.4.2的Nexus 4上确实会出现错误,但是在运行Android 4.0.4的Desire S上却没有出现。它们都安装了当前所需的YouTube App(5.3.32)才能使用API。

问题:为什么会出现“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();
}

这里发生了以下情况:
- 创建Fragment,YouTube缩略图正确加载并显示。 - 然后我第一次旋转设备(比如横屏方向): - Fragment被创建,已存在的适配器连接到listView上,但当前可见列表项的所有缩略图都未加载。 - 当我向下滚动时,新的列表项进入屏幕。 - 如果新的列表项使用新的ViewHolder,则其缩略图会正确加载。 - 如果新的列表项使用第一个可见列表项的ViewHolder,则其缩略图不会加载。 - 这导致缩略图列表交替在正确加载和完全未加载之间。 - 然后我第二次旋转设备(回到纵向方向): - 创建Fragment,现有适配器连接到listView并且所有缩略图都正确加载。 - 现在我第三次旋转设备(我们再次处于前面错误的横向方向): - 创建Fragment,现有适配器连接到listView并且所有缩略图都正确加载。 - 因此,只有在第一次旋转设备后才会发生某些事情,因此可见的缩略图未加载到其列表项中。 - 对于第一次旋转后的每个缩略图请求,logcat会显示以下消息:
03-09 17:43:08.770  30446-30446/com.mypackage.name E/ActivityThread: Activity com.mypackage.name.activities.SearchActivity has leaked ServiceConnection com.google.android.youtube.player.internal.r$e@41b0a6c0 that was originally bound here
出现这种ServiceConnectionLeaked错误是为什么?
我已经搜索了很多资料,大多数情况下需要进行某种API初始化,在此过程中必须提供应用程序上下文而不是活动上下文。 但是,使用YouTubePlayer Android API时,没有这样的初始化(至少在我的代码部分中没有)。
3个回答

12

我终于找到了解决方案。我追踪了第一次创建所有视图和片段时发生的每一个步骤,并将其与第一次旋转期间发生的情况进行了比较。记录下每个对象的创建和重用后,我发现在Fragment的onCreateView方法中重新初始化了listView,但是将旧的adapter设为了它。到目前为止,这不是问题,而且据我所知是一个好的做法,因为可以重复使用适配器到此时保存的所有信息(如果我错了,请纠正我)。

问题存在于适配器早期的初始化中。适配器在第一次旋转之前被初始化,并且将当前Activity作为其上下文。因此,在旋转后,尽管它仍然在新创建的横向Activity中,但仍具有旧的纵向Activity作为其上下文。导致经典活动泄漏。

看起来YoutubeThumbnail-initializer使用了这个上下文,尽管没有任何提示或文档。因此,当我使用应用程序上下文初始化适配器时,这个应用程序上下文用于YouTube缩略图,不会产生泄漏。


2
无法理解您的发现和解决方案。您能否添加一些代码片段以便更好地理解? - Zenith
很遗憾,没有代码片段可以解释当时发生了什么(那是4年前的事了)。简而言之:当您初始化列表适配器时,请勿使用Activity上下文作为参数。而是使用应用程序上下文。此外:今天您不应再使用ListViewAdapter,而应改用RecyclerView + LayoutManager + Adapter(请参阅Google文档以获取更多信息)。 - muetzenflo

3

这个错误是由于屏幕方向改变导致的。所以简单的解决方法就是保存活动状态,以便不会丢失活动上下文,从而避免ServiceConnectionLeaked错误。因此,在Manifest文件中的活动声明标签中添加以下行。

    android:configChanges="orientation|screenSize"

0
在清单文件的活动标签中添加android:configChanges="orientation|screenSize",并在初始化youTubeThumbnailView时,在onInitializationSuccess中添加youTubeThumbnailLoader.release();,如下所示:
holder.youTubeThumbnailView.initialize(Config.YOUTUBE_API_KEY, new YouTubeThumbnailView.OnInitializedListener() {
        @Override
        public void onInitializationSuccess(YouTubeThumbnailView youTubeThumbnailView, YouTubeThumbnailLoader youTubeThumbnailLoader) {

            youTubeThumbnailLoader.setVideo(extractYoutubeVideoId("https://www.youtube.com/watch?v=xxxxxxxx"));
            youTubeThumbnailLoader.setOnThumbnailLoadedListener(onThumbnailLoadedListener);
            youTubeThumbnailLoader.release();
        }

        @Override
        public void onInitializationFailure(YouTubeThumbnailView youTubeThumbnailView, YouTubeInitializationResult youTubeInitializationResult) {
            //write something for failure
        }
    });

它不加载缩略图,那我们为什么要使用它呢? - Pradeep Kumar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接