更新
我刚刚在另一台设备上测试了我的应用,发现我确实在运行Android 4.4.2的Nexus 4上出现了错误,但在运行Android 4.0.4的Desire S上出现了 NOT 。他们都安装了当前的YouTube应用程序(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();
}
以下是发生的事情:
现在我将设备旋转第一次时间(让我们说横向横向):
因此,这会产生一个缩略图列表,这些缩略图可以正确加载并且根本不加载。 现在我将设备旋转秒时间(返回纵向):
现在我将设备旋转第三时间(我们再次处于以前错误的横向方向):
因此,只有在设备第一次旋转后才会发生某些事情,因此可见的缩略图不会加载到列表项中。
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 android.app.ServiceConnectionLeaked: Activity com.mypackage.name.activities.SearchActivity has leaked ServiceConnection com.google.android.youtube.player.internal.r$e@41b0a6c0 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初始化,其中必须提供应用程序上下文而不是活动上下文。 但是对于YouTubePlayer Android API,没有这样的初始化(至少在我的部分代码中没有。)
答案 0 :(得分:12)
我终于找到了解决方案。我追踪了第一次创建所有vies和片段时发生的每一步,并将其与第一次旋转期间发生的事情进行比较。记下创建和重用的每个对象,我发现在片段的onCreateView-Method期间,我重新启动了listView,但是将旧的适配器设置为它。到目前为止,这不是一个问题,也不是一个良好的做法,因为我可以重新使用适配器所持有的每一个信息(如果我错了,请纠正我)。
早期初始化适配器时存在问题。适配器在第一次旋转之前初始化,并将当前活动作为其上下文。因此,在轮换之后,它仍然具有旧的肖像活动作为上下文,尽管它在新创建的景观活动中存在。 =&GT;经典活动泄漏。
似乎YoutubeThumbnail-initializer使用了这个上下文,尽管没有提示或文档。因此,当我使用应用程序上下文初始化适配器时,此应用程序上下文将用于YouTubeThumbnails,并且不会产生任何泄漏。
答案 1 :(得分:2)
这个错误是由于方向改变而产生的。如此简单的解决方案是保存活动状态,以便不丢失活动上下文,从而导致ServiceConnectionLeaked错误。 因此,只需在Manifest文件的活动声明标记中添加此行。
android:configChanges="orientation|screenSize"
答案 2 :(得分:0)
在清单文件中的活动标签中添加 android:configChanges =“ orientation | screenSize” ,并在初始化youTubeThumbnailView时,将 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
}
});