在上一篇文章中我提到过在制作快捷中心的时候遇到Android碎片化的问题,首当其冲的就是这个FlashLight(闪光灯)在5.0系统上不能正常打开的问题。
除了Lollipop系统,在其之下的版本按照现有的代码方案,对FlashLight控制没有任何问题。可是同样的代码,在Lollipop系统中却失效了。肯定是碎片化造成的问题。
那么在解决这个问题之前,可以先看一看通常情况下FlashLight的控制方案。这个在Google上一搜一大堆,我照例只说关键点和开发中需要注意的地方。
打开
if (mCamera == null) { mCamera = Camera.open(); } mCamera.startPreview(); mParameters = mCamera.getParameters(); mParameters.setFlashMode(Parameters.FLASH_MODE_TORCH); //设置camera参数为Torch模式 mCamera.setParameters(mParameters);
mParameters = mCamera.getParameters(); mParameters.setFlashMode(Parameters.FLASH_MODE_OFF); //设置camera参数为OFF模式 mCamera.setParameters(mParameters); mCamera.stopPreview(); mCamera.release(); //记得一定要释放 mCamera = null;
调用以上两个代码即可完成FlashLight的开关,但是在很多的Demo中大家举的例子是在一个Activity中进行开关闭,这样Camera.Open()这个参数不会重复调用,也无需判断Camera的状态,可是在我需要做的例子里是在一个View里进行开关,当View消失的时候,FlashLight仍然要维持当前的状态(打开或者关闭),这个时候我们就需要用到Service了。和View之间的通信就使用广播。
public class FloatWindowService extends Service { private class FlashlightSwitchReceiver extends BroadcastReceiver { private String action = null; @Override public void onReceive(Context context, Intent intent) { if (QuickCenterParams.MESSAGE_FLASHLIGHT_TURN_ON.equals(action)) { if (mCamera == null) { mCamera = Camera.open(); } mCamera.startPreview(); mParameters = mCamera.getParameters(); mParameters.setFlashMode(Parameters.FLASH_MODE_TORCH); mCamera.setParameters(mParameters); } else if (QuickCenterParams.MESSAGE_FLASHLIGHT_TURN_OFF.equals(action)) { mParameters = mCamera.getParameters(); mParameters.setFlashMode(Parameters.FLASH_MODE_OFF); mCamera.setParameters(mParameters); mCamera.stopPreview(); mCamera.release(); mCamera = null; } } } //其他功能代码 }
最后再多加一个FlashLight能否使用的判断
/** * 判断手机是否支持闪光灯 * @param context * @return */ public static boolean isSupportFlashlight(Context context) { PackageManager packageManager = context.getPackageManager(); FeatureInfo[] features = packageManager.getSystemAvailableFeatures(); for(FeatureInfo f : features) { if(PackageManager.FEATURE_CAMERA_FLASH.equals(f.name)) return true; } return false; }
但是这套代码在Lollipop中却无法成功实现,百思不得其解,在Google上资料也比较少,刚好发现在Android Lollipop的通知栏中有闪光灯开关的控制按键,好吧,求人不如求己,去看源代码。
FlashLight在Lollipop版本上的具体方案
其实当我们在开发中把TargetSDKVersion设置为21的时候,就会发现原来写的Camera.Open()等方法全部划了根横线,说明在Lollipop的最新接口中已经提示开发者这个API有了替换方案,只是让我没想到的是直接不让用,够狠够诶,看来版本判断跑不了了,碎片化真是够够够够够蛋疼。
点进去看看描述
/* * * @deprecated We recommend using the new {@link android.hardware.camera2} API for new * applications. */ @Deprecated public class Camera { }可以看到已经明确提示用户使用camera2包下的代码了。这样就是为什么在Lollipop上之前的功能代码不能用的原因。
源码中的使用了观察者模式,注册监听,都可以不理睬,主要还是因为原理不同了。Lollipop中用了CameraDevice剥离Camera对象、CameraManager管理类来管理设备、通过CameraId作为识别,猜测可能这样做能够扩展多摄像头的使用功能,代码逻辑上也确实更合理,有别于之前的Camera粗暴的打开和关闭,现在的Open用了下面的方案,在打开的时候注册了CameraListener以及Handle,通过这样的方式与UI交互。
private void startDevice() throws CameraAccessException { mCameraManager.openCamera(getCameraId(), mCameraListener, mHandler); }而我们也只需要在调用的时候直接通过setFlashlight()设置FlashLight可用性以及通过killFlashlight()直接关闭FlashLight。
public synchronized void setFlashlight(boolean enabled) { if (mFlashlightEnabled != enabled) { mFlashlightEnabled = enabled; postUpdateFlashlight(); } } public void killFlashlight() { boolean enabled; synchronized (this) { enabled = mFlashlightEnabled; } if (enabled) { mHandler.post(mKillFlashlightRunnable); } }
private void updateFlashlight(boolean forceDisable) { try { boolean enabled; synchronized (this) { enabled = mFlashlightEnabled && !forceDisable; } if (enabled) { if (mCameraDevice == null) { startDevice(); return; } if (mSession == null) { startSession(); return; } if (mFlashlightRequest == null) { CaptureRequest.Builder builder = mCameraDevice .createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH); builder.addTarget(mSurface); CaptureRequest request = builder.build(); mSession.capture(request, null, mHandler); mFlashlightRequest = request; } } else { if (mCameraDevice != null) { mCameraDevice.close(); teardown(); } } } catch (CameraAccessException e) { Log.e(TAG, "Error in updateFlashlight", e); handleError(); } }
由于调用不同的API,并且在Lollipop上原来的接口不能再使用了,于是需要在实际的项目中做版本号适配,并且执行不同的使用方法
public class FloatWindowService extends Service { private class FlashlightSwitchReceiver extends BroadcastReceiver { private String action = null; @Override public void onReceive(Context context, Intent intent) { // TODO Auto-generated method stub action = intent.getAction(); if (Build.VERSION.SDK_INT >= 21 /*Build.VERSION_CODES.LOLLIPOP*/) { //版本适配 if (mFlashlightController == null) { mFlashlightController = new FlashlightController(context); } if (QuickCenterParams.MESSAGE_FLASHLIGHT_TURN_ON.equals(action)) { mFlashlightController.setFlashlight(true); //设置Torch } else if (QuickCenterParams.MESSAGE_FLASHLIGHT_TURN_OFF.equals(action)) { mFlashlightController.killFlashlight(); //关闭Torch } } else { //这块代码可以参考文章之前提到的 } //其他功能代码 }
当初做适配FlashLight这个碎片化的时候确实花了我不少精力,主要是花在查阅资料寻找问题上,找到Lollipop系统状态栏中的使用源码后,虽然可以拿来主义,但是还是要把它弄懂,相对一两行就能实现的代码,确实复杂了很多。如果有理解得不对或者说得不够清晰的地方,欢迎指正。