上一篇文章 简单的手电筒程序(基于Camera类实现) 介绍了通过Camera类进行Flash操作,但是通过camera接口可能会有下面两个问题: 1. 因为是间接使用camera接口,所以时间上会有延时 ;2. 进行Camera相关操作,耗电会比较大。下面我们介绍另外一种方法,通过操作驱动节点进行操作,控制闪关灯亮灭。
我使用的手机是三星G5309W,节点文件为/sys/class/camera/flash/rear_flash,操作如下表:
Flash On | 写入 “1” |
Flash Off | 写入 “0” |
我估计并不是每个手机都适用,可以用adb命令 “echo "1" >/sys/class/camera/flash/rear_flash ”验证下你的手机是否支持,当然,前提是root的,因为是system节点,节点的权限如下
如果你发现你的手机支持上面的节点,哈哈,很高兴和可以和你继续share这个有趣的应用,如果没有这个节点,也不用桑心,下面涉及JNI操作,如果你对这个知识点有兴趣也是有所裨益。
大概的思路 : JAVA层通过JNI调用Native层函数进行节点操作
1. Java层的代码比较简单,主要是声明以及调用native函数
package com.saberhao.bulbjni;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.view.Menu;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.ToggleButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
public class BulbSwitch extends Activity {
ToggleButton tb;
static {
// load the JNI so
System.loadLibrary("BulbJNI");
}
//declare the JNI function.plz pay attention to the key word -- "native"
public native void TurnOn();
public native void TurnOff();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bulb_switch);
tb = (ToggleButton)findViewById(R.id.BulbButton);
tb.setOnCheckedChangeListener
(
new OnCheckedChangeListener()
{
@Override
public void onCheckedChanged(CompoundButton buttonView,boolean isChecked)
{
setBulbState(isChecked);
}
}
);
}
protected void setBulbState(boolean isChecked) {
// TODO Auto-generated method stub
ImageView iv=(ImageView)findViewById(R.id.ImageView);
iv.setImageResource((isChecked)?R.drawable.bulb_on:R.drawable.bulb_off);
if(isChecked)
TurnOn();
else
TurnOff();
ToggleButton tb=(ToggleButton)findViewById(R.id.BulbButton);
tb.setChecked(isChecked);
}
private void releaseImageViews() {
ImageView iv=(ImageView)findViewById(R.id.ImageView);
releaseImageView(iv);
}
private void releaseImageView(ImageView imageView) {
Drawable d = imageView.getDrawable();
if (d != null)
d.setCallback(null);
imageView.setImageDrawable(null);
imageView.setBackgroundDrawable(null);
}
}
2. 生成native h头文件
a .通过命令行进入工程目录(D:\workspace\BulbJNI)
#include <jni.h>
/* Header for class com_saberhao_bulbjni_BulbSwitch */
#ifndef _Included_com_saberhao_bulbjni_BulbSwitch
#define _Included_com_saberhao_bulbjni_BulbSwitch
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_com_saberhao_bulbjni_BulbSwitch_TurnOn
(JNIEnv *, jobject);
JNIEXPORT void JNICALL Java_com_saberhao_bulbjni_BulbSwitch_TurnOff
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
当然,也可以通过NDK生成头文件,详情请参考文末的参考文献
3.编写Native处理函数
在JNI目录下新建一个Cpp文件,用于实现Native函数,这个也是本应用最重要的一步
#include<jni.h>
#include<stdio.h>
#include <fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include"com_saberhao_bulbjni_BulbSwitch.h"
#include <android/log.h>
#include <unistd.h>
#define LOG_TAG "BulbJNI"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define TRUNON "1"
#define TRUNOFF "0"
FILE *fp;
int fd;
int fd0;
JNIEXPORT void JNICALL Java_com_saberhao_bulbjni_BulbSwitch_TurnOn
(JNIEnv *, jobject)
{
fd=open("/sys/class/camera/flash/rear_flash",O_WRONLY);
if(fd < 0)
{
LOGE("[turn on]open device error ");
}else{
LOGI("turn on the flash");
write(fd,TRUNON,sizeof(TRUNON));
}
close(fd);
if(fd0 < 0)
{
LOGE("[turn on]open test0 device error ");
}else{
//rewind(fp);
LOGI("turn on test0 the flash");
write(fd0,TRUNON,sizeof(TRUNON));
}
close(fd0);
}
JNIEXPORT void JNICALL Java_com_saberhao_bulbjni_BulbSwitch_TurnOff
(JNIEnv *, jobject)
{
fd=open("/sys/class/camera/flash/rear_flash",O_WRONLY);
if(fd < 0)
{
LOGE("[turn off]open device error ");
}else{
LOGI("turn off the flash");
write(fd,TRUNOFF,sizeof(TRUNOFF));
}
close(fd);
}
这部分代码主要实现对节点的操作,也是本应用的核心,有一点是需要注意的,上文提到sys节点的权限,对同个所有者或者同个group是可写的,其他不赋予权限,所以在定义句柄fd的时候必须使用O_WRONLY(只写),而不能使用到 O_RDWR(可读可写),要不然会出现fd初始化失败的情况,这个问题也纠结了我很久~
4.编写JNI mk文件
native函数提供实现方法,需要编译成so文件,才能被JAVA层调用,在JNI文件夹下创建Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_C_INCLUDES := \$(JNI_H_INCLUDE)
LOCAL_PRELINK_MODULE := false
LOCAL_LDLIBS := -llog -lGLESv2
LOCAL_MODULE := libBulbJNI
LOCAL_SRC_FILES :=BulbJNI.cpp
include $(BUILD_SHARED_LIBRARY)
LOCAL_LDLIBS := -llog -lGLESv2 //在Native函数中打log必须添加的lib
LOCAL_MODULE_TAGS := eng //在Eng模式下编译
LOCAL_SRC_FILES -编译的源文件
LOCAL_MODULE -编译的目标对象
5. 编译生成so文件
a. 配置NDK
打开Eclipse,点Window->Preferences->Android->NDK,设置NDK路径,例如D:\android-ndk-r9d-windows-x86\android-ndk-r9d
b. 点Properties->C/C++ Build的Building Settings中去掉Use default build command,然后输入${NDKROOT}/ndk-build.cmd
c. 在C/C++ Build中点击Environment,点Add...添加环境变量NDKROOT,值为NDK的根目录
全部编译之后,就可以再libs目录下看到对应的so文件
6. 添加app mk文件
由于节点 /sys/class/camera/flash/rear_flash 为系统节点,普通应用无法直接操作,需要在xml中添加
android:sharedUserId="android.uid.system"
同时需要在 app的android.mk中添加
LOCAL_CERTIFICATE := platform
才能进行操作,但是eclipse中无法添加Android.mk,所以必须在源代码环境中编译生成apk,添加的mk如下
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_DEX_PREOPT := false
LOCAL_PRIVILEGED_MODULE := true
LOCAL_PACKAGE_NAME := BulbJNI
LOCAL_CERTIFICATE := platform
LOCAL_SRC_FILES := $(call all-subdir-java-files)
include $(BUILD_PACKAGE)
##################################################
# Use the folloing include to make our test apk.
include $(call all-makefiles-under,$(LOCAL_PATH))
最后,在源码中使用mm进行部分编译即可生成apk
运行效果如下:
Flash On | Flash Off |
源代码,请点击 这里下载,稍后会传到github~
相关参考文档:
http://jingyan.baidu.com/article/5d6edee22d908799eadeec9f.html
http://blog.csdn.net/sdvch/article/details/17492861
http://blog.csdn.net/vincent_czz/article/details/7199567
http://jingyan.baidu.com/article/5d6edee22d908799eadeec9f.html