ConstraintLayout 中的 MoPubRecyclerAdapter 和 Facebook 原生广告崩溃

Crash with MoPubRecyclerAdapter and Facebook Native Ads in ConstraintLayout

预计

MoPubRecyclerAdapter 预计会使用定义的 ConstraintLayout.

膨胀原生 Facebook RecyclerView 单元格

观察到

错误 对于从 Facebook 原生广告创建的 MoPubRecyclerAdapterMoPubRecyclerAdapter 会间歇性崩溃。此问题已在 MoPub SDK forum and MoPub Android Mediation GitHub 存储库中记录。

日志


Fatal Exception: java.lang.ClassCastException: androidx.constraintlayout.widget.ConstraintLayout cannot be cast to android.widget.RelativeLayout
       at com.mopub.nativeads.FacebookAdRenderer$FacebookNativeViewHolder.fromViewBinder(FacebookAdRenderer.java:139)
       at com.mopub.nativeads.FacebookAdRenderer.renderAdView(FacebookAdRenderer.java:58)
       at com.mopub.nativeads.FacebookAdRenderer.renderAdView(FacebookAdRenderer.java:28)
       at com.mopub.nativeads.NativeAd.renderAdView(NativeAd.java:166)
       at com.mopub.nativeads.MoPubStreamAdPlacer.bindAdView(MoPubStreamAdPlacer.java:433)
       at com.mopub.nativeads.MoPubRecyclerAdapter.onBindViewHolder(MoPubRecyclerAdapter.java:424)
       at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:6781)
       at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:6823)
       at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:5752)
       at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6019)
       at androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:286)
       at androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:343)
       at androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:359)
       at androidx.recyclerview.widget.GapWorker.prefetch(GapWorker.java:366)
       at androidx.recyclerview.widget.GapWorker.run(GapWorker.java:397)
       at android.os.Handler.handleCallback(Handler.java:873)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:205)
       at android.app.ActivityThread.main(ActivityThread.java:6991)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:884)

实施

facebook_native_ad_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/native_outer_view"
    style="@style/AdContentCardStyle"
    android:layout_width="match_parent"
    android:layout_height="@dimen/cell_content_feed_height"
    android:textDirection="locale">

    <TextView
        android:id="@+id/native_title"
        style="@style/CellCreatorStyle"
        app:layout_constraintBottom_toBottomOf="@+id/guideline"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/native_icon_image" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintBottom_toTopOf="@id/native_media_view"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/sponsored"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/sponsored"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/guideline" />

    <com.facebook.ads.AdIconView
        android:id="@+id/native_icon_image"
        android:layout_width="@dimen/native_icon_image_dimen"
        android:layout_height="@dimen/native_icon_image_dimen"
        android:paddingRight="@dimen/padding_tiny"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.facebook.ads.MediaView
        android:id="@+id/native_media_view"
        style="@style/AdCellPreviewImageStyle"
        android:contentDescription="@string/native_main_image"
        app:layout_constraintBottom_toTopOf="@id/native_text"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/sponsored" />

    <TextView
        android:id="@+id/native_text"
        style="@style/CellTitleStyle"
        app:layout_constraintBottom_toTopOf="@id/native_cta"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/native_media_view"
        tools:text="@string/learn_more" />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/native_ad_choices_relative_layout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="left"
        app:layout_constraintBottom_toBottomOf="@id/native_cta"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="@id/native_cta" />

    <TextView
        android:id="@+id/native_cta"
        style="@style/NativeCtaStyle"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/native_text"
        tools:text="@string/learn_more" />

</androidx.constraintlayout.widget.ConstraintLayout>

SomeFragment.kt

