天天看点

FlashLight在Android5.0上的使用方法

背景

在上一篇文章中我提到过在制作快捷中心的时候遇到Android碎片化的问题,首当其冲的就是这个FlashLight(闪光灯)在5.0系统上不能正常打开的问题。

现象

除了Lollipop系统,在其之下的版本按照现有的代码方案,对FlashLight控制没有任何问题。可是同样的代码,在Lollipop系统中却失效了。肯定是碎片化造成的问题。

FlashLight在KitKat以及之下的具体方案

那么在解决这个问题之前,可以先看一看通常情况下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;
			}
		}
	}

	//其他功能代码
}
           

还有几个个需要注意的地方,Camera的状态是无法判断的,所以需要本地存储。另外一个就是在关闭FlashLight的同时一定要释放掉Camera,否则下次选择会崩溃。最后一个是需要在manifest中打开Camera权限。

最后再多加一个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上之前的功能代码不能用的原因。

FlashlightController流程

FlashLight在Android5.0上的使用方法

差异

源码中的使用了观察者模式,注册监听,都可以不理睬,主要还是因为原理不同了。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);
	}
}
           

同时通过之前注册的Handle更新FlashLight的状态

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();
	}
}	
           

FlashLight碎片化适配

由于调用不同的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系统状态栏中的使用源码后,虽然可以拿来主义,但是还是要把它弄懂,相对一两行就能实现的代码,确实复杂了很多。如果有理解得不对或者说得不够清晰的地方,欢迎指正。