В ListView: активность просочилась ServiceConnection <youtube.player.internal>, который был изначально связан здесь

Обновить:

Я только что проверил свое приложение на другом устройстве и обнаружил, что я получаю ошибку на Nexus 4 под управлением Android 4.4.2, но НЕ на Desire S под управлением Android 4.0.4. На обоих из них установлено текущее приложение YouTube (5.3.32), которое требуется для использования API.

Вопрос: Почему я получаю эти сообщения ServiceConnectionLeaked? (См. Logcat ниже)

Описание:

Я использую YouTube Player 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, имеет следующий код:

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 загружены и отображаются правильно.

Теперь я поворачиваю устройство в первый раз (скажем, в альбомной ориентации):

  • Фрагмент создан, уже существующий адаптер подключен к listView, но миниатюра не загружается для всех видимых в данный момент элементов списка.
  • Когда я прокручиваю вниз, новые элементы списка появляются на экране.
    • Если такие новые элементы списка используют новый ViewHolder, его миниатюра загружается правильно.
    • Если такие новые элементы списка используют ViewHolder из первых видимых элементов списка, его миниатюра вообще не загружается.

Таким образом, это приводит к появлению списка миниатюр, которые чередуются при правильной загрузке и не загружаются вообще. Теперь я поворачиваю устройство во второй раз (обратно в портретную ориентацию):

  • Фрагмент создан, существующий адаптер подключен к listView, и все миниатюры загружены правильно.

Теперь я поворачиваю устройство в третий раз (мы снова находимся в ранее ошибочной альбомной ориентации):

  • Фрагмент создан, существующий адаптер подключен к 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, когда нужно предоставить контекст приложения вместо контекста активности. Но в API YouTubePlayer Android такой инициализации нет (по крайней мере, в моей части кода).

3 ответа

Решение

Я наконец нашел решение. Я проследил каждый шаг, который происходил при первом создании всех соперников и фрагментов, и сравнил его с тем, что происходит во время первой ротации. Делая заметки о каждом созданном и повторно использованном объекте, я обнаружил, что во время onCreateView-метода фрагмента я заново инициирую listView, но устанавливаю для него старый адаптер. Пока это не проблема, и это хорошая практика, так как я могу повторно использовать любую информацию, которую адаптер содержит на данный момент (поправьте меня, если я ошибаюсь).

Проблема существовала в более ранней инициализации адаптера. Адаптер инициализируется перед первым оборотом и получает текущую активность в качестве контекста. Следовательно, после поворота у него все еще была старая портретная активность в качестве контекста, хотя она лежала во вновь созданном ландшафтном действии. => классическая утечка активности.

Кажется, что YoutubeThumbnail-initializer использует этот контекст, хотя нет никаких подсказок или документации по этому вопросу. Поэтому, когда я инициализирую адаптер с контекстом приложения, этот контекст приложения используется для эскизов YouTube, и утечка не возникает.

Ну, эта ошибка приходит из-за изменения ориентации. Таким простым решением было бы сохранить состояние активности, чтобы контекст активности не терялся, что приводит к ошибке ServiceConnectionLeaked. Так что просто добавьте эту строку в тег объявления активности в файле манифеста.

    android:configChanges="orientation|screenSize"

Добавьте android:configChanges="direction|screenSize" в тег активности в файле манифеста и при инициализации youTubeThumbnailView add youTubeThumbnailLoader.release (); для onInitializationSuccess, как показано ниже,

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
        }
    });
Другие вопросы по тегам