Android 让Google Cast v3在不受支持的设备上运行

Android 让Google Cast v3在不受支持的设备上运行,android,chromecast,Android,Chromecast,CastV3框架具有一些功能,这些功能试图使它能够在没有Google Play服务的设备上运行,但在测试时我遇到了一些问题 在Kindle上,Google API返回服务_INVALID,并带有isUserResolvable true。 在onActivityResult在升级后返回ConnectionResult.SUCCESS的设备上,CastContext.getSharedInstance可能引发RuntimeError。 作为2的副作用,包含MiniControllerFragmen

CastV3框架具有一些功能,这些功能试图使它能够在没有Google Play服务的设备上运行,但在测试时我遇到了一些问题

在Kindle上,Google API返回服务_INVALID,并带有isUserResolvable true。 在onActivityResult在升级后返回ConnectionResult.SUCCESS的设备上,CastContext.getSharedInstance可能引发RuntimeError。 作为2的副作用,包含MiniControllerFragment的项的XML膨胀将失败。 我发现了一些错误

java.lang.RuntimeException: Unable to start activity ComponentInfo{##########.MainActivity}: android.view.InflateException: Binary XML file line #42: Error inflating class fragment

Caused by: java.lang.RuntimeException: 
  com.google.android.gms.dynamite.DynamiteModule$zzc: Remote load failed. No local fallback found.
    at com.google.android.gms.internal.zzauj.zzan(Unknown Source)
    at com.google.android.gms.internal.zzauj.zza(Unknown Source)
    at com.google.android.gms.cast.framework.CastContext.<init>(Unknown Source)
    at com.google.android.gms.cast.framework.CastContext.getSharedInstance(Unknown Source)
    at com.google.android.gms.cast.framework.media.uicontroller.UIMediaController.<init>(Unknown Source)
    at com.google.android.gms.cast.framework.media.widget.MiniControllerFragment.onCreateView(Unknown Source)

当我实现ViewStub时,我仍然在预发布测试机器中崩溃,因为它们返回了成功,但没有可用的CastContext。为了解决这个问题,我需要另一个测试来检查CastContext是否可创建。

在应用程序中需要一个单例/代码,如下所示

boolean gCastable = false;
boolean gCastTested = false;
public boolean isCastAvailable(Activity act, int resultCode ){
    if( gCastTested == true ){
        return gCastable;
    }

    GoogleApiAvailability castApi = GoogleApiAvailability.getInstance();
    int castResult = castApi.isGooglePlayServicesAvailable(act);
    switch( castResult ) {
        case ConnectionResult.SUCCESS:
            gCastable = true;
            gCastTested = true;
            return true;
     /*  This code is needed, so that the user doesn't get a 
      *
      *  your device is incompatible "OK" 
      *
      * message, it isn't really "user actionable"
      */
        case ConnectionResult.SERVICE_INVALID: // Result from Amazon kindle - perhaps check if kindle first??
            gCastable = false;
            gCastTested = true;
            return false;
      ////////////////////////////////////////////////////////////////
        default:
            if (castApi.isUserResolvableError(castResult)) {
                castApi.getErrorDialog(act, castResult, resultCode, new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialog) {
                        gCastable = false;
                        gCastTested = false;
                        return;
                    }
                }).show();
            } else {
                gCastTested = true;
                gCastable = false;
                return false;
            }
    }
    return gCastable;
}

public void setCastOK(Activity mainActivity, boolean result ) {
    gCastTested = true;
    gCastable = result;
}
和一个助手函数来检查我们是否知道强制转换的状态

public boolean isCastAvailableKnown() {
    return gCastable;
}
然而,为了处理返回成功的设备,我在App/singleton中还需要以下代码

当活动收到cast结果时,我们创建一个CastContext。希望是,如果应用程序能够创建CastContext,那么框架将以导致崩溃的相同方式成功

public boolean onCastResultReceived( Activity act, int result ) {
    boolean wasOk = false;
    if( result == ConnectionResult.SUCCESS ){
        try {
            CastContext ctx = CastContext.getSharedInstance(act );
            wasOk = true;
        } catch ( RuntimeException e ){
            wasOk = false;
        }
    }
    if( wasOk ) {
        setCastOK(act, true);
        return true;
    }else {
        setCastOK(act, false );
        return false;
    }
}
通过使用ViewStub和片段禁用迷你控制器的充气

