Android开发之Sensors与摇一摇

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/AnalyzeSystem/article/details/51577060

Sensor概述

基于Android的设备有内置的传感器,测量运动,方向,和各种环境条件。这些传感器能够提供原始数据的高精度和准确度,并且是有用的如果你想要监测装置、定位的三维运动,或者你想监控在设备周围环境的变化。例如,一个可能的轨道的读数装置的重力传感器来推断用户的手势和身体的动作复杂,如倾斜、摇晃、旋转、摆动或。同样,一个天气应用程序可能使用的设备的温度传感器和湿度传感器来计算和报告。

Android平台支持的传感器三大类:

  • 运动传感器
    这些传感器测量加速度的力和旋转力沿三轴。这一类包括加速度传感器、重力传感器、陀螺仪、旋转矢量传感器。

  • 环境传感器
    这些传感器测量各种环境参数,如空气温度、压力、光照、湿度。这一类包括气压计、光度计、温度计。

  • 位置传感器
    这些传感器测量设备的物理位置。这一类包括定位传感器和磁力计。

你可以访问设备提供传感器和利用android sensor框架获得原始传感器数据。传感器框架提供了一些类和接口,帮助你完成各种传感器相关的任务。传感器的具体类型稍后源码中再来了解,我们可以访问这些传感器利用Android传感器框架获得原始传感器数据。传感器框架的一部分android.hardware包我们可以直接访问,包括相关的类和接口:

  • SensorManager

  • Sensor

  • SensorEvent

  • SensorEventListener

获取SensorManager的实例通过Context.getSystemService获取

private SensorManager mSensorManager;
...
mSensorManager=(SensorManager)getSystemService(Context.SENSOR_SERVICE);

传感器并不是所有手机都支持的,在原生android系统4.0以后才基本完全支持,我们该如何判断手机设备支持哪些传感器呢?且看下列代码块获取所有支持传感器和判断是否支持某个传感器

List<Sensor> deviceSensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);

//....................处女分割线.........................

private SensorManager mSensorManager;
  ...
  mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
  if (mSensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE) != null){
  // Success! There's a pressure sensor.
  }
  else {
  // Failure! No pressure sensor.
  }

Sensor的使用需要对应的Activity在对应的声明周期进行注册和取消注册,官方提供示例代码如下

public class SensorActivity extends Activity implements SensorEventListener{
  private SensorManager mSensorManager;
  private Sensor mLight;

  @Override
  public final void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    mSensorManager=(SensorManager)getSystemService(Context.SENSOR_SERVICE);
    mLight=mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
  }

  @Override
  public final void onAccuracyChanged(Sensor sensor,int accuracy){
    // Do something here if sensor accuracy changes.
  }

  @Override
  public final void onSensorChanged(SensorEvent event){
    // The light sensor returns a single value.
    // Many sensors return 3 values, one for each axis.
    //event.value数组对应的值 value[0] x轴**值,value[1] y轴**值,value[2] z轴**值
    floatlux=event.values[0];
    // Do something with this sensor value.
  }

  @Override
  protected void onResume(){
    super.onResume();
    mSensorManager.registerListener(this,mLight,SensorManager.SENSOR_DELAY_NORMAL);
  }

  @Override
  protected void onPause(){
    super.onPause();
    mSensorManager.unregisterListener(this);
  }
}

Sensor相关源码分析

SensorListener定义的两个方法分别是精度发生变化和xyz轴上的对应值发生变化执行对应函数回调(官方源码一个类一堆英文注释目的是为了让我们看不懂么,汉语多么掉渣天,一句话的事儿)


public interface SensorListener {

    public void onSensorChanged(int sensor, float[] values);

    public void onAccuracyChanged(int sensor, int accuracy);    
}

SensorManager通过getSystemService获取,具体实现原理可以参考我以前根据爱哥和何红辉编写的设计模式书籍学习记录的博客:http://blog.csdn.net/analyzesystem/article/details/50054163以及个人关注罗升阳博客收藏一篇http://blog.csdn.net/luoshengyang/article/details/6578352,最好实操对照浏览,看完助你超神。

