开发者

Android 集成Google Cast 异常问题解析

开发者 https://www.devze.com 2023-02-11 10:18 出处:网络 作者: Dai_Dev
目录GoogleCast 异常问题需求Fragment/Activity 启动时查询Available Cast DevicesFragment/Activity 处于前台时,动态获取Availabe Cast Devices问题分析解决问题1问题2总结GoogleCast 异常问题
目录
  • GoogleCast 异常问题
    • 需求
    • Fragment/Activity 启动时查询Available Cast Devices
    • Fragment/Activity 处于前台时,动态获取Availabe Cast Devices
    • 问题
    • 分析
  • 解决
    • 问题1
    • 问题2
  • 总结

    GoogleCast 异常问题

    项目中集成 Google Cast 功能时, 部分机型遇到一些问题

    需求

    项目中Cast需求有点特别,默认Cast Button处于不可见状态。 APP进入前台时,查询周围是否有可用Cast设备, 有才显示Cast Button, 否则隐藏

    根据流程, 我们有两种情况需要处理

    Fragment/Activity 启动时查询Available Cast Devices

    暂命名方法1

    代码实现如下: Fragment -> onResume(), checkCastDevices:

        public static boolean checkCastDevice(Context activity) {
            MediaRouter mediaRouter = MediaRouter.getInstance(activity);
            List<mediarouter.routeinfo> routes = mediaRouter.getRoutes();
            if (routes != null &amp;&amp; routes.size() != 0) {
                for (MediaRouter.RouteInfo info : routes) {
                    if (info.getDeviceType() != MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED
                            && !info.isDefault() && !info.isBluetooth() && !info.isDeviceSpeaker()) {
                        return true;
                    }
                }
            }
            return false;
        }
    

    Fragment/Activity 处于前台时,动态获取Availabe Cast Devices

    暂命名方法2

    监听了addCastStateListener CastContext.addCastStateListener(this)

        @Override
        public void onCastStateChanged(int i) {
            stateListenerListTemp.clear();
            stateListenerListTemp.addAll(stateListenerList);
            switch (i) {
                case CastState.CONNECTED:
                    for (CastStateListener listener : stateListenerListTemp) {
                        listener.connected();
                    }
                    break;
                case CastState.CONNECTING:
                    for (CastStateListener listener : stateListenerListTemp) {
                        listener.connecting();
                    }
                    break;
                case CastState.NO_DEVICES_AVAILABLE:  //has not available devices
                    for (CastStateListener listener : stateListenerListTemp) {
                        listener.noDevicesAvailable();  // hide cast button
                    }
                    break;
                case CastState.NOT_CONNECTED:  //has available devices, but not connect
                    for (CastStateListener listener : stateListenerListTemp) {
                        listener.notConnected();  // show cajsst button
                    }
                    break;
            }
            stateListenerListTemp.clear();
        }
    

    依据上述实现, 可以即时监听Cast Devices的变化,动态更新Cast Button的可见性

    问题

    新增了一个有个需求, Toolbar上新增了一个视图,需要根据Cast Button的可见性来设置视图可见性,即二者可见性互斥,但在测试中发现一些问题。

    部分设备上,例如Remi Note 4, 上述两个方法都无效

    • 问题1,方法1 只有在进程销毁后第一次启动可以获取周围Available Cast Devices,之后关闭APP重启一直返回false
    • 问题2,方法2 APP处于前台时,断开WIFI时,Cast Button消失,重新连接WIFI,Cast Button可见,但代码中并没有收到Cast Devices 状态更新的回调, 设置Cast Button Visibility的代码并未执行,这就出现一种情况:虽然Cast Button不可见,但是新增的视图却并未显示。

    分析

    • 问题1

    当App进程销毁后重新启动, 可以成功获取Cast Devices, 说明初始化CastContext时, 主动扫描了周边Routes, 而App关闭后重新打开, 部分设备未主动扫描, 所以获取状态失败。

    • 问题2

    当Cast Button可见时,断开WIFI连接, 因为没有收到onCastStateChanged的回调,CastButton.SetVisibility(View.GONE)并未执行, 但是Cast Button 却不可见了, 打开Developers options--Show layouts bounds开关, 发现Cast Button 在布局中存在,但是图标却不可见,那么有两种可能: - Cast Button的Drawable 被移除了 - Cast Button被设为View.INVISIBLE

    但是onCastStateChanged的回调并未执行, 但是Cast Button 状态却有变化,那只能说明Cast Button内部监听了连接状态的变化,并内部处理了展示状态。

    项目中Cast Button是一个MediaRouteButton, 查询源码

    public class MediaRouteButton extends View {
        private static final String TAG = "MediaRouteButton";
        private static ConnectivityReceiver sConnectivityReceiver;
        private int mVisibility = VISIBLE;
        private boolean mAlwaysVisible;
        public MediaRouteButton(Context context) {
            this(context, null);
        }
        public MediaRouteButton(Context context, AttributeSet attrs) {
            this(context, attrs, R.attr.mediaRouteButtonStyle);
        }
        public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
            super(MediaRouterThemeHelper.createThemedButtonContext(context), attrs, defStyleAttr);
            if (sConnectivityReceiver == null) {
                sConnectivityReceiver = new ConnectivityReceiver(context.getApplicationContext());
            }
        }
        @Override
        public void onAttachedToWindow() {
            super.onAttachedToWindow();
            sConnectivityReceiver.registerReceiver(this);
        }
        @Override
        public void onDetachedFromWindow(js) {
       javascript     if (!isInEditMode()) {
                sConnectivityReceiver.unregisterReceiver(this);
            }
            super.onDetachedFromWindow();
        }
        void refreshVisibility() {
            //set VISIBLE or INVISIBLE
            super.setVisibility(mVisibility == VISIBLE
                    && !(mAlwaysVisible || sConnectivityReceiver.isConnected())
     开发者_C教程               ? INVISIBLE : mVisibility);
        }
        /**
        * 监听连接状态的变化,刷新MediaRouteButton可见性
        */
        private static final class ConnectivityReceiver extends BroadcastReceiver {
            private final Context mContext;
            // If we have no information, assume that the device is connected
            private boolean mIsConnected = true;
            private List<MediaRouteButton> mButtons;
            ConnectivityReceiver(Context context) {
                mContext = context;
                mButtons = new ArrayList<MediaRouteButton>();
            }
            public void registerReceiver(MediaRouteButto编程客栈n button) {
                if (mButtons.size() == 0) {
                    IntentFilter intentFilter = new IntentFilter();
                    intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
                    mContext.registerReceiver(this, intentFilter);
                }
                mButtons.add(button);
            }
            public void unregisterReceiver(MediaRouteButton button) {
                mButtons.remove(button);
                if (mButtons.size() == 0) {
                    mContext.unregisterReceiver(this);
                }
            }
            public boolean isConnected() {
                return mIsConnected;
            }
            @Override
            public void onReceive(Context context, Intent intent) {
                final String action = intent.getAction();
                if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
                    boolean isConnected = !intent.getBooleanExtra(
                            ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
                    if (mIsConnected != isConnected) {
                        mIsConnected = isConnected;
                        for (MediaRouteButton button: mButtons) {
                            button.refreshVisibility();
                        }
                    }
                }
            }
        }
    }
    

    源码中可见,内部定义了ConnectivityReceiver来监听连接状态的变化, 从而内部控制MediaRouteButton的显示。

    解决

    问题1

    App启动时,虽然主动扫描周边Routes,来更新可以Cast Devices状态

        //Init Cast
        public static void initCast(Context context) {
            if (context == null)
                return;
            if (initialized) {
                CastContextManager.getInstance().addRouter(context);
                return;
            }
            initialized = true;
            //初始化CastContextManager
            ....
        }
        public void addRouter(Context context) {
            if(castContext !=null){
                MediaRouter mediaRouter = MediaRouter.getInstance(context);
                mediaRouter.removeCallback(callback);
                mediaRouter.addCallback(MediaRouteSelector.EMPTY, callback, MediaRouter.CALLBACK_FLAG_PERFORM_ACandroidTIVE_SCAN);
            }
        }
        private final MediaRouter.Callback callback = new MediaRouter.Callback() {
            @Override
            public void onProviderAdded(MediaRouter router, MediaRouter.ProviderInfo provider) {
                super.onProviderAdded(router, provider);
                ZenLogger.dd("MXMediaRouter", () -> "onProviderAdded  " + provider.getRoutes());
            }
            @Override
            public void onProviderRemoved(MediaRouter router, MediaRouter.ProviderInfo provider) {
                super.onProviderRemoved(router, provider);
                ZenLogger.dd("MXMediaRouter", () -> "onProviderRemoved  " + provider.getRoutes());
            }
            @Override
            public void onProviderChanged(MediaRouter router, MediaRouter.ProviderInfo provider) {
                super.onProviderChanged(router, provider);
                ZenLogger.dd("MXMediaRouter", () -> "onProviderChanged  " + provider.getRoutes());
            }
        };
    

    问题2

    因为无法收到onCastStateChanged的回调, 而且MediaRouteButton内部监听了连接状态的变化来更新显示,那么我们也效仿相同的实现,通过监听当前连接状态,结合MediaRouteButton的可见性,来判断Available Cast Devices

    • 当前连接断开, 直接隐藏MediaRouteButton
    • 当前连接恢复, 查询是否有Available Cast Devices,来设置MediaRouteButton可见
    • 再判断MediaRouteButton是否可见,来设置新视图的可见性
        public static boolean checkCastDevice(Context activity) {
            //如果当前无连接, 说明unavailable Cast devices
            if (!NetworkMonitor.isConnected(App.applicationContext())) return false;
            MediaRouter mediaRouter = MediaRouter.getInstance(activity);
            List<MediaRouter.RouteInfo> routes = mediaRouter.getRoutes();
            if (routes != null && routes.size() != 0) {
                for (MediaRouter.RouteInfo info : routes) {
                    if (info.getDeviceType() != MediaRouter.RouteInfo.CONNECTION_STATE_DISCONNECTED
                            && !info.isDefault() && !info.isBluetooth() && !info.isDeviceSpeaker()) {
                        return true;
                    }
                }
            }
            return false;
        }
        private NetworkMonitor.OnNetworkListener onNetWorkListener = (last, current) -> {
            showMediaRouteButton(CastHelper.checkCastDevice(getActivity()));
            //check MediaRouteButton isVisible
            ...
        };
    

    总结

    部分设备当非杀进程重新启动App,不能主动扫描routes, 需要在添加主动扫描,来获取最新的Cast Devices

    部分设备无法获取onCastStateChanged回调,且MediaRouteButton内部会监听连接状态设置显隐性, 所以我们也同样监听连接状态的变化,来判断Cast 是否可用

    以上就是android 集成Google Cast 异常问题解析的详细内容,更多关于Android集成Google Cast异常的资料请关注我们其它相关文章!

    0

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    关注公众号