4

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

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

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

Описание:

Я использую API YouTube Android игрока 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 показывает folllowing сообщения для каждого запроса пиктограмм после первого поворота:

 
    03-09 17:43:08.770 30446-30446/com.mypackage.name E/ActivityThread: Activity com.mypackage.name.activities.SearchActivity has leaked ServiceConnection [email protected] that was originally bound here 
    android.app.ServiceConnectionLeaked: Activity com.mypackage.name.activities.SearchActivity has leaked ServiceConnection [email protected] that was originally bound here 
      at android.app.LoadedApk$ServiceDispatcher.(LoadedApk.java:1041) 
      at android.app.LoadedApk.getServiceDispatcher(LoadedApk.java:935) 
      at android.app.ContextImpl.bindServiceAsUser(ContextImpl.java:1692) 
      at android.app.ContextImpl.bindService(ContextImpl.java:1680) 
      at android.content.ContextWrapper.bindService(ContextWrapper.java:496) 
      at com.google.android.youtube.player.internal.r.e(Unknown Source) 
      at com.google.android.youtube.player.YouTubeThumbnailView.initialize(Unknown Source) 
      at com.mypackage.name.adapters.ListViewAdapter.getView(ListViewAdapter.java:94) 
      at com.mypackage.name.views.ListView.obtainView(ListView.java:1285) 
      at com.mypackage.name.views.ListView.fillDown(ListView.java:1046) 
      at com.mypackage.name.views.ListView.populate(ListView.java:720) 
      at com.mypackage.name.views.ListView.onLayout(ListView.java:677) 
      at android.view.View.layout(View.java:14520) 
      at android.view.ViewGroup.layout(ViewGroup.java:4604) 
      at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1077) 
      at android.view.View.layout(View.java:14520) 
      at android.view.ViewGroup.layout(ViewGroup.java:4604) 
      at android.widget.FrameLayout.onLayout(FrameLayout.java:448) 
      ... 

Почему я получаю эти ServiceConnectionLeaked сообщения?

Я уже искал вокруг, и в большинстве случаев существует какая-то инициализация API, необходимая там, где нужно предоставить контекст приложения вместо контекста активности. Но с API-интерфейсом YouTubePlayer Android такой инициализации нет (по крайней мере, не в моей части кода).

ответ

12

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

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

Кажется, что инициализатор YoutubeThumbnail использует этот контекст, хотя на нем нет намека или документации. Поэтому, когда I инициализирует адаптер с помощью контекста приложения, этот контекст приложения используется для изображений YouTubeThumbnails и никакой утечки не производится.

0

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

android:configChanges="orientation|screenSize"