废话过后我们接着跑SensorMananger源码(在通过android studio查阅源码过程遇到英文注释,如果你感觉很棘手,可以使用翔哥提供的插件,相关博文地址:http://blog.csdn.net/lmj623565791/article/details/51548272)SensorManager源码内部定义了许多常量多数是个各种传感器相关的初始化参数以及传感器的类型等,内部方法对我们有用处的也就是上面的注册于取消注册。以及传感器的响应延迟的默认值配置getDelay方法

SensorManager提供的register和unRegistern方法迭代,跟踪发现核心类LegacySensorManager,通过其构造函数发现,旋转传感器的速率与屏幕旋转的关联,通过LegacySensorManager.onRotationChanged(rotation)同步旋转的值,代码如下

 public LegacySensorManager(SensorManager sensorManager) {
        mSensorManager = sensorManager;

        synchronized (SensorManager.class) {
            if (!sInitialized) {
                sWindowManager = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                if (sWindowManager != null) {
                    // if it's null we're running in the system process
                    // which won't get the rotated values
                    try {
                        sRotation = sWindowManager.watchRotation(
                                new IRotationWatcher.Stub() {
                                    public void onRotationChanged(int rotation) {
                                        LegacySensorManager.onRotationChanged(rotation);
                                    }
                                }
                        );
                    } catch (RemoteException e) {
                    }
                }
            }
        }
    }

ServiceManager在我上面提到的博客有提及,IWindowManager属于IPC通信范畴的AIDL模块,稍后会有专门博客介绍。由于篇幅关系这里直接看register相关流程,unRegister略过

private boolean registerLegacyListener(int legacyType, int type,
            SensorListener listener, int sensors, int rate) {
        boolean result = false;
        // Are we activating this legacy sensor?
        if ((sensors & legacyType) != 0) {
            // if so, find a suitable Sensor
            //获取默认Sensor流程先查出所有Sensor集合,根据匹配type值的结果,如果有则返回相应Sensor
            Sensor sensor = mSensorManager.getDefaultSensor(type);
            if (sensor != null) {
                // We do all of this work holding the legacy listener lock to ensure
                // that the invariants around listeners are maintained.  This is safe
                // because neither registerLegacyListener nor unregisterLegacyListener
                // are called reentrantly while sensors are being registered or unregistered.
                synchronized (mLegacyListenersMap) {
                    // If we don't already have one, create a LegacyListener
                    // to wrap this listener and process the events as
                    // they are expected by legacy apps.
                    LegacyListener legacyListener = mLegacyListenersMap.get(listener);
                    if (legacyListener == null) {
                        // we didn't find a LegacyListener for this client,
                        // create one, and put it in our list.
                        //如果Sensor根据type匹配到了,但是缓存Map里面没有需要重新缓存
                        legacyListener = new LegacyListener(listener);
                        mLegacyListenersMap.put(listener, legacyListener);
                    }

                    // register this legacy sensor with this legacy listener
                    if (legacyListener.registerSensor(legacyType)) {
                        // and finally, register the legacy listener with the new apis
                        // 再回到SensorMananger注册的抽象方法
                        result = mSensorManager.registerListener(legacyListener, sensor, rate);
                    } else {
                        result = true; // sensor already enabled
                    }
                }
            }
        }
        return result;
    }

上例代码中提到的静态类LegacyListener实现了SensorEventerListener接口,重写onAccuracyChanged和onSensorChanged方法在一定条件下执行相应回调,而回掉函数的参数values在调用mapSensorDataToWindow方法根据Type赋值

public void onAccuracyChanged(Sensor sensor, int accuracy) {
            try {
                mTarget.onAccuracyChanged(getLegacySensorType(sensor.getType()), accuracy);
            } catch (AbstractMethodError e) {
                // old app that doesn't implement this method
                // just ignore it.
            }
        }

        public void onSensorChanged(SensorEvent event) {
            final float v[] = mValues;
            v[0] = event.values[0];
            v[1] = event.values[1];
            v[2] = event.values[2];
            int type = event.sensor.getType();
            int legacyType = getLegacySensorType(type);
            mapSensorDataToWindow(legacyType, v, LegacySensorManager.getRotation());
            if (type == Sensor.TYPE_ORIENTATION) {
                if ((mSensors & SensorManager.SENSOR_ORIENTATION_RAW)!=0) {
                    mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION_RAW, v);
                }
                if ((mSensors & SensorManager.SENSOR_ORIENTATION)!=0) {
                    v[0] = mYawfilter.filter(event.timestamp, v[0]);
                    mTarget.onSensorChanged(SensorManager.SENSOR_ORIENTATION, v);
                }
            } else {
                mTarget.onSensorChanged(legacyType, v);
            }
        }

