Android 集成Google Cast 异常问题解析
Dai_Dev 人气:0GoogleCast 异常问题
项目中集成 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 && 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 cast 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() { if (!isInEditMode()) { sConnectivityReceiver.unregisterReceiver(this); } super.onDetachedFromWindow(); } void refreshVisibility() { //set VISIBLE or INVISIBLE super.setVisibility(mVisibility == VISIBLE && !(mAlwaysVisible || sConnectivityReceiver.isConnected()) ? 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(MediaRouteButton 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_ACTIVE_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 是否可用
加载全部内容