Fragment mini_controller_Fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<fragment
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/cast_mini_controller"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    app:castShowImageThumbnail="true"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />
一旦创建修改如下

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);
    mApp = (MyApplication)getApplication();
    if( mApp.isCastAvailable( (Activity)this, GPS_RESULT )) {
        onCastAvailable();
    }

    ...
}
onActivityResult需要处理Google Play服务升级的结果

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if( requestCode == GPS_RESULT ) {
        if(mApp.onCastResultReceived( this, resultCode ) ){
            onCastAvailable();
        }
恢复

protected void onResume() {
    if( mCastContext != null && mCastStateListener != null ) {
        mCastContext.addCastStateListener(mCastStateListener);
        mCastContext.getSessionManager().addSessionManagerListener(
                mSessionManagerListener, CastSession.class);
        if (mCastSession == null) {
            mCastSession = CastContext.getSharedInstance(this).getSessionManager()
                    .getCurrentCastSession();
        }
        if (mQueueMenuItem != null) {
            mQueueMenuItem.setVisible(
                    (mCastSession != null) && mCastSession.isConnected());
        }
    }
    super.onResume();
}
暂停

protected void onPause() {
    super.onPause();
    if( mCastContext != null && mCastStateListener != null ) {
        mCastContext.removeCastStateListener(mCastStateListener);
        mCastContext.getSessionManager().removeSessionManagerListener(
                mSessionManagerListener, CastSession.class);
    }
}
类中的会话管理器侦听器

private final SessionManagerListener<CastSession> mSessionManagerListener =
        new MySessionManagerListener();
private class MySessionManagerListener implements SessionManagerListener<CastSession> {

    @Override
    public void onSessionEnded(CastSession session, int error) {
        if (session == mCastSession) {
            mCastSession = null;
        }
        invalidateOptionsMenu();
    }

    @Override
    public void onSessionResumed(CastSession session, boolean wasSuspended) {
        mCastSession = session;
        invalidateOptionsMenu();
    }

    @Override
    public void onSessionStarted(CastSession session, String sessionId) {
        mCastSession = session;
        invalidateOptionsMenu();
    }

    @Override
    public void onSessionStarting(CastSession session) {
    }

    @Override
    public void onSessionStartFailed(CastSession session, int error) {
    }

    @Override
    public void onSessionEnding(CastSession session) {
    }

    @Override
    public void onSessionResuming(CastSession session, String sessionId) {
    }

    @Override
    public void onSessionResumeFailed(CastSession session, int error) {
    }

    @Override
    public void onSessionSuspended(CastSession session, int reason) {
    }
}

你是如何解决你的问题的?通过检查GoogleServices版本?OnCasterSultReceived,可以在try块中创建CastContext。如果此操作失败,则不会使强制转换可用。我试图编辑答案来澄清这一点。但我没有使用你的解决方案。总的来说,我想问的是,您提到的问题的原因是什么?我的活动在OnActivityResult中获得了成功代码,但嵌入式GPS UI在充气时崩溃。考虑到它无法创建一个CastContext,在启用充气之前创建一个CastContext允许我预测结果。在我的情况下,很难重现。我也得到了成功代码。很少有设备会遇到这个问题。将viewstub放在一个try-catch中并观察它是否失败不是更容易吗?@AndreasRudolph,这会使演员阵容是否可用变得不那么清楚,如果他们没有正确处理捕捉,可能会破坏一些UI元素。如果我希望ViewStub正常工作,那么我觉得我最好控制结果。你是对的,我认为这是一个大的正确解决方案,而不是一个小的但不是目标明确的解决方案。一个人可以选择自己-谢谢你的帮助!
protected void onResume() {
    if( mCastContext != null && mCastStateListener != null ) {
        mCastContext.addCastStateListener(mCastStateListener);
        mCastContext.getSessionManager().addSessionManagerListener(
                mSessionManagerListener, CastSession.class);
        if (mCastSession == null) {
            mCastSession = CastContext.getSharedInstance(this).getSessionManager()
                    .getCurrentCastSession();
        }
        if (mQueueMenuItem != null) {
            mQueueMenuItem.setVisible(
                    (mCastSession != null) && mCastSession.isConnected());
        }
    }
    super.onResume();
}
protected void onPause() {
    super.onPause();
    if( mCastContext != null && mCastStateListener != null ) {
        mCastContext.removeCastStateListener(mCastStateListener);
        mCastContext.getSessionManager().removeSessionManagerListener(
                mSessionManagerListener, CastSession.class);
    }
}
private final SessionManagerListener<CastSession> mSessionManagerListener =
        new MySessionManagerListener();
private class MySessionManagerListener implements SessionManagerListener<CastSession> {

    @Override
    public void onSessionEnded(CastSession session, int error) {
        if (session == mCastSession) {
            mCastSession = null;
        }
        invalidateOptionsMenu();
    }

    @Override
    public void onSessionResumed(CastSession session, boolean wasSuspended) {
        mCastSession = session;
        invalidateOptionsMenu();
    }

    @Override
    public void onSessionStarted(CastSession session, String sessionId) {
        mCastSession = session;
        invalidateOptionsMenu();
    }

    @Override
    public void onSessionStarting(CastSession session) {
    }

    @Override
    public void onSessionStartFailed(CastSession session, int error) {
    }

    @Override
    public void onSessionEnding(CastSession session) {
    }

    @Override
    public void onSessionResuming(CastSession session, String sessionId) {
    }

    @Override
    public void onSessionResumeFailed(CastSession session, int error) {
    }

    @Override
    public void onSessionSuspended(CastSession session, int reason) {
    }
}
    int visibility = View.GONE;
    if( mApplication.isCastAvailableKnown( ) ) {
        CastSession castSession = CastContext.getSharedInstance(mApplication).getSessionManager()
                .getCurrentCastSession();
        if( castSession != null && castSession.isConnected() ){
            visibility = View.VISIBLE;
        }
    }
    viewHolder.mMenu.setVisibility( visibility);