Sensor的具体Type定义参照源码里面的常量字段定义太多了不细说。提供的基本方法获取设备相关信息

SensorEvent对象内部封装value数组以及速率等相关参数,纪录的数据在LegacySensorManager调用onSensorChanged方法被传入用于计算onSensorChanged方法回调需要的参数。


知识拓展

震动

需求如果在摇一摇,取到结果非空需要震动效果,这时需要用到Vibrator,首先震动需要相应的权限

 <uses-permission android:name="android.permission.VIBRATE" />

特别注意市面上已有部分手机支持android6.0,用户可以拒绝权限请求,这里我们需要兼容适配,可以参考我之前的一篇博文 Android开发之6.0运行时权限处理,我们为什么一定要给权限呢?VibratorService源码中进行了权限检查,没有权限会抛出异常

  @Override // Binder call
  public void vibrate(int uid, String opPkg, long milliseconds, int usageHint,
          IBinder token) {
      if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
                != PackageManager.PERMISSION_GRANTED) {
          throw new SecurityException("Requires VIBRATE permission");
      }
     ..................................
  }

Vibrator为我们提供两个直接操作方法vibrate(long[] pattern, int repeat)和cancel();pattern指代震动模式,repeat指代震动重复次数,对于传入参数的限制条件请参照下例代码

 if (pattern == null || pattern.length == 0
                    || isAll0(pattern)
                    || repeat >= pattern.length || token == null) {
                return;
            }

为了开发方便封装了一个VibratorHelper类


/**
 * Created by idea on 2016/6/6.
 * Must have permissions : <uses-permission android:name="android.permission.VIBRATE" />
 */
public class VibratorHelper {

    /**
     * 默认震动模式以及默认不重复震动
     * @param applicationContext
     */
    public static void startVibratorWithDefultPatternRepeat(Context applicationContext) {
        startVibratorWithDefultPattern(applicationContext, -1);
    }

    /**
     * 默认震动模式
     * @param applicationContext
     * @param repeatCount
     */
    public static void startVibratorWithDefultPattern(Context applicationContext, int repeatCount) {
        long[] pattern = {100,500,100,300,100,300,100,500};
        startVibrator(applicationContext,pattern,repeatCount);
    }

    /**
     * 不重复震动
     * @param applicationContext
     * @param pattern
     */
    public static void startVibratorWithNoRepeat(Context applicationContext, long[] pattern) {
        startVibrator(applicationContext,pattern,-1);
    }

    /**
     * 重复多次Vibrator设置repeatCount 如果只想震动一次,index设为-1
     * 想设置震动大小可以通过改变pattern来设定,如果开启时间太短,震动效果可能感觉不到
     * @param applicationContext 用于获取震动服务
     * @param pattern 自定义震动模式
     * @param repeatCount 自定义震动重复次数
     */
    public static void startVibrator(Context applicationContext, long[] pattern, int repeatCount) {
        Vibrator vibrator = (Vibrator) applicationContext.getSystemService(Context.VIBRATOR_SERVICE);
        vibrator.vibrate(pattern,repeatCount);
    }