adapter = FeedAdapter(feedViewModel, viewEvent)
moPubAdapter = MoPubRecyclerAdapter(
                    requireActivity(),
                    adapter,
                    MoPubNativeAdPositioning.MoPubServerPositioning())
            moPubAdapter.registerAdRenderer(FacebookAdRenderer(
                    FacebookViewBinder.Builder(fb_native_ad_item)
                            .titleId(native_title)
                            .textId(native_text)
                            .mediaViewId(native_media_view)
                            .adIconViewId(native_icon_image)
                            .adChoicesRelativeLayoutId(native_ad_choices_relative_layout)
                            .advertiserNameId(native_title)
                            .callToActionId(native_cta)
                            .build()))
            val viewBinder = ViewBinder.Builder(native_ad_item)
                    .titleId(native_title)
                    .textId(native_text)
                    .mainImageId(R.id.native_main_image)
                    .iconImageId(native_icon_image)
                    .callToActionId(native_cta)
                    .privacyInformationIconImageId(string.native_privacy_information_icon_image)
                    .build()
            moPubAdapter.registerAdRenderer(FlurryNativeAdRenderer(FlurryViewBinder(Builder(viewBinder))))
            moPubAdapter.registerAdRenderer(MoPubVideoNativeAdRenderer(
                    MediaViewBinder.Builder(fb_native_ad_item)
                            .mediaLayoutId(native_media_view)
                            .iconImageId(native_icon_image)
                            .titleId(native_title)
                            .textId(native_text)
                            .privacyInformationIconImageId(native_ad_choices_relative_layout)
                            .build()))
            moPubAdapter.registerAdRenderer(MoPubStaticNativeAdRenderer(viewBinder))
            moPubAdapter.setContentChangeStrategy(MOVE_ALL_ADS_WITH_CONTENT)
            contentRecyclerView.adapter = moPubAdapter

FeedApater.kt

@ExperimentalCoroutinesApi
class FeedAdapter(val viewModel: FeedViewModel, val viewEvent: FeedViewEvent)
    : PagedListAdapter<Content, FeedAdapter.ViewHolder>(DIFF_CALLBACK) {

    class ViewHolder(private var binding: CellContentBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(viewModel: FeedViewModel, content: Content, onClickListener: OnClickListener) {
            binding.viewModel = viewModel
            binding.data = content
            binding.clickListener = onClickListener
            binding.executePendingBindings()
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = CellContentBinding.inflate(inflater, parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        getItem(position)?.let { content ->
            holder.bind(viewModel, content, createOnClickListener(content, position))
        }
    }

    private fun createOnClickListener(content: Content, position: Int) = OnClickListener { view ->
}

环境

图书馆

implementation("com.mopub:mopub-sdk-native-static:5.11.1@aar") { transitive = true }
implementation("com.mopub:mopub-sdk-native-video:5.11.1@aar") { transitive = true }
implementation 'com.facebook.android:audience-network-sdk:5.1.0'
implementation 'com.mopub.mediation:facebookaudiencenetwork:5.1.0.0'
implementation 'com.flurry.android:ads:12.1.0@aar'
implementation 'com.flurry.android:analytics:12.1.0@aar'
implementation 'com.mopub.mediation:flurry:11.4.0.0'

Android 级别

设备

尝试的解决方案

MoPub SDK 的库版本已更新至 5.12.0,Facebook Audience Network 已更新至 5.8.0,Facebook 中介已更新至 5.8.0.0。待确定是否解决了上述崩溃问题。

implementation("com.mopub:mopub-sdk-native-static:5.12.0") { transitive = true }
implementation("com.mopub:mopub-sdk-native-video:5.12.0") { transitive = true }
implementation 'com.facebook.android:audience-network-sdk:5.8.0'
implementation 'com.mopub.mediation:facebookaudiencenetwork:5.8.0.0'

使用RelativeLayout代替ConstraintLayout

正如 MoPub 的工程师在此 GitHub issue 中指出的那样,Facebook 中介尚不兼容 ConstraintLayout

AdChoices icon XML view (with the ID native_ad_choices_relative_layout) needs to be a RelativeLayout due to internal changes in the 4.99.0+ version of the Facebook Audience Network SDK. The adapter expects a RelativeLayout here.

之前

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/native_ad_choices_relative_layout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="left"
        app:layout_constraintBottom_toBottomOf="@id/native_cta"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="@id/native_cta" />

之后

    <RelativeLayout
        android:id="@+id/native_ad_choices_relative_layout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="left" />

文档:Setup Ad Renderers for Native Ads

示例:github.com/mopub/mopub-sdk-android