视频顺时针旋转变卡时为什么它的角速度要小于0

您所在位置: &
&nbsp&&nbsp&nbsp&&nbsp
2013人教版选修(2-2)《刚体平衡的条件》课件1.ppt 27页
本文档一共被下载:
次 ,您可全文免费在线阅读后下载本文档。
下载提示
1.本站不保证该用户上传的文档完整性,不预览、不比对内容而直接下载产生的反悔问题本站不予受理。
2.该文档所得收入(下载+内容+预览三)归上传者、原创者。
3.登录后可充值,立即自动返金币,充值渠道很便利
你可能关注的文档:
··········
··········
* 质点: 刚体: (力矩的)功 (力矩的)冲量 转动动能 角动量 动量矩 冲量矩 守恒 守恒 质点
的对比 1. 定轴转动的运动学问题 解法:利用定轴转动的运动学描述关系 习题基本类型
轴 P ? z r 3. 定轴转动的动力学问题 解法:利用定轴转动中的转动定律 步骤:
(1)审题,确定研究对象; (2)建立坐标系;
(3)对研究对象进行受力分析和受力矩分析,并按坐标系的正方向写出外力矩的表达式及规律方(注:受力分析和受力矩须取隔离体),并用线角量关系将
F = ma 与 M = Jα 联系起来; (4)计算对轴的转动惯量;
(5)解方程,求未知,并对结果进行必要的讨论。 4. 定轴转动中的功能问题 解法:利用动能定理和机械能守恒定律 5. 角动量原理及角动量守恒定律 6. 混合题型
解法:应用运动学公式、转动定律和角动量守恒定律。 1、一作匀变速转动的飞轮在10s内转了16圈,其末角速度为15rad/s,它的角加速度的大小等于多少? 解: 分别用ω0、 ω 、 ? 、 θ 、t表示初角速度、末角速度、角加速度、角位移和时间. =0.99rad/s2. 习题课 2、如图,一匀质细杆可绕通过其上端与杆⊥的水平光滑轴O旋转,初始状态为静止悬挂。现有一小球自左方水平打击细杆。设小球与细杆间为非弹性碰撞,则在碰撞过程中,对细杆与小球这一系统,动量、动能、对O轴的角动量等各量中,哪些是守恒的?为什么? 解: ∵动量不能描述刚体的转动,∴刚体转动问题中不谈动量。 ∵是非弹性碰撞,有动能转换为热能,∴动能不守恒。 ∵碰撞瞬间,(细杆+小球)系统所受对O轴的合外力矩为0,∴对O轴的角动量守恒。 3、对一个绕固定水平轴O匀速转动的圆盘,沿如图所示的同一水平直线从相反方向同时射入两颗质量相同、速率相等的子弹,并留在盘中,则子弹射入后转盘的角速度是否改变?如何变? 解: ∵(盘+两子弹)系统所受对O轴的合外力矩为0, 两子弹射入前对O轴的角动量等值、反向,正好抵消(?). 当两子弹射入后,系统对O的转动惯量J′>J, ∴系统角动量守恒。 故射入前系统的角动量只是转盘的角动量。 故?′< ? ,即转盘的角速度减小。 4、如图,一静止的均匀细棒,长L,质量M,可绕通过棒的端点且⊥棒长的光滑固定轴O在光滑水平面内转动,转动惯量为
. 一质量为m速率为v的子弹在水平面内沿与棒⊥的方向射入棒的自由端。设击穿棒后子弹的速率减为v/2,则此时棒的角速度为 (A)mv/ML
(B)3mv/2ML
(C)5mv/3ML (D)7mv/4ML. 解: 对(子弹+棒)系统,对O轴 选⊙为正向,则 ∴选(B) 5、一质量为m的蚂蚁,在有光滑竖直固定中心轴的圆盘边缘(圆盘放在光滑水平面上) ,沿逆时针方向爬行,它相对于地面的速率为v,此时圆盘正沿顺时针方向转动,相对于地面的角速度为? 0,设圆盘对中心轴的转动惯量为J,若蚂蚁遇到一面包屑(质量不计)后停止爬行,则圆盘的角速度为___ 解: ? 0 O · m R 对(蚂蚁+盘)系统,对O轴 选?为正向,则 6、(习题集p3 ,17) 一刚体以每分钟60转绕Z轴作匀速转动(
沿Z轴正方向)。设某时刻刚体上一点P的位置矢量为
,其单位为“10-2m”,若以“10-2m/s”为速度单位,则该时刻P点的速度为(
) ∴P点在转动平面内对圆心O′的矢径为 又ω=60rev/min=2πrad/s,则 该时刻P点的速度为 ∴选(B ). 7、(习题集p6,35) 如图所示,质点的质量为2kg,位置矢量为
,它受到力
的作用。这三个矢量均在OXY面内,且r=3.0m,v=4.0m/s,F=2N,则该质点对原点O的角动量
= ___ ;作用在质点上的力对原点的力矩
= ___. L 的大小: 8、(习题集p8,48) 转动着的飞轮的转动惯量为J,在t=0时角速度为?0.此后飞轮经历制动过程。阻力矩M的大小与角速度?的平方成正比,比例系数为k(k为大于0的常数)。当?= ?0 /3时,飞轮的角加速度?= ___.从开始制动到?= ?0 /3所经过的时间t= ___. 现
- k?2 分离变量积分: 由转动定律 M=J?, 9、(习题集p13.73) 在一光滑水平面上,有一轻弹簧,一端固定,一端连接一质量m=1kg的滑块,如图所示。弹簧自然长度l0=0.2m,倔强系数k=100N/m.设t=0时,弹簧长度为l0 ,滑块速度v0=5m/s,方向与弹簧垂直。在某一时刻,弹簧位于与初始位置垂直的位置,长度l=0.5m.求该时刻滑块速度的大小和方向。 以?表末速度与
正在加载中,请稍后...Android 硬件传感器
1. 传感器入门
自从苹果公司在2007年发布第一代iPhone以来,以前看似和手机挨不着边的传感器也逐渐成为手机硬件的重要组成部分。如果读者使用过iPhone、HTC Dream、HTC Magic、HTC Hero以及其他的Android手机,会发现通过将手机横向或纵向放置,屏幕会随着手机位置的不同而改变方向。这种功能就需要通过重力传感器来实现,除了重力传感器,还有很多其他类型的传感器被应用到手机中,例如磁阻传感器就是最重要的一种传感器。虽然手机可以通过GPS来判断方向,但在GPS信号不好或根本没有GPS信号的情况下,GPS就形同虚设。这时通过磁阻传感器就可以很容易判断方向(东、南、西、北)。有了磁阻传感器,也使罗盘(俗称指向针)的电子化成为可能。 在Android应用程序中使用传感器要依赖于android.hardware.SensorEventListener接口。通过该接口可以监听传感器的各种事件。SensorEventListener接口的代码如下:
package android.
public interface SensorEventListener
public void onSensorChanged(SensorEvent event);
public void onAccuracyChanged(Sensor sensor, int accuracy);
在SensorEventListener接口中定义了两个方法:onSensorChanged和onAccuracyChanged。当传感器的值发生变化时,例如磁阻传感器的方向改变时会调用onSensorChanged方法。当传感器的精度变化时会调用onAccuracyChanged方法。onSensorChanged方法只有一个SensorEvent类型的参数event,其中SensorEvent类有一个values变量非常重要,该变量的类型是float[]。但该变量最多只有3个元素,而且根据传感器的不同,values变量中元素所代表的含义也不同。
在解释values变量中元素的含义之前,先来介绍一下Android的坐标系统是如何定义X、Y、Z轴的。
下面是values变量的元素在主要的传感器中所代表的含义。
1.1 方向传感器
在方向传感器中values变量的3个值都表示度数,它们的含义如下:
values[0]:该值表示方位,也就是手机绕着Z轴旋转的角度。0表示北(North);90表示东(East);180表示南(South);270表示西(West)。如果values[0]的值正好是这4个值,并且手机是水平放置,表示手机的正前方就是这4个方向。可以利用这个特性来实现电子罗盘,实例76将详细介绍电子罗盘的实现过程。
values[1]:该值表示倾斜度,或手机翘起的程度。当手机绕着X轴倾斜时该值发生变化。values[1]的取值范围是-180≤values[1]≤180。假设将手机屏幕朝上水平放在桌子上,这时如果桌子是完全水平的,values[1]的值应该是0(由于很少有桌子是绝对水平的,因此,该值很可能不为0,但一般都是-5和5之间的某个值)。这时从手机顶部开始抬起,直到将手机沿X轴旋转180度(屏幕向下水平放在桌面上)。在这个旋转过程中,values[1]会在0到-180之间变化,也就是说,从手机顶部抬起时,values[1]的值会逐渐变小,直到等于-180。如果从手机底部开始抬起,直到将手机沿X轴旋转180度,这时values[1]会在0到180之间变化。也就是values[1]的值会逐渐增大,直到等于180。可以利用values[1]和下面要介绍的values[2]来测量桌子等物体的倾斜度。
values[2]:表示手机沿着Y轴的滚动角度。取值范围是-90≤values[2]≤90。假设将手机屏幕朝上水平放在桌面上,这时如果桌面是平的,values[2]的值应为0。将手机左侧逐渐抬起时,values[2]的值逐渐变小,直到手机垂直于桌面放置,这时values[2]的值是-90。将手机右侧逐渐抬起时,values[2]的值逐渐增大,直到手机垂直于桌面放置,这时values[2]的值是90。在垂直位置时继续向右或向左滚动,values[2]的值会继续在-90至90之间变化。
1.2 加速传感器
该传感器的values变量的3个元素值分别表示X、Y、Z轴的加速值。例如,水平放在桌面上的手机从左侧向右侧移动,values[0]为负值;从右向左移动,values[0]为正值。读者可以通过本节的例子来体会加速传感器中的值的变化。要想使用相应的传感器,仅实现SensorEventListener接口是不够的,还需要使用下面的代码来注册相应的传感器。
获得传感器管理器
SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
注册方向传感器
sm.registerListener(this, sm.getDefaultSensor(Sensor.TYPE_ORIENTATION),
SensorManager.SENSOR_DELAY_FASTEST);
如果想注册其他的传感器,可以改变getDefaultSensor方法的第1个参数值,例如,注册加速传感器可以使用Sensor.TYPE_ACCELEROMETER。在Sensor类中还定义了很多传感器常量,但要根据手机中实际的硬件配置来注册传感器。如果手机中没有相应的传感器硬件,就算注册了相应的传感器也不起任何作用。getDefaultSensor方法的第2个参数表示获得传感器数据的速度。SensorManager.SENSOR_DELAY_ FASTEST表示尽可能快地获得传感器数据。除了该值以外,还可以设置3个获得传感器数据的速度值,这些值如下:
SensorManager.SENSOR_DELAY_NORMAL:默认的获得传感器数据的速度。
SensorManager.SENSOR_DELAY_GAME:如果利用传感器开发游戏,建议使用该值。
SensorManager.SENSOR_DELAY_UI:如果使用传感器更新UI中的数据,建议使用该值。
1.3 重力感应器
加速度传感器的类型常量是Sensor.TYPE_GRAVITY。重力传感器与加速度传感器使用同一套坐标系。values数组中三个元素分别表示了X、Y、Z轴的重力大小。Android SDK定义了一些常量,用于表示星系中行星、卫星和太阳表面的重力。下面就来温习一下天文知识,将来如果在地球以外用Android手机,也许会用得上。
public static final float GRAVITY_SUN= 275.0f;
public static final float GRAVITY_MERCURY= 3.70f;
public static final float GRAVITY_VENUS= 8.87f;
public static final float GRAVITY_EARTH= 9.80665f;
public static final float GRAVITY_MOON= 1.6f;
public static final float GRAVITY_MARS= 3.71f;
public static final float GRAVITY_JUPITER= 23.12f;
public static final float GRAVITY_SATURN= 8.96f;
public static final float GRAVITY_URANUS= 8.69f;
public static final float GRAVITY_NEPTUNE= 11.0f;
public static final float GRAVITY_PLUTO= 0.6f;
public static final float GRAVITY_DEATH_STAR_I= 0.145f;
public static final float GRAVITY_THE_ISLAND= 4.f;
1.4 光线传感器
光线传感器的类型常量是Sensor.TYPE_LIGHT。values数组只有第一个元素(values[0])有意义。表示光线的强度。最大的值是f。Android SDK将光线强度分为不同的等级,每一个等级的最大值由一个常量表示,这些常量都定义在SensorManager类中,代码如下:
public static final float LIGHT_SUNLIGHT_MAX =f;
public static final float LIGHT_SUNLIGHT=f;
public static final float LIGHT_SHADE=20000.0f;
public static final float LIGHT_OVERCAST= 10000.0f;
public static final float LIGHT_SUNRISE= 400.0f;
public static final float LIGHT_CLOUDY= 100.0f;
public static final float LIGHT_FULLMOON= 0.25f;
public static final float LIGHT_NO_MOON= 0.001f;
上面的八个常量只是临界值。读者在实际使用光线传感器时要根据实际情况确定一个范围。例如,当太阳逐渐升起时,values[0] 的值很可能会超过LIGHT_SUNRISE ,当values[0] 的值逐渐增大时,就会逐渐越过LIGHT_OVERCAST ,而达到LIGHT_SHADE ,当然,如果天特别好的话,也可能会达到LIGHT_SUNLIGHT ,甚至更高。
1.5 陀螺仪传感器
陀螺仪传感器的类型常量是Sensor.TYPE_GYROSCOPE。values数组的三个元素表示的含义如下:
当手机逆时针旋转时,角速度为正值,顺时针旋转时,角速度为负值。陀螺仪传感器经常被用来计算手机已转动的角度,代码如下:
private static final float NS2S = 1.0f / .0f;
public void onSensorChanged(SensorEvent event)
if (timestamp != 0)
event.timesamp表示当前的时间,单位是纳秒(1百万分之一毫秒)
final float dT = (event.timestamp - timestamp) * NS2S;
angle[0] += event.values[0] * dT;
angle[1] += event.values[1] * dT;
angle[2] += event.values[2] * dT;
timestamp = event.
上面代码中通过陀螺仪传感器相邻两次获得数据的时间差(dT )来分别计算在这段时间内手机延X 、 Y 、Z 轴旋转的角度,并将值分别累加到angle 数组的不同元素上。
1.6 其他传感器
其他传感器在前面几节介绍了加速度传感器、重力传感器、光线传感器、陀螺仪传感器以及方向传感器。除了这些传感器外,Android SDK还支持如下的几种传感器。关于这些传感器的使用方法以及与这些传感器相关的常量、方法,读者可以参阅官方文档。
虽然AndroidSDK定义了十多种传感器,但并不是每一部手机都完全支持这些传感器。例如,Google Nexus S支持其中的9种传感器(不支持压力和温度传感器),而HTC G7只支持其中的5种传感器。如果使用了手机不支持的传感器,一般不会抛出异常,但也无法获得传感器传回的数据。读者在使用传感器时最好先判断当前的手机是否支持所使用的传感器。
2. 测试手机中有哪些传感器
我们可以通过如下三步使用传感器。
(1)编写一个截获传感器事件的类。该类必须实现android.hardware.SensorEventListener接口。
(2)获得传感器管理对象(SensorManager对象)。
(3)使用SensorManager.registerListener方法注册指定的传感器。
通过上面三步已经搭建了传感器应用程序的框架。而具体的工作需要在SensorEventListener接口的onSensorChanged和onAccuracyChanged方法中完成。SensorEventListener接口的定义如下:
package android.
public interfaceSensorEventListener
// 传感器数据变化时调用
public void onSensorChanged(SensorEventevent);
// 传感器精确度变化时调用
public void onAccuracyChanged(Sensorsensor, int accuracy);
SensorManager 对象通过getSystemService 方法获得,代码如下:
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
通常手机中包含了若干个传感器模块(如方向传感器、光线传感器等),因此,注册传感器需要指定传感器的类型,如下面的代码注册了光线传感器。
sensorManager.registerListener(this,sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT),
SensorManager.SENSOR_DELAY_FASTEST);
registerListener 方法有三个参数。第1 个参数是实现SensorEventListener 接口的对象。第2 个参数用于指定传感器的类型。AndroidSDK 预先定义了表示各种传感器的常量,这些常量都被放在Sensor 类中。例如,上面代码中的Sensor.TYPE_LIGHT 。第3 个参数表示传感器获得数据的速度。该参数可设置的常量如下:
上面四种类型获得传感器数据的速度依次递减。从理论上说,获得传感器数据的速度越快,消耗的系统资源越大。因此建议读者根本实际情况选择适当的速度获得传感器的数据。
如果想停止获得传感器数据,可以使用unregisterSensor 方法注销传感器事件对象。unregisterSensor 方法的定义如下:
public voidunregisterListener(SensorEventListener listener)
public voidunregisterListener(SensorEventListener listener, Sensor sensor)
unregisterSensor 方法有两个重载形式。第一个重载形式用于注销所有的传感器对象。第二个重载形式用于注销指定传感器的事件对象。其中Sensor 对象通过SensorManager.getDefaultSensor 方法获得。getDefaultSensor方法只有一个int 类型的参数,表示传感器的类型。如Sensor.TYPE_LIGHT 表示光线传感器。
注意:一个传感器对像可以处理多个传感器。也就是说,一个实现 SensorEventListener 接口的类可以接收多个传感器传回的数据。为了区分不同的传感器,需要使用 Sensor.getType 方法来获得传感器的类型。getType 方法的将在本节的例子中详细介绍。
通过SensorManager.getSensorList 方法可以获得指定传感器的信息,也可以获得手机支持的所有传感器的信息,代码如下:
// 获得光线传感器
List&Sensor&sensors = sensorManager.getSensorList(Sensor.TYPE_LIGHT);
// 获得手机支持的所有传感器
List&Sensor&sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
下面给出一个完整的例子来演示如何获得传感器传回的数据。本例从如下4 个传感器获得数据,同时输出了测试手机中支持的所有传感器名称。
本例需要在真机上运行。由于不同的手机可能支持的传感器不同(有的手机并不支持Android SDK 中定义的所有传感器),因此,如果运行程序后,无法显示某个传感器的数据,说明当前的手机并不支持这个传感器。笔者已使用Google Nexus S 测试了本例。如果读者使用的也是GoogleNexus S ,则会输出如图1 类似的信息。
图1 获得传感器传回的数据
本例的完整代码如下:
package mobile.android.
import java.util.L
import android.app.A
import android.hardware.S
import android.hardware.SensorE
import android.hardware.SensorEventL
import android.hardware.SensorM
import android.os.B
import android.widget.TextV
public class Main extends Activity implements SensorEventListener
private TextView tvA
private TextView tvM
private TextView tvL
private TextView tvO
private TextView tvS
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
获得SensorManager对象
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
注册加速度传感器
sensorManager.registerListener(this,
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_FASTEST);
注册磁场传感器
sensorManager.registerListener(this,
sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),
SensorManager.SENSOR_DELAY_FASTEST);
注册光线传感器
sensorManager.registerListener(this,
sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT),
SensorManager.SENSOR_DELAY_FASTEST);
注册方向传感器
sensorManager.registerListener(this,
sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),
SensorManager.SENSOR_DELAY_FASTEST);
tvAccelerometer = (TextView) findViewById(R.id.tvAccelerometer);
tvMagentic = (TextView) findViewById(R.id.tvMagentic);
tvLight = (TextView) findViewById(R.id.tvLight);
tvOrientation = (TextView) findViewById(R.id.tvOrientation);
tvSensors = (TextView)findViewById(R.id.tvSensors);
获得当前手机支持的所有传感器
List&Sensor& sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
for(Sensor sensor:sensors)
输出当前传感器的名称
tvSensors.append(sensor.getName() + "\n");
public void onSensorChanged(SensorEvent event)
通过getType方法获得当前传回数据的传感器类型
switch (event.sensor.getType())
case Sensor.TYPE_ACCELEROMETER:
处理加速度传感器传回的数据
String accelerometer = "加速度\n" + "X:" + event.values[0] + "\n"
+ "Y:" + event.values[1] + "\n" + "Z:" + event.values[2] + "\n";
tvAccelerometer.setText(accelerometer);
case Sensor.TYPE_LIGHT:
处理光线传感器传回的数据
tvLight.setText("亮度:" + event.values[0]);
case Sensor.TYPE_MAGNETIC_FIELD:
处理磁场传感器传回的数据
String magentic = "磁场\n" + "X:" + event.values[0] + "\n" + "Y:"
+ event.values[1] + "\n" + "Z:" + event.values[2] + "\n";
tvMagentic.setText(magentic);
case Sensor.TYPE_ORIENTATION:
处理方向传感器传回的数据
String orientation = "方向\n" + "X:" + event.values[0] + "\n"
+ "Y:" + event.values[1] + "\n" + "Z:" + event.values[2] + "\n";
tvOrientation.setText(orientation);
public void onAccuracyChanged(Sensor sensor, int accuracy)
上面的代码中使用了event.values数组中的数据来获得传感器传回的数据。这个values数组非常重要,它的长度为3。但不一定每一个数组元素 都有意义。对于不同的传感器,每个数组元素的含义不同。在下面的部分将详细介绍不同传感器中values数组各个元素的含义。
注意:虽然使用Sensor.TYPE_ALL可以获得手机支持的所有传感器信息,但不能使用Sensor.TYPE_ALL注册所有的传感器,也就是getDefaultSensor方法的参数值必须是某个传感器的类型常量,而不能是Sensor.TYPE_ALL。
3. 传感器应用
3.1 电子罗盘
电子罗盘又叫电子指南针。在实现本例之前,先看一下如图1所示的运行效果。
图1 电子罗盘
其中N、S、W和E分别表示北、南、西和东4个方向。
本例只使用了onSensorChanged事件方法及values[0]。由于指南针图像上方是北,当手机前方是正北时(values[0]=0),图 像不需要旋转。但如果不是正北,就需要将图像按一定角度旋转。假设当前values[0]的值是60,说明方向在东北方向。也就是说,手机顶部由北向东旋 转。这时如果图像不旋转,N的方向正好和正北的夹角是60度,需要将图像逆时针(从东向北旋转)旋转60度,N才会指向正北方。因此,可以使用在 11.2.3节介绍的旋转补间动画来旋转指南针图像,代码如下:
public void onSensorChanged(SensorEvent event)
if (event.sensor.getType() == Sensor.TYPE_ORIENTATION)
float degree = event.values[0];
以指南针图像中心为轴逆时针旋转degree度
RotateAnimation ra = new RotateAnimation(currentDegree, -degree,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
在200毫秒之内完成旋转动作
ra.setDuration(200);
开始旋转图像
imageView.startAnimation(ra);
保存旋转后的度数,currentDegree是一个在类中定义的float类型变量
currentDegree = -
上面的代码中使用了event.values数组中的数据来获得传感器传回的数据。这个values数组非常重要,它的长度为3。但不一定每一个数组元素 都有意义。对于不同的传感器,每个数组元素的含义不同。在下面的部分将详细介绍不同传感器中values数组各个元素的含义。
注意:虽然使用Sensor.TYPE_ALL可以获得手机支持的所有传感器信息,但不能使用Sensor.TYPE_ALL注册所有的传感器,也就是getDefaultSensor方法的参数值必须是某个传感器的类型常量,而不能是Sensor.TYPE_ALL。
还可以利用方向传感器做出更有趣的应用,例如利用values[1]或values[2]的变化实现一个计步器。由于人在走路时会上下振动,因此,可以通 过判断values[1]或values[2]中值的振荡变化进行计步。基本原理是在onSensorChanged方法中计算两次获得 values[1]值的差,并根据差值在一定范围之外开始计数,代码如下:
public void onSensorChanged(SensorEvent event)
lastPoint = event.values[1];
当两个values[1]值之差的绝对值大于8时认为走了一步
if (Math.abs(event.values[1] - lastPoint) & 8)
保存最后一步时的values[1]的峰值
lastPoint = event.values[1];
将当前计数显示在TextView组件中
textView.setText(String.valueOf(++count));
本例设置3个按钮用于控制计步的状态,这3个按钮可以控制开始计步、重值(将计步数清0)和停止计步。这3个按钮的单击事件代码如下:
public void onClick(View view)
String msg = "";
switch (view.getId())
case R.id.btnStart:
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
注册方向传感器
sm.registerListener(this, sm
.getDefaultSensor(Sensor.TYPE_ORIENTATION),
SensorManager.SENSOR_DELAY_FASTEST);
msg = "已经开始计步器.";
重置计步器
case R.id.btnReset:
count = 0;
msg = "已经重置计步器.";
case R.id.btnStop:
注销方向传感器
sm.unregisterListener(this);
count = 0;
msg = "已经停止计步器.";
textView.setText(String.valueOf(count));
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
运行本例后,单击【开始】按钮,将手机放在兜里,再走两步看看,计步的效果如图1所示。
图1 计步器
增强版计步器
程序运行界面如图4所示, 其中主窗体包括两个部分,上半部分用于显示最近7 天每天走过的步数, 以及今日走过的步数, 通过一个自定义的View 来实现; 下半部分用于摆放3个程序控制按钮。
图1 计步器主界面
在程序运行时自动连接数据库, 读取历史数据并将其可视化地显示到屏幕上。程序运行后会自动启动一个Service 组件, 用于检测手机的加速度状态, 当用户携带手机步行时, 传感器会捕获到这个动作并更新记录已走步数的计数器。如果此时程序正在前台显示, 那么在屏幕中除了刷新走过的步数之外, 还将播放一小段走路的动画。点击“删除数据” 按钮会删除掉数据库中存储的历史数据。点击“停止服务” 按钮会停止后台Service 的执行, 同时状态栏将不再显示计步器的Notification。点击“转为后台” 按钮将关闭程序界面, 但保留后台执行的Service。当用户按住手机屏幕的状态栏往下拖拉时, 会在展开的状态栏中看到本程序的Notification, 如图2 所示。
手机状态栏中的计步器
本应用程序使用了Android 平台内置的SQLite 嵌入式数据库, 数据库中包含一张名为“step_table” 的表, 用来存放历史的已走步数信息, 表1 列出了step_table 表各个字段的情况。
step_table表的结构
开发应用正式功能之前首先要开发对数据库访问的辅助类MySQLiteHelper, 其主要的功能为连接并打开SQLite 数据库,代码如下:
package wyf. //声明所在包
import android.content.C //引入相关类
…//省略部分引入相关类的代码
import android.database.sqlite.SQLiteOpenH
public class MySQLiteHelper extends SQLiteOpenHelper {
public static final String TABLE_NAME = "step_table";
public static final String ID = "id";
public static final String STEP = "step";
public static final String UPDATE_DATE = "up_date";
public MySQLiteHelper(Context context, String name, CursorFactory factory,int version) {//构造器
super(context, name, factory, version);
public void onCreate(SQLiteDatabase db) {
db.execSQL ("create table if not exists " + TABLE_NAME + "(" //创建数据库表
+ID+" integer primary key,"
+STEP + " integer)");
public void onUpgrade(SQLiteDatabase db, int oldVersion , int newVersion) {}//对onUpgrade 方法的重写
在上述代码中创建了一个继承自SQLiteOpenHelper 类的子类, 并重写了其中的onCreate 和onUpgrade 方法。onCreate 方法将在数据库第一次被创建时调用, 本案例在该方法中执行了创建表的代码。onUpgrade 方法在数据库版本发生变化时调用。
完成了数据库辅助类的开发后就可以开发WalkingActivity类了, 其是应用程序的用户界面, 主要功能是按照XML 布局文件的内容显示界面并与用户进行交互, 代码如下:
package wyf. //声明所在包
import java.util.ArrayL //引入相关类
import android.app.A
…//省略部分引入相关类的代码
import android.view.View.OnClickL
import android.widget.B
public class WalkingActivity extends Activity implements OnClickListener{
WalkingV //WalkingView 对象引用
//数据库名称
public static final String DB_NAME = "step.db";
MySQLiteH //声明数据库辅助类
SQLiteD //数据库对象
Button btnToB //转入后台按钮
Button btnStopS //停止服务按钮
Button btnDeleteD //删除数据按钮
StepUpdateR
//定义一个继承自BroadcastReceiver 的内部类 StepUpdateReceiver 来接受传感器的信息
public class StepUpdateReceiver extends BroadcastReceiver{
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();//获得Bundle
int steps = bundle.getInt("step");//读取步数
wv.stepsToday =
wv.isMoving =
wv.postInvalidate(); //刷新WalkingView
//重写onCreate 方法,在Activity 被创建时调用
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);//设置当前屏幕
wv = (WalkingView)
findViewById(R.id.walkingView);
btnToBackstage = (Button)
findViewById(R.id.btnDispose);
btnToBackstage.setOnClickListener(this);
btnStopService = (Button)findViewById(R.id.btnStop);
btnStopService.setOnClickListener(this);
btnDeleteData = (Button)findViewById(R.id.btnDeleteData);
btnDeleteData.setOnClickListener(this);
//注册Receiver
receiver = new StepUpdateReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("wyf.wpf.WalkingActivity");
registerReceiver(receiver, filter);
//启动注册了传感器监听的Service
Intent i = new Intent(this,WalkingService.class);
startService(i);
mh=new MySQLiteHelper(this,DB_NAME,null,1);
requireData(); //向Service 请求今日走过步数
//重写onDestroy 方法
protected void onDestroy() {
unregisterReceiver(receiver); //注销Receiver
super.onDestroy();
//重写OnClickListener 接口的onClick 方法
public void onClick(View view) {}
//方法:向Service 请求今日走过的步数
public void requireData(){}
上述代码为Walking Activity 类的代码框架, 由WalkingActivity 实现了OnClickListener 接口, 所以需要对接中的onClick 方法进行重写, 重写的onClick 方法代码如下:
public void onClick(View view) {
if(view == btnStopService){
//停止后台服务
Intent intent = new Intent();
intent.setAction("wyf.wpf.WalkingService");
intent.putExtra("cmd",
WalkingService.CMD_STOP);
sendBroadcast(intent);
} else if (view == btnToBackstage) {
finish();//转到后台
} else if (view == btnDeleteData) {
//查看历史数据
SQLiteDatabase db = (SQLiteDatabase)
openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
db.delete(MySQLiteHelper.TABLE_NAME, null, null);
db.close();
wv.stepsInWeek = wv.getSQLData("7");
wv.postInvalidate();
在WalkingActivity 的onCreate 方法的最后调用了require-Data 方法向Service 发送Intent 请求今日走过的步数, 该方法的代码如下:
public void requireData() {
Intent intent = new Intent(); //创建Intent
intent.setAction("wyf.wpf.WalkingService");
intent.putExtra("cmd", WalkingService.CMD_UPDATAE);
sendBroadcast(intent); //发出消息广播
完成了WalkingActivity 类的开发后就需要开发用于显示计步器的历史数据及绘制今日走过的步数及走步时动画的自定义View———WalkingView 了, 其代码框架如下:
package wyf.
import java.util.ArrayL
…//省略部分引入相关类的代码
import android.view.V
public class WalkingView extends View {
ArrayList&String& stepsInWeek=//存历史数据
int stepsToday=0; //记录今天走的步数
int gapY = 8; //屏幕最上面留出的空隙
int distY = 10; //每一条的间距
int cellHeight = 30; //每一条的高度
float STEP_MAX = 1000.0f; //每天最大的步数
int maxStepWidth = 280; //最大步数在屏幕中宽度
Bitmap [] //运动小人的图片数组
Bitmap [] //数字图片数组
Bitmap back_ //颜色渐变条
boolean isMoving =
int frameI //记录运动小人的帧索引
MySQLiteH //操作数据库的辅助类
SQLiteD //数据库操作对象
public WalkingView(Context context, AttributeSet attrs) {
super(context, attrs);
sprite = new Bitmap[5];
digit = new Bitmap[10];
//初始化图片
Resources res = getResources();
sprite[0] = BitmapFactory.decodeResource(res, R.drawable.act_1);
…//省略部分Bitmap 的创建代码
back_cell = BitmapFactory.decodeResource(res, R.drawable.back_cell);
//获取数据库中最近7 天内的数据
mh = new MySQLiteHelper(context, WalkingActivity.DB_NAME, null,1);
stepsInWeek = getSQLData("7");
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawPrevious(canvas);//画以前走过的步数
drawToday(canvas); //画今天走过的步数
//画今天走的步数
private void drawToday(Canvas canvas) {}
//画之前走过的步数
private void drawPrevious(Canvas canvas) {}
//从数据库中获取历史数据
public ArrayList&String& getSQLData(String limit){}
上述为WalkingView 类的代码框架, 在WalkingView 类的构造器中将需要用到的图片资源初始化的同时, 调用getSQLData方法获取数据库中的历史数据。WalkingView 类重写了onDraw 方法, 该方法需要调用drawPrevious 和drawToday 方法分别对历史数据和今日走步情况进行绘制。这3 个方法以及在drawToday 方法中调用到的drawDigits 方法的详细代码如下:
//画今天走的步数
private void drawToday(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.CYAN);
float strokewidth = paint.getStrokeWidth();
Style s = paint.getStyle();
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2.0f);
canvas.drawLine(0, 300, 320, 300, paint);
paint.setStyle(s);
paint.setStrokeWidth(strokewidth);//恢复画笔
//把当前步数换算为在屏幕上绘制的条宽度
int width = (int)(stepsToday/STEP_MAX*280);
canvas.drawBitmap(back_cell, 0, 320, paint);
paint.setColor(Color.BLACK);
canvas.drawRect(width, 320, 320, 320+cellHeight, paint);
//画出遮罩层
if(isMoving) { //如果在运动,就切换帧序列
canvas.drawBitmap(sprite[(++frameIndex)%5], width+20, 320,paint);
isMoving =
} else { //如果没在走步,就绘制静止的那张图片
canvas.drawBitmap(sprite[4], width+20, 320, paint);
drawDigit(canvas,width); //绘制数字
//画之前走过的步数
private void drawPrevious(Canvas canvas) {
Paint paint = new Paint();
for(int i=0; i & stepsInWeek.size(); i++) {
String os = stepsInWeek.get(i);
int s = Integer.valueOf(os).intValue();
int width = (int) (s/STEP_MAX * maxStep-Width); //求出指定的步数在统计条中占得宽度
int tempY = (cellHeight+distY)*i;
canvas.drawBitmap(back_cell, 0, (cellHeight+distY)*i, paint); //画出渐变条
paint.setColor(Color.BLACK);
canvas.drawRect(width, tempY, 320, tempY+cell-Height, paint);
paint.setTextAlign(Align.LEFT);
paint.setColor(Color.CYAN);
paint.setAntiAlias(true);
canvas.drawText("走了"+stepsInWeek.get(i)+"步", width, tempY+cellHeight/2, paint);
//从数据库中获取历史数据
public ArrayList&String& getSQLData(String limit) {
//获得SQLiteDatabase 对象
db = mh.getReadableDatabase();
String [] cols = {
MySQLiteHelper.ID,MySQLiteHelper.STEP
Cursor c = db.query (MySQLiteHelper.TABLE_NAME, cols, null, null, null, null, MySQLiteHelper.ID+" DESC",limit);
ArrayList&String& al = new ArrayList&String&();
for (c.moveToFirst(); !(c.isAfterLast()); c.moveToNext()) {
al.add(c.getString(1));
c.close();
db.close();
//将数字通过数字图片绘制到屏幕上
public void drawDigit(Canvas canvas,int width) {
String sStep = ""+stepsT
int l = sStep.length();
for (int i=0; i&l; i++) {
int index = sStep.charAt(i) - '0';
canvas.drawBitmap(digit[index], width+20+40+32*i, 320, null);//绘制数字图片
完成了数据库辅助类及界面部分的开发后就可以开发后台的服务类———WalkingService 了。WalkingService 继承自Service类, 其主要实现的功能包括如下几个方面:
(1) 注册或注销传感器监听器。
(2) 在手机屏幕状态栏显示Notification。
(3) 定时将今日走过的步数写入数据库。
(4) 与WalkingActivity 进行通信。
下面将围绕这4 个功能对WalkingService 类的代码逐一进行介绍, 其类框架与成员声明的代码如下:
package wyf.
import java.util.C
import android.app.N
…//省略部分引入相关类的代码
import android.os.M
import org.openintents.sensorsimulator.hardware.*;
public class WalkingService extends Service {
//SensorManager mySensorM
SensorManagerSimulator mySensorM
int steps=0;
boolean isActivityOn = //Activity 是否运行
boolean isServiceOn =
NotificationM//声明NotificationManager
long timeInterval = 24*60*60*1000;
//Handler 延迟发
//送消息的时延
final static int CMD_STOP = 0;
final static int CMD_UPDATAE = 1;
CommandR //声明BroadcastReceiver
Handler myHandler = new Handler() {//定时上传数据
public void handleMessage(Message msg) {
uploadData();
super.handleMessage(msg);
上述代码声明和创建了WalkingService 的成员变量。需要注意的是由于本案例将使用SensorSimulator 进行测试, 所以需要对正常代码进行修改。首先是成员变量mySensorManager 的声明, 代码中注释掉的部分为正常代码, 未被注释的是为了使用SensorSimulator 工具中相关类声明的引用。完成了类框架与成员声明代码的开发后就可以开发此Service的初始化方法onCreate 及其他相关功能方法了, 代码如下:
public void onCreate() {
super.onCreate();
wl = new WalkingListener(this); //创建监听器类
//初始化传感器
// mySensorManager = (SensorManager)
// getSystemService(SENSOR_SERVICE);
mySensorManager = SensorManagerSimulator.getSystemService(this, SENSOR_SERVICE);
mySensorManager.connectSimulator();
//注册监听器
mySensorManager.registerListener(wl, SensorManager.SENSOR_ACCELEROMETER, SensorManager.SENSOR_DELAY_UI);
nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
Calendar c = Calendar.getInstance();
long militime = c.getTimeInMillis();
//将Calendar 设置为第二天0 时
c.set(Calendar.DAY_OF_MONTH, c.get(Calendar.DAY_OF_MONTH)+1);
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
long nextDay = c.getTimeInMillis();
timeInterval = nextDay -
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
isServiceOn =
showNotification();//添加Notification
receiver = new CommandReceiver();
IntentFilter filter1 = new IntentFilter();
filter1.addAction("wyf.wpf.WalkingService");
registerReceiver(receiver, filter1);
//设定Message 并延迟到本日结束发送
if (isServiceOn) {
Message msg = myHandler.obtainMessage();
myHandler.sendMessageDelayed(msg, timeInterval);
//重写onDestroy 方法
public void onDestroy() {
mySensorManager.unregisterListener(wl);
mySensorManager =
nm.cancel(0);
unregisterReceiver(receiver);
super.onDestroy();
//重写onBind 方法
public IBinder onBind(Intent arg0) {
//方法:显示Notification
private void showNotification() {}
//方法:向数据库中插入今日走过的步数
public void uploadData(){}
//开发继承自BroadcastReceiver 的子类接收广播消息
class CommandReceiver extends BroadcastReceiver{}
上述代码包括了WalkingService 的几个成员方法, 可以看到在onCreate 方法中, 同样为了使用SensorSimulator 工具测试案例而注释掉了正常代码并将其替换为使用SensorSimulator 的相关代码。同时在onCreate 方法中, 还计算了从Service 被启动时刻到这一天的结束之间的时间间隔, 并将该时间间隔作为Handler对象发送消息的延迟, 这样在本天过完之后, 会及时地向数据库中插入数据。在Service 的onStart 方法中调用了showNotification 方法,该方法将会在手机的状态栏(显示信号强度、电池等状态的区域) 显示本程序的Notification, 展开状态栏并点击Notification 后会启动WalkingActivity。showNotification 方法的代码如下:
//方法:显示Notification
private void showNotification() {
Intent intent = new Intent(this,WalkingActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification myNotification = new Notification();
myNotification.icon = R.drawable.
myNotification.defaults = otification.DEFAULT_ALL;
myNotification.setLatestEventInfo(this, "计步器运行中", "点击查看", pi);
nm.notify(0,myNotification);
本程序使用Handler 来定时发送消息, 收到消息后会调用uploadData 方法将今日走过的步数插入到数据库, 该方法的代码如下:
//方法:向数据库中插入今日走过的步数
public void uploadData() {
MySQLiteHelper mh = new MySQLiteHelper(this, WalkingActivity.DB_NAME, null, 1);
SQLiteDatabase db = mh.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(MySQLiteHelper.STEP, this.steps);
db.insert(MySQLiteHelper.TABLE_NAME, MySQLiteHelper.ID, values);
Cursor c = db.query(MySQLiteHelper.TABLE_NAME, null, null, null, null, null, null);
c.close();
db.close(); //关闭数据库
mh.close();
if (isServiceOn) {//设置24 小时后再发同样的消息
Message msg = myHandler.obtainMessage();
myHandler.sendMessageDelayed(msg, 24*60*60*1000);
WalkingService 和WalingActivity 进行通信是通过注册CommandReceiver 组件来实现的, CommandReceiver 继承自BroadcastReceiver, 负责接收WalingActivity 发来的Intent。
CommandReceiver 类的代码如下:
//开发继承自BroadcastReceiver 的子类接收广播消息
class CommandReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
int cmd = intent.getIntExtra("cmd", -1);
switch(cmd) {
case WalkingService.CMD_STOP://停止服务
stopSelf();
case WalkingService.CMD_UPDATAE: //传数据
isActivityOn =
Intent i = new Intent();
i.setAction("wyf.wpf.WalkingActivity");
i.putExtra("step", steps);
sendBroadcast(i);
WalkingService 和WalingActivity 在进行通信时, 被广播的Intent 对象中的action 必 须和另一方的IntentFilter 设置的action相同, 并且为了保证action 的惟一性, 一般以应 用程序包名后跟一个字符串来定义action。另外在开发了WalkingService 的代码之后, 还需要在AndroidManifest.xml 文件中声明该Service, 否则该Service 对Android系统是不可见的。
完成了前面的大部分工作后, 就可以开发传感器监听接口的实现类——— WalkingListener 了。WalkingListener 实现了SensorListener接口, 其主要的功能是监听加速度传感器的变化并进行相应的处理, 代码如下:
package wyf.
import android.content.I //引入相关包
import android.hardware.SensorL
import android.hardware.SensorM
public class WalkingListener implements SensorListener {
WalkingS // WalkingService 引用
float [] preC
double currentTime=0,lastTime=0; //记录时间
float WALKING_THRESHOLD = 20;
public WalkingListener(WalkingService father) {
this.father =
public void onAccuracyChanged(int arg0, int arg1) {}
//传感器发生变化后调用该方法
public void onSensorChanged(int sensor, float[] values) {
if(sensor == SensorManager.SENSOR_ACCELEROMETER) {
analyseData(values);//调用方法分析数据
//方法:分析参数进行计算
public void analyseData(float[] values) {
//获取当前时间
currentTime=System.currentTimeMillis();
//每隔200MS 取加速度力和前一个进行比较
if (currentTime - lastTime & 200) {
if (preCoordinate == null) {//还未存过数据
preCoordinate = new float[3];
for (int i=0; i&3; i++) {
preCoordinate =
} else { //记录了原始坐标的话,就进行比较
int angle = calculateAngle(values, preCoordinate);
if(angle &= WALKING_THRESHOLD) {
father.steps++; //步数增加
updateData(); //更新步数
for(int i=0; i&3; i++) {
preCoordinate =
lastTime = currentT//重新计时
//方法:计算加速度矢量角度的方法
public int calculateAngle(float[] newPoints,float[] oldPoints){}
//方法:向Activity 更新步数
public void updateData(){}
WalkingListener 类的代码中, 主要是对SensorListener 接口中的onSensorChanged 方法进行了重写, 在该方法中将读取到的传感器采样值传给analyseData 方法进行分析。在analyseData 方法中,调用了calculateAngle 方法来计算固定的时间间隔间手机加速度向量方向的夹角。calculateAngle方法的代码如下:
//方法:计算两个加速度矢量夹角的方法
public int calculateAngle(float[] newPoints, float[] oldPoints) {
int angle=0;
float vectorProduct=0; //向量积
float newMold=0; //新向量的模
float oldMold=0; //旧向量的模
for (int i=0; i&3; i++) {
vectorProduct += newPoints*oldP
newMold += newPoints*newP
oldMold += oldPoints*oldP
newMold = (float)Math.sqrt(newMold);
oldMold = (float)Math.sqrt(oldMold);
//计算夹角的余弦
float cosineAngle=(float)(vectorProduct/(newMold*oldMold));
//通过余弦值求角度
float fangle = (float)Math.toDegrees(Math.acos(cosineAngle));
angle = (int)
//返回向量的夹角
如果calculateAngle 方法返回的加速度向量角度变化超过了程序中设定的阈值, 应用程序将WalkingService 中已走步数计数器加1, 并调用updateData 方法将更新的步数传递给WalkingActivity 显示到界面, 该方法代码如下:
public void updateData(){
Intent intent = new Intent(); //创建Intent 对象
intent.setAction("wyf.wpf.WalkingActivity");
intent.putExtra("step", father.steps);//添加步数
father.sendBroadcast(intent); //发出广播
完成了应用程序代码的开发之后, 就可以将应用程序打包安装调试了。在Eclipse 中构建本项目, 完成构建后本应用程序项目文件夹下bin 目录中的JBQ.apk 即为本计步器应用程序的发布apk 包。将此apk 包安装到手机模拟器, 然后启动SensorSimulator桌面端, 如图3 所示。
测试传感器
运行SensorSimulator 桌面端之后, 还需要在模拟器上安装SensorSimulator 的客户端, 根据桌面端显示的IP 地址和端口号进行响应的配置。配置好SensorSimulator 之后, 就可以运行已经安装过的计步器程序。在SensorSimulator 的桌面端可以模拟手机的动作变化从而达到调试传感器应用程序的目的。要特别注意的是, 在调试完成真正发布应用程序前, 需要将WalkingService 类中使用SensorSimulator 的代码注释掉, 将真正使用物理传感器的代码去掉注释。最后再次构建项目, 这样得到的apk 包就是最终真正的发布版了。
通过开发计步器应用程序, 读者应该对Android 平台下开发传感器应用的流程有了一定的了解。传感器的特性和Android平台的开放性结合在一起, 使得在移动手机终端上开发各种新奇有趣的传感器应用成为可能, 同时也为开发人员开辟一个新的应用领域。可以预见, 在不久的将来, Android 嵌入式平台下的传感器应用必将大放光彩。
4 在模拟器上模拟重力感应
众所周知,Android 系统支持重力感应,通过这种技术,可以利用手机的移动、翻转来实现更为有趣的程序。但遗憾的是,在Android 模拟器上是无法进行重力感应测试的。既然Android 系统支持重力感应,但又在模拟器上无法测试,该怎么办呢?别着急,天无绝人之路,有一些第三方的工具可以帮助我们完成这个工作,本节将介绍一种在模拟器上模拟重力感应的工具(sensorsimulator )。这个工具分为服务端和客户端两部分。服务端是一个在PC 上运行的Java Swing GUI 程序,客户端是一个手机程序(apk 文件),在运行时需要通过客户端程序连接到服务端程序上才可以在模拟器上模拟重力感应。
读者可以从下面的地址下载这个工具:
进入下载页面后,下载如图1 所示的黑框中的zip 文件。
图1 sensorsimulator 下载页面
将zip 文件解压后,运行bin 目录中的sensorsimulator.jar 文件,会显示如图2 所示的界面。界面的左上角是一个模拟手机位置的三维图形,右上角可以通过滑杆来模拟手机的翻转、移动等操作。
图2 sensorsimulator 主界面
下面来安装客户端程序,先启动Android 模拟器,然后使用下面的命令安装bin 目录中的SensorSimulatorSettings.apk 文件。
adb install SensorSimulatorSettings.apk
如果安装成功,会在模拟器中看到如图3 所示黑框中的图标。运行这个程序,会进入如图4 所示的界面。在IP 地址中输入如图3 所示黑框中的IP (注意,每次启动服务端程序时这个IP 可能不一样,应以每次启动服务端程序时的IP 为准)。最后进入【Testing 】页,单击【Connect 】按钮,如果连接成功,会显示如图5 所示的效果。
图3 安装客户端设置软件
图4 进行客户端设置
下面来测试一下SensorSimulator 自带的一个demo ,在这个demo 中输出了通过模拟重力感应获得的数据。
这个demo 就在samples 目录中,该目录有一个SensorDemo 子目录,是一个Eclipse 工程目录。读者可以直接使用Eclipse 导入这个目录,并运行程序,如果显示的结果如图5 所示,说明成功使用SensorSimulator 在Android模拟器上模拟了重力感应。
图5 测试连接状态
图6 测试重力感应demo
5. 手机翻转静音
与手机来电一样,手机翻转状态(重力感应)也由系统服务提供。重力感应服务(android.hardware.SensorManager 对象)可以通过如下代码获得:
SensorManager sensorManager =(SensorManager)getSystemService(Context.SENSOR_SERVICE);
本例需要在模拟器上模拟重力感应,因此,在本例中使用SensorSimulator 中的一个类(SensorManagerSimulator )来获得重力感应服务,这个类封装了SensorManager 对象,并负责与服务端进行通信,监听重力感应事件也需要一个监听器,该监听器需要实现SensorListener 接口,并通过该接口的onSensorChanged 事件方法获得重力感应数据。本例完整的代码如下:
package net.blogjava.
import org.openintents.sensorsimulator.hardware.SensorManagerS
import android.app.A
import android.content.C
import android.hardware.SensorL
import android.hardware.SensorM
import android.media.AudioM
import android.os.B
import android.widget.TextV
public class Main extends Activity implements SensorListener {
private TextView tvSensorS
private SensorManagerSimulator sensorM
public void onAccuracyChanged(int sensor, int accuracy) {
public void onSensorChanged(int sensor, float[] values) {
switch (sensor) {
case SensorManager.SENSOR_ORIENTATION:
获得声音服务
AudioManager audioManager = (AudioManager)
getSystemService(Context.AUDIO_SERVICE);
在这里规定翻转角度小于-120度时静音,values[2]表示翻转角度,也可以设置其他角度
if (values[2] & -120) {
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
audioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
tvSensorState.setText("角度:" + String.valueOf(values[2]));
protected void onResume() {
注册重力感应监听事件
sensorManager.registerListener(this, SensorManager.SENSOR_ORIENTATION);
super.onResume();
protected void onStop() {
取消对重力感应的监听
sensorManager.unregisterListener(this);
super.onStop();
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
通过SensorManagerSimulator对象获得重力感应服务
sensorManager = (SensorManagerSimulator) SensorManagerSimulator
.getSystemService(this, Context.SENSOR_SERVICE);
连接到服务端程序(必须执行下面的代码)
sensorManager.connectSimulator();
在上面的代码中使用了一个SensorManagerSimulator 类,该类在SensorSimulator 工具包带的sensorsimulator-lib.jar 文件中,可以在lib 目录中找到这个jar 文件。在使用SensorManagerSimulator类之前,必须在相应的Eclipse 工程中引用这个jar 文件。
现在运行本例,并通过服务端主界面右侧的【Roll 】滑动杆移动到指定的角度,例如,-74.0 和-142.0 ,这时设置的角度会显示在屏幕上,如图1 和图2 所示。
图1 翻转角度大于-120 度
图2 翻转角度小于-120 度
读者可以在如图1 和图2 所示的翻转状态下拨入电话,会发现翻转角度在-74.0 度时来电仍然会响铃,而翻转角度在-142.0 度时就不再响铃了。
发布于 2年前, 阅读(282) | 评论(0) | 投票(1) | 收藏(1) 阅读全文...
加速度传感器(Sensor.TYPE_ACCELEROMETER )
磁场传感器(Sensor.TYPE_MAGNETIC_FIELD )
光线传感器(Sensor.TYPE_LIGHT )
方向传感器(TYPE_ORIENTATION )
SENSOR_DELAY_FASTEST :以最快的速度获得传感器数据。
SENSOR_DELAY_GAME :适合于在游戏中获得传感器数据。
SENSOR_DELAY_UI :适合于在UI 控件中获得传感器数据。
SENSOR_DELAY_NORMAL :以一般的速度获得传感器的数据。
近程传感器(Sensor.TYPE_PROXIMITY)
线性加速度传感器(Sensor.TYPE_LINEAR_ACCELERATION)
旋转向量传感器(Sensor.TYPE_ROTATION_VECTOR)
磁场传感器(Sensor.TYPE_MAGNETIC_FIELD)
压力传感器(Sensor.TYPE_PRESSURE)
温度传感器(Sensor.TYPE_TEMPERATURE)
values[0]:延X轴旋转的角速度。
values[1]:延Y轴旋转的角速度。
values[2]:延Z轴旋转的角速度。
X轴的方向是沿着屏幕的水平方向从左向右。如果手机不是正方形的话,较短的边需要水平放置,较长的边需要垂直放置。
Y轴的方向是从屏幕的左下角开始沿着屏幕的垂直方向指向屏幕的顶端。
将手机平放在桌子上,Z轴的方向是从手机里指向天空。
转 电子罗盘的工作原理及校准
分类: 转贴移动开发文章
罗盘是一种重要的导航工具,能实时提供移动物体的航向和姿态。随着半导体工艺的进步和操作系统的发展,集成了越来越多的变得功能强大,很多手机上都实现了的功能。而基于电子罗盘的应用(如Android的Skymap)在各个平台上也流行起来。
  要实现电子罗盘功能,需要一个检测磁场的三轴磁 和一个三轴加 。随着微机械工艺的成熟,意法半导体推出将三轴磁力计和三轴加速计集成在一个封装里的二合一传感器模块LSM303DLH,方便用户在短时间内设计出成本低、性能高的电子罗盘。本文以LSM303DLH为例讨论该器件的工作原理、技术参数和电子罗盘的实现方法。
地磁场和航向角的背景知识
  如图1所示,地球的磁场象一个条形磁体一样由磁南极指向磁北极。在磁极点处磁场和当地的水平面垂直,在赤道磁场和当地的水平面平行,所以在北半球磁场 方向倾斜指向地面。用来衡量磁感应强度大小的单位是Tesla或者Gauss(1Tesla=10000Gauss)。随着地理位置的不同,通常地磁场的 强度是0.4-0.6 Gauss。需要注意的是,磁北极和地理上的北极并不重合,通常他们之间有11度左右的夹角。
图1 地磁场分布图
  地磁场是一个矢量,对于一个固定的地点来说,这个矢量可以被分解为两个与当地水平面平行的分量和一个与当地水平面垂直的分量。如果保持电子罗盘和当地的水平面平行,那么罗盘中磁力计的三个轴就和这三个分量对应起来,如图2所示。
图2 地磁场矢量分解示意图
  实际上对水平方向的两个分量来说,他们的矢量和总是指向磁北的。罗 盘中的航向角(Azimuth)就是当前方向和磁北的夹角。由于罗盘保持水平,只需要用磁力计水平方向两轴(通常为X轴和Y轴)的检测数据就可以用式1计 算出航向角。当罗盘水平旋转的时候,航向角在0?- 360?之间变化。
   2.ST集成磁力计和加速计的传感器模块LSM303DLH
磁力计工作原理
  在LSM303DLH中磁力计采用各向异性磁致 (Anisotropic Magneto-Resistance)材料来检测空间中磁感应强度的大小。这种具有晶体结构的合金材料对外界的磁场很敏感,磁场的强弱变化会导致AMR自身电阻值发生变化。
  在制造过程中,将一个 场加在AMR上使其在某一方向上磁化,建立起一个主磁域,与主磁域垂直的轴被称为该AMR的敏感轴,如图3所示。为了使测量结果以线性的方式变化,AMR材料上的金属 呈45&角倾斜排列,电流从这些导线上流过,如图4所示。由初始的强磁场在AMR材料上建立起来的主磁域和电流的方向有45&的夹角。
图3 AMR材料示意图
图4 45&角排列的导线
  当有外界磁场Ha时,AMR上主磁域方向就会发生变化而不再是初始的方向了,那么磁场方向和电流的夹角θ也会发生变化,如图5所示。对于AMR材料来说,θ角的变化会引起AMR自身阻值的变化,并且呈线性关系,如图6所示。
图5 磁场方向和电流方向的夹角
图6 θ-R特性曲线
  ST利用惠斯通 检测AMR阻值的变化,如图7所示。R1/R2/R3/R4是初始状态相同的AMR电阻,但是R1/R2和R3/R4具有相反的磁化特性。当检测到外界磁场的时候,R1/R2阻值增加&# ;R而R3/R4减少?R。这样在没有外界磁场的情况下,电桥的输出为零;而在有外界磁场时电桥的输出为一个微小的电压?V。
图7 惠斯通电桥
  当R1=R2=R3=R4=R,在外界磁场的作用下电阻变化为?R时,电桥输出?V正比于?R。这就是磁力计的工作原理。
置位/复位(Set/Reset)
  由于受到外界环境的影响,LSM303DLH中AMR上的主磁域方向不会永久保持不变。LSM303DLH内置有置位/复位电路,通过内部的金属 周期性的产生电流脉冲,恢复初始的主磁域,如图8所示。需要注意的是,置位脉冲和复位脉冲产生的效果是一样的,只是方向不同而已。
图8 LSM303DLH置位/复位电路
  置位/复位电路给LSM303DLH带来很多优点:
即使遇到外界强磁场的干扰,在干扰消失后LSM303DLH也能恢复正常工作而不需要用户再次进行校正。
即使长时间工作也能保持初始磁化方向实现精确测量,不会因为芯片温度变化或内部噪音增大而影响测量精度。
消除由于温漂引起的电桥偏差。
LSM303DLH的性能参数
  LSM303DLH集成三轴磁力计和三轴加速计,采用数字接口。磁力计的测量范围从1.3 Gauss到8.1 Gauss共分7档,用户可以自由选择。并且在20 Gauss以内的磁场环境下都能够保持一致的测量效果和相同的敏感度。它的分辨率可以达到8 mGauss并且内部采用12位ADC,以保证对磁场强度的精确测量。和采用霍尔效应原理的磁力计相比,LSM303DLH的功耗低,精度高,线性度好, 并且不需要温度补偿。
  LSM303DLH具有自动检测功能。当控制寄存器A被置位时,芯片内部的自测电路会产生一个约为地磁场大小的激励信号并输出。用户可以通过输出数据来判断芯片是否正常工作。
  作为高集成度的传感器 ,除了磁力计以外LSM303DLH还集成一颗高性能的加速计。加速计同样采用12位ADC,可以达到1mg的测量精度。加速计可运行于低功耗模式,并有睡眠/唤醒功能,可大大降低功耗。同时,加速计还集成了6轴方向检测,两路可编程中断接口。
ST电子罗盘方案介绍
  一个传统的电子罗盘系统至少需要一个三轴的磁力计以测量磁场数据,一个三轴加速计以测量罗盘倾角,通过信号条理和数据采集部分将三维空间中的重力分布 和磁场数据传送给处理器。处理器通过磁场数据计算出方位角,通过重力数据进行倾斜补偿。这样处理后输出的方位角不受电子罗盘空间姿态的影响,如图9所示。
图9 电子罗盘结构示意图
  LSM303DLH将上述的加速计、磁力计、A/D转化器及信号条理电路集成在一起,仍然通过I2C 和处理器通信。这样只用一颗芯片就实现了6轴的数据检测和输出,降低了客户的设计难度,减小了PCB板的占用面积,降低了器件成本。
  LSM303DLH的典型应用如图10所示。它需要的周边器件很少,连接也很简单,磁力计和加速计各自有一条I2C总线和处理器通信。如果客户的I/O接口电平为 ,Vdd_dig_M、 Vdd_IO_A和Vdd_I2C_Bus均可接1.8V供电,Vdd使用2.5V以上供电即可;如果客户接口电平为2.6V,除了Vdd_dig_M要 求1.8V以外,其他皆可以用2.6V。在上文中提到,LSM303DLH需要置位/复位电路以维持AMR的主磁域。C1和C2为置位/复位电路的外部匹 配电容,由于对置位脉冲和复位脉冲有一定的要求,建议用户不要随意修改C1和C2的大小。
图10 LSM303DLH典型应用电路图
  对于便携式设备而言,器件的功耗非常重要,直接影响 其待机的时间。LSM303DLH可以分别对磁力计和加速计的供电模式进行控制,使其进入睡眠或低功耗模式。并且用户可自行调整磁力计和加速计的数据更新 频率,以调整功耗水平。在磁力计数据更新频率为7.5Hz、加速计数据更新频率为50Hz时,消耗电流典型值为0.83mA。在待机模式时,消耗电流小于 3uA。
铁磁场干扰及校准
   主 要是通过感知地球磁场的存在来计算磁北极的方向。然而由于地球磁场在一般情况下只有微弱的0.5高斯,而一个普通的手机喇叭当相距2厘米时仍会有大约4高 斯的磁场,一个手机马达在相距2厘米时会有大约6高斯的磁场,这一特点使得针对电子设备表面地球磁场的测量很容易受到电子设备本身的干扰。
  磁场干扰是指由于具有磁性物质或者可以影响局部磁场强度的物质存在,使得 所 放置位置上的地球磁场发生了偏差。如图11所示,在磁传感器的XYZ 坐标系中,绿色的圆表示地球磁场矢量绕z轴圆周转动过程中在XY平面内的投影轨迹,再没有外界任何磁场干扰的情况下,此轨迹将会是一个标准的以 O(0,0)为中心的圆。当存在外界磁场干扰的情况时,测量得到的磁场强度矢量α将为该点地球磁场β与干扰磁场γ的矢量和。记作:
图11 磁传感器XY坐标以及磁力线投影轨迹
  一般可以认为,干扰磁场γ在该点可以视为一个恒定的矢量。有很多因素可以造成磁场的干扰,如摆放在电路板上的马达和喇叭,还有含有铁镍钴等金属的材料如屏蔽罩,螺丝,电阻, LCD背板以及外壳等等。同样根据安培定律有电流通过的导线也会产生磁场,如图12。
图12 电流对磁场产生的影响
  为了校准这些来自电路板的磁场干扰,主要的工作就是通过计算将γ求出。
平面校准方法
  针对XY轴的校准,将配备有磁传感器的设备在XY平面内自转,如图11,等价于将地球磁场矢量绕着过点O(γx,γy)垂直于XY平面的法线旋转, 而红色的圆为磁场矢量在旋转过程中在XY平面内投影的轨迹。这可以找到圆心的位置为((Xmax + Xmin)/2,
(Ymax + Ymin)/2).
同样将设备在XZ平面内旋转可以得到地球磁场在XZ平面上的轨迹圆,这可以求出三维空间中的磁场干扰矢量γ(γx, γy, γz).
立体8字校准方法
  一般情况下,当带有传感器的设备在空中各个方向旋转时,测量值组成的空间几何结构实际上是一个圆球,所有的采样点都落在这个球的表面上,如图13所示,这一点同两维平面内投影得到的圆类似。
图13 地球磁场空间旋转后在传感器空间坐标内得到球体
  这种情况下,可以通过足够的样本点求出圆心O(γx, γy, γz), 即固定磁场干扰矢量的大小及方向。公式如下:
  8字校准法要求用户使用需要校准的设备在空中做8字晃动,原则上尽量多的让设备法线方向指向空间的所有8个象限,如图14所示。
图14 设备的空中8字校准示意图
十面校准方法
  同样,通过以下10面校准方法,也可以达到校准的目的。
图15 10面交准法步骤
  如图16所示,经过10面校准方法之后,同样可以采样到以上所述球体表面的部分轨迹,从而推导出球心的位置,即固定磁场干扰矢量的大小及方向。
图16 10面校准后的空间轨迹
   5.倾斜补偿及航偏角计算
  经过校准后电子指南针在水平面上已经可以正常使用了。但是更多的时候手机并不是保持水平的,通常它和水平面都有一个夹角。这个夹角会影响航向角的精度,需要通过 进行倾斜补偿。
  对于一个物体在空中的姿态,导航系统里早已有定义,如图17所示,Android中也采用了这个定义。Pitch(Φ)定义为x轴和水平面的夹角,图 示方向为正方向;Roll(θ)定义为y轴和水平面的夹角,图示方向为正方向。由Pitch角引起的航向角的误差如图18所示。可以看出,在x轴方向10 度的倾斜角就可以引起航向角最大7-8度的误差。
图17 Pitch角和Roll角定义
图18 Pitch角引起的航向角误差
  手机在空中的倾斜姿态如图19所示,通过3轴加速度传感器检测出三个轴上重力加速度的分量,再通过式2可以计算出Pitch和Roll。
图19 手机在空中的倾斜姿态
  式3可以将磁力计测得的三轴数据(XM,YM ,ZM)通过Pitch和Roll转化为式1中计算航向角需要的Hy和Hx。之后再利用式1计算出航向角。
   6.Android平台指南针的实现
  在当前流行的android 手机中,很多都配备有指南针的功能。为了实现这一功能,只需要配备有ST提供的二合一传感模块LSM303DLH,ST 提供整套解决方案。Android中的软件实现可以由以下框图表示:
  其中包括:
  BSP Reference
  Linux Kernel Driver (LSM303DLH_ACC + LSM303DLH_MAG)
   HAL Library(Sensors_lsm303dlh + Liblsm303DLH) for sensors.default.so
  经过library 的计算,上层的应用可以很轻松的运用由Android定义由Library提供的航偏角信息进行应用程序的编写。
发布于 2年前, 阅读(169) | 评论(0) | 投票(0) | 收藏(0) 阅读全文...
转 Android操作系统11种传感器介绍
分类: 转贴移动开发文章
在Android2.3 gingerbread系统中,google提供了11种传感器供应用层使用。
#define SENSOR_TYPE_ACCELEROMETER
1 //加速度
#define SENSOR_TYPE_MAGNETIC_FIELD
#define SENSOR_TYPE_ORIENTATION
#define SENSOR_TYPE_GYROSCOPE
4 //陀螺仪
#define SENSOR_TYPE_LIGHT
5 //光线感应
#define SENSOR_TYPE_PRESSURE
#define SENSOR_TYPE_TEMPERATURE
#define SENSOR_TYPE_PROXIMITY
#define SENSOR_TYPE_GRAVITY
#define SENSOR_TYPE_LINEAR_ACCELERATION 10//线性加速度
#define SENSOR_TYPE_ROTATION_VECTOR
11//旋转矢量
我们依次看看这十一种传感器
1 加速度传感器
加速度传感器又叫G-sensor,返回x、y、z三轴的加速度数值。
该数值包含地心引力的影响,单位是m/s^2。
将手机平放在桌面上,x轴默认为0,y轴默认0,z轴默认9.81。
将手机朝下放在桌面上,z轴为-9.81。
将手机向左倾斜,x轴为正值。
将手机向右倾斜,x轴为负值。
将手机向上倾斜,y轴为负值。
将手机向下倾斜,y轴为正值。
加速度传感器可能是最为成熟的一种mems产品,市场上的加速度传感器种类很多。
手机中常用的加速度传感器有BOSCH(博世)的BMA系列,AMK的897X系列,ST的LIS3X系列等。
这些传感器一般提供±2G至±16G的加速度测量范围,采用I2C或SPI接口和MCU相连,数据精度小于16bit。
2 磁力传感器
磁力传感器简称为M-sensor,返回x、y、z三轴的环境磁场数据。
该数值的单位是微特斯拉(micro-Tesla),用uT表示。
单位也可以是高斯(Gauss),1Tesla=10000Gauss。
硬件上一般没有独立的磁力传感器,磁力数据由电子罗盘传感器提供(E-compass)。
电子罗盘传感器同时提供下文的方向传感器数据。
3 方向传感器
方向传感器简称为O-sensor,返回三轴的角度数据,方向数据的单位是角度。
为了得到精确的角度数据,E-compass需要获取G-sensor的数据,
经过计算生产O-sensor数据,否则只能获取水平方向的角度。
方向传感器提供三个数据,分别为azimuth、pitch和roll。
azimuth:方位,返回水平时磁北极和Y轴的夹角,范围为0°至360°。
0°=北,90°=东,180°=南,270°=西。
pitch:x轴和水平面的夹角,范围为-180°至180°。
当z轴向y轴转动时,角度为正值。
roll:y轴和水平面的夹角,由于历史原因,范围为-90°至90°。
当x轴向z轴移动时,角度为正值。
电子罗盘在获取正确的数据前需要进行校准,通常可用8字校准法。
8字校准法要求用户使用需要校准的设备在空中做8字晃动,
原则上尽量多的让设备法线方向指向空间的所有8个象限。
手机中使用的电子罗盘芯片有AKM公司的897X系列,ST公司的LSM系列以及雅马哈公司等等。
由于需要读取G-sensor数据并计算出M-sensor和O-sensor数据,
因此厂商一般会提供一个后台daemon来完成工作,电子罗盘算法一般是公司私有产权。
4 陀螺仪传感器
陀螺仪传感器叫做Gyro-sensor,返回x、y、z三轴的角速度数据。
角速度的单位是radians/second。
根据Nexus S手机实测:
水平逆时针旋转,Z轴为正。
水平逆时针旋转,z轴为负。
向左旋转,y轴为负。
向右旋转,y轴为正。
向上旋转,x轴为负。
向下旋转,x轴为正。
ST的L3G系列的陀螺仪传感器比较流行,iphone4和google的nexus s中使用该种传感器。
5 光线感应传感器
光线感应传感器检测实时的光线强度,光强单位是lux,其物理意义是照射到单位面积上的光通量。
光线感应传感器主要用于Android系统的LCD自动亮度功能。
可以根据采样到的光强数值实时调整LCD的亮度。
6 压力传感器
压力传感器返回当前的压强,单位是百帕斯卡hectopascal(hPa)。
7 温度传感器
温度传感器返回当前的温度。
8 接近传感器
接近传感器检测物体与手机的距离,单位是厘米。
一些接近传感器只能返回远和近两个状态,
因此,接近传感器将最大距离返回远状态,小于最大距离返回近状态。
接近传感器可用于接听电话时自动关闭LCD屏幕以节省电量。
一些芯片集成了接近传感器和光线传感器两者功能。
下面三个传感器是Android2新提出的传感器类型,目前还不太清楚有哪些应用程序使用。
9 重力传感器
重力传感器简称GV-sensor,输出重力数据。
在地球上,重力数值为9.8,单位是m/s^2。
坐标系统与加速度传感器相同。
当设备复位时,重力传感器的输出与加速度传感器相同。
10 线性加速度传感器
线性加速度传感器简称LA-sensor。
线性加速度传感器是加速度传感器减去重力影响获取的数据。
单位是m/s^2,坐标系统与加速度传感器相同。
加速度传感器、重力传感器和线性加速度传感器的计算公式如下:
加速度 = 重力 + 线性加速度
11 旋转矢量传感器
旋转矢量传感器简称RV-sensor。
旋转矢量代表设备的方向,是一个将坐标轴和角度混合计算得到的数据。
RV-sensor输出三个数据:
x*sin(theta/2)
y*sin(theta/2)
z*sin(theta/2)
sin(theta/2)是RV的数量级。
RV的方向与轴旋转的方向相同。
RV的三个数值,与cos(theta/2)组成一个四元组。
RV的数据没有单位,使用的坐标系与加速度相同。
sensors_event_t.data[0] = x*sin(theta/2)
sensors_event_t.data[1] = y*sin(theta/2)
sensors_event_t.data[2] = z*sin(theta/2)
sensors_event_t.data[3] =
cos(theta/2)
GV、LA和RV的数值没有物理传感器可以直接给出,
需要G-sensor、O-sensor和Gyro-sensor经过算法计算后得出。
算法一般是传感器公司的私有产权。
参考文献:
android source code hardware\libhardware\include\hardwaresensor.h
http://www.dzsc.com/data/html//87454.html
发布于 2年前, 阅读(1044) | 评论(4) | 投票(2) | 收藏(18) 阅读全文...
荐 转 Android内存泄漏简介
分类: 转贴移动开发文章
不少人认为 JAVA程序,因为有垃圾回收机制,应该没有内存泄露。 其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露。如果我们的java运行很久,而这种内存泄露不断的发生,最后就没内存可用了。当然java的内存泄漏和C/C++是不一样的。如果java程序完全结束后,它所有的对象就都不可达了,系统就可以对他们进行垃圾回收,它的内存泄露仅仅限于它本身,而不会影响整个系统的。C/C++的内存泄露就比较糟糕了,它的内存泄露是系统级,即使该C/C++程序退出,它的泄露的内存也无法被系统回收,永远不可用了,除非重启机器。
Android 的一个应用程序的内存泄露对别的应用程序影响不大。 为了能够使得Android应用程序安全且快速的运行,Android的每个应用程序都会使用一个专有的Dalvik虚拟机实例来运行,它是由Zygote 服务进程孵化出来的,也就是说每个应用程序都是在属于自己的进程中运行的。 Android 为不同类型的进程分配了不同的内存使用上限,如果程序在运行过程中出现了内存泄漏的而造成应用进程使用的内存超过了这个上限,则会被系统视为内存泄漏,从而被 kill 掉,这使得仅仅自己的进程被 kill 掉,而不会影响其他进程(如果是 system_process 等系统进程出问题的话,则会引起系统重启)。
一、引用没释放造成的内存泄露
1.1、注册没取消造成的内存泄露
这种Android的内存泄露比纯java的内存泄露还要严重,因为其他一些Android程序可能引用我们的Anroid程序的对象(比如注册机制)。即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄露的内存依然不能被垃圾回收。
假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen中定义一个PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当需要显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。
但是如果在释放LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process进程挂掉。
虽然有些系统程序,它本身好像是可以自动取消注册的(当然不及时),但是我们还是应该在我们的程序中明确的取消注册,程序结束时应该把所有的注册都取消掉。
1.2、集合容器对象没清理造成的内存泄露
我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
1.3、Context泄漏
所谓的 Context泄漏,其实更多的是指 Activity泄露,这是一个很隐晦的OutOfMemoryError的情况。
先看一个Android官网提供的例子:
private static Drawable sB
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
label.setBackgroundDrawable(sBackground);
setContentView(label);
这段代码效率很快,但同时又是极其错误的; 在第一次屏幕方向切换时它泄露了一开始创建的Activity。当一个Drawable附加到一个 View上时, View会将其作为一个 callback设定到Drawable上。上述的代码片段,意味着这个静态的Drawable拥有一个TextView的引用, 而TextView又拥有Activity(Context类型)的引用,换句话说,Drawable拥有了更多的对象引用。即使Activity被销毁,内存仍然不会被释放。
另外,对Context的引用超过它本身的生命周期,也会导致该Context无法回收,从而导致内存泄漏。所以尽量使用Application这种Context类型。 这种Context拥有和应用程序一样长的生命周期,并且不依赖Activity的生命周期。如果你打算保存一个长时间的对象, 并且其需要一个 Context,记得使用Application对象。你可以通过调用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。
最近遇到一种情况引起了Context泄漏,就是在Activity销毁时,里面有其他线程没有停。
总结一下避免Context泄漏应该注意的问题:
1.4、static关键字的滥用
当类的成员变量声明成static后,它是属于类的而不是属于对象的,如果我们将很大的资源对象(Bitmap,context等)声明成static,那么这些资源不会随着对象的回收而回收, 会一直存在,所以在使用static关键字定义成员变量的时候要慎重。
1.5、WebView对象没有销毁
当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其占用的内存长期也不能被回收,从而造成内存泄露
1.6、GridView的滥用
GridView和ListView的实现方式不太一样。GridView的View不是即时创建的,而是全部保存在内存中的。比如一个GridView有100项,虽然我们只能看到10项,但是其实整个100项都是在内存中的。
二、资源对象没关闭造成的内存泄露
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。 因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。
程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。
三、一些不良代码成内存压力
有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存,对内存的回收和分配造成很大影响的,容易迫使虚拟机不得不给该应用进程分配更多的内存,造成不必要的内存开支。
3.1、Bitmap没调用recycle()
Bitmap对象在不使用时,我们应该先调用recycle(),然后才它设置为null.虽然Bitmap在被回收时可以通过BitmapFinalizer来回收内存。但是调用recycle()是一个良好的习惯在Android4.0之前,Bitmap的内存是分配在Native堆中,调用recycle()可以立即释放Native内存。从Android4.0开始,Bitmap的内存就是分配在dalvik堆中,即JAVA堆中的,调用recycle()并不能立即释放Native内存。但是调用recycle()也是一个良好的习惯。
可以通过dumpsys meminfo命令查看一个进程的内存情况。
adb shell "dumpsys meminfo com.lenovo.robin"
运行结果。
Applications Memory Usage (kB):
** MEMINFO in pid 7985 [com.lenovo.robin] **
allocated:
(shared dirty):
(priv dirty):
ViewRoots:
AppContexts:
Activities:
AssetManagers:
Local Binders:
Proxy Binders:
Death Recipients:
OpenSSL Sockets:
MEMORY_USED:
PAGECACHE_OVERFLOW:
MALLOC_SIZE:
关于内存统计的更多内容请参考
3.2、构造Adapter时,没有使用缓存的 convertView
以构造ListView的BaseAdapter为例,在BaseAdapter中提共了方法:
public View getView(int position, View convertView, ViewGroup parent)
来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。
由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费时间,也造成内存垃圾,给垃圾回收增加压力,如果垃圾回收来不及的话,虚拟机将不得不给该应用进程分配更多的内存,造成不必要的内存开支。ListView回收list item的view对象的过程可以查看:
android.widget.AbsListView.java --& void addScrapView(View scrap) 方法。
示例代码:
public View getView(int position, View convertView, ViewGroup parent) {
View view = new Xxx(...);
修正示例代码:
public View getView(int position, View convertView, ViewGroup parent){
View view =
if (convertView != null) {
view = convertV
populate(view, getItem(position));
view = new Xxx(...);
3.3、ThreadLocal使用不当
如果我们粗暴的把ThreadLocal设置null,而不调用remove()方法或set(null),那么就可能造成ThreadLocal绑定的对象长期也不能被回收,因而产出内存泄露。
关于此的更多内容请参考
四、JNI代码的内存泄露
关于此的详细内容请参考
发布于 2年前, 阅读(1400) | 评论(4) | 投票(0) | 收藏(118) 阅读全文...
尽量使用Application这种Context类型。
注意对Context的引用不要超过它本身的生命

我要回帖

更多关于 矩阵顺时针旋转90度 的文章

 

随机推荐