    /**
     * 取消震动
     * @param vibrator
     */
    public static void cancelVibrator(Vibrator vibrator) {
        vibrator.cancel();
    }

}

关于Vibrator的深入源码层不再叙述不是本篇重点,如果你感兴趣可以查看VibratorService源码和InputDevice、InputManager相关源码,这里奉上VibratorService源码地址(其他类Android Studio内可以直接查看)https://github.com/android/platform_frameworks_base/blob/master/services/core/java/com/android/server/VibratorService.java#L568

声音播放

微信的摇一摇,检索到附近的人你会听“咔咔”的声音提示,类似这种效果是要使用到MediaPlayer.MediaPlayer的实例创建方法有好几种迭代,可以直接new 一个无参的对象,设置播放资源,也可以调用create方法传入路径等相关参数,播放流程:create>prepare>start 播放结束需要调用release释放,终断播放stop,下面是相关方法的具体含义:

isLooping():是否循环播放
isPlaying():是否正在播放
pause():暂停
prepare():准备(同步)
prepareAsync():准备(异步)
release():释放MediaPlayer对象
reset():重置MediaPlayer对象
seekTo(int msec):指定播放的位置(以毫秒为单位的时间)
start():开始播放
stop():停止播放
setOnCompletionListener(MediaPlayer.OnCompletionListener listener): 播放监听,播放结束回调onCompletion

界面结束注意释放资源,如果音视频还在播放注意stop然后释放(stop只是针对摇一摇,像qq音乐那种后台也可以播放,更能需求不同!!),setDataSource方法切换/设置播放资源,在设置资源后要重新调用prepare方法>start

阻止屏幕休眠

世面上的手机多数为定制机非原生,很多手机都提供了一些定制化的辅助功能,比如甩动手机熄灭屏幕,那么在我们摇一摇的时候需要保持屏幕常量,老臣就办不到了(一开始寻找了几种方案代码拦截都不成功),最后只有老实的手动关掉手机的摇一摇辅助功能,同时提供一篇不锁屏幕相关的博客,需要push 到 system/app 安装,地址http://blog.csdn.net/chenyafei617/article/details/6575621

常驻进程

在开发门禁app时需要摇一摇开门功能,不开app照样开门,这里就需要用到常驻进程,核心要素两点:

  1. 首先开机自启动服务

  2. 保证服务存活不被杀死

开机自启动服务只需要注册接收系统广播,添加对应的权限,接收到系统广播立即启动服务

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />  

<receiver android:name=".XXXReceiver">  
    <intent-filter>    
        <action android:name="android.intent.action.BOOT_COMPLETED" />    
    </intent-filter>  
</receiver>  
public class XXXReceiver extends BroadcastReceiver {  

    @Override  
    public void onReceive(Context context, Intent intent) {  
        // TODO Auto-generated method stub  
        Log.d("LibraryTestActivity", "recevie boot completed ... ");  
        context.startService(new Intent(context, XXXService.class));  
    }  
}  

android 开机自启动的几种方法,监听不到RECEIVE_BOOT_COMPLETED的处理办法,参考下面链接:http://www.itnose.net/st/6178717.html至于如何保证Service的存活,听过FM蜻蜓么,多个Service相互守护,普通的Service守护不能满足你的需求时,可以考虑AIDL 搞个进程通信配合Service完成杀不死的服务,不得不说这是非常流氓的软件,不到迫不得已不建议这样搞。


总结

上述知识点demo暂无,只作知识储备。如果你有兴趣可以仿写京东摇一摇和微信摇一摇,以及拨号键盘左右晃动让控件左右对齐、摇一摇开锁屏幕(PowerManager核心),慕课网有京东摇一摇实现视频


参考资料

https://developer.android.com/guide/topics/sensors/sensors_overview.html?hl=id
http://www.w2bc.com/Article/80927
http://blog.csdn.net/howard_liu1314/article/details/38977057

猜你喜欢

转载自blog.csdn.net/AnalyzeSystem/article/details/51577060