Author:Harish_hu@qq.com

由于现在电脑上只有4.0的代码,考虑到代码差别也不大,所以下部分,就基于4.0来分析。

 3:SensorManager

上一部分说过,开机后,system server启动时,就会初始化sensor service,也就是说,开机后她一直都在后台运行着,客户端部分,直接connect就行了。至于怎么connect,这一切都被封装到SensorManager里了。

3.1 SensorManager的创建

获取SensorManager的对象实例代码:

mSensorManager =(SensorManager)getSystemService(SENSOR_SERVICE);

调用Activity的成员函数来获取SensorManager实例,我们从Activity派生关系可以追溯到,这个函数的最终在ContextImpl实现:

//ContextImpl.java
@Override
public ObjectgetSystemService(String name) {
ServiceFetcherfetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher ==null ? null : fetcher.getService(this);
}

这个函数从SYSTEM_SERVICE_MAP中获取了name对应的特定对象实例,所以从SYSTEM_SERVICE_MAP的初始化,就可以看到SensorManager对象的创建:

//ContextImpl.java
static{ ...
registerService(SENSOR_SERVICE, newServiceFetcher() {
public ObjectcreateService(ContextImpl ctx) {
return newSensorManager(ctx.mMainThread.getHandler().getLooper());
}}); ...
}

3.2 初始化并连接sensor service

初始化过程肯定是在构造函数中进行,那如何连接sensor service呢?上一部分说过,sensor service是基于c++代码编写的native binder,客户端要与其连接并交互,当然也是使用c++更方便(我只是说比较方便,当然你如果硬要用java与其建立连接并交互数据,也是可以的).

如果一种做法可以让你更方便,我想大多数人的选择都是一样的,就是使用C++代码访问服务,然后java代码通过jni调用c++代码,这也是android系统的通用做法;接下去,我们看下SensorManager的jni函数映射:

在android jni中c++类文件的命名规则一般都是java类的package路径+类名,还有一点需要注意的是,这里jni映射函数名都是一样的,这只是这个类的设计者这么命名而已,实际上c++类中的对应函数命名是没有限制的,关于jni的详细描述,大家可查看相关资料,这里就不再赘述.

在了解了jni函数映射后,后续在java代码中如果调用了native函数,我们将直接跳转到c++代码.

SensorManager被实例化,地球人都知道构造函数先走,所以接下去看SensorManager构造函数:

 public SensorManager(Looper mainLooper) {
mMainLooper = mainLooper;
synchronized(sListeners) {
if (!sSensorModuleInitialized) {
sSensorModuleInitialized =true;
nativeClassInit();
sWindowManager =IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
if (sWindowManager != null) {
// if it's null we'rerunning in the system process
// which won't get therotated values
try {
sRotation =sWindowManager.watchRotation(
newIRotationWatcher.Stub() {
public voidonRotationChanged(int rotation) {
SensorManager.this.onRotationChanged(rotation);
}
}
);
} catch (RemoteException e){
}
} // initialize the sensor list
sensors_module_init();
final ArrayList<Sensor>fullList = sFullSensorsList;
int i = 0;
do {
Sensor sensor = newSensor();
i =sensors_module_get_next_sensor(sensor, i); if (i>=0) {
//Log.d(TAG,"found sensor: " + sensor.getName() +
// ", handle=" + sensor.getHandle());
sensor.setLegacyType(getLegacySensorType(sensor.getType()));
fullList.add(sensor);
sHandleToSensor.append(sensor.getHandle(), sensor);
}
} while (i>0); sPool = new SensorEventPool(sFullSensorsList.size()*2 );
sSensorThread = newSensorThread();
}
}
}

先调用nativeClassInit来初始化JNI相关java类信息,对应C++代码:

static void
nativeClassInit(JNIEnv *_env, jclass _this)
{
jclass sensorClass =_env->FindClass("android/hardware/Sensor");
SensorOffsets& sensorOffsets =gSensorOffsets;
sensorOffsets.name = _env->GetFieldID(sensorClass,"mName", "Ljava/lang/String;");
sensorOffsets.vendor = _env->GetFieldID(sensorClass,"mVendor", "Ljava/lang/String;");
sensorOffsets.version = _env->GetFieldID(sensorClass,"mVersion", "I");
sensorOffsets.handle = _env->GetFieldID(sensorClass, "mHandle", "I");
sensorOffsets.type = _env->GetFieldID(sensorClass,"mType", "I");
sensorOffsets.range = _env->GetFieldID(sensorClass,"mMaxRange", "F");
sensorOffsets.resolution = _env->GetFieldID(sensorClass,"mResolution","F");
sensorOffsets.power = _env->GetFieldID(sensorClass,"mPower", "F");
sensorOffsets.minDelay = _env->GetFieldID(sensorClass,"mMinDelay", "I");
}

从代码上看出,这个函数主要是保存java类Sensor的各个filed的id值,方便后续在c++代码中利用Jni环境向jave层传递数据,这个在后续poll sensor值的时候会用到。接着调用jni函数sensors_module_init,c++代码如下:

static jint
sensors_module_init(JNIEnv*env, jclass clazz)
{
SensorManager::getInstance();
return 0;
}

函数很简单,就调用SensorManager::getInstance实例化SensorManager对象实例。注意这里是jnic++层的实现,SensorManager对象是C++层的对象实例,不要跟上面java层的搞浑了。getInstance,可以明显看出来,这是一个单例对象,继续看c++ SensorManager的构造函数:

SensorManager::SensorManager()
: mSensorList(0)
{
// okay we're not locked here, but it's notneeded during construction
assertStateLocked();
}

构造函数就调了assertStateLocked,继续看这个函数:

status_tSensorManager::assertStateLocked() const {
if (mSensorServer == NULL) {
// try for one second
const String16name("sensorservice");
for (int i=0 ; i<4 ; i++) {
status_t err = getService(name,&mSensorServer);
if (err == NAME_NOT_FOUND) {
usleep(250000);
continue;
}
if (err != NO_ERROR) {
return err;
}
break;
} class DeathObserver : publicIBinder::DeathRecipient {
SensorManager& mSensorManger;
virtual void binderDied(constwp<IBinder>& who) {
LOGW("sensorservice died[%p]", who.unsafe_get());
mSensorManger.sensorManagerDied();
}
public:
DeathObserver(SensorManager&mgr) : mSensorManger(mgr) { }
}; mDeathObserver = newDeathObserver(*const_cast<SensorManager *>(this));
mSensorServer->asBinder()->linkToDeath(mDeathObserver); mSensors =mSensorServer->getSensorList();
size_t count = mSensors.size();
mSensorList = (Sensorconst**)malloc(count * sizeof(Sensor*));
for (size_t i=0 ; i<count ; i++) {
mSensorList[i] = mSensors.array() +i;
}
} return NO_ERROR;
}

这个函数通过getService拿到sensorservice的proxy binder,这样就建立了与sensorservice的数据连接,然后调用getsensorlist从sensorservice获取sensor list并保存。

ok,到这里,java层的jni函数sensors_module_init()就走完了,我们已经与sensor service建立连接,并已经取得了sensor list,但是这些数据目前是存于c++层的,我们要通过jni将数据拿到java层,所以在java层SensorManager构造函数调用sensors_module_init()后,调用sensors_module_get_next_sensor获取sensor数据并保存。

下面是jni函数sensors_module_get_next_sensor的c++实现:

//android_hardware_SensorManager.cpp
static jint
sensors_module_get_next_sensor(JNIEnv*env, jobject clazz, jobject sensor, jint next)
{
SensorManager&mgr(SensorManager::getInstance()); Sensor const* const* sensorList;
size_t count =mgr.getSensorList(&sensorList);
if (size_t(next) >= count)
return -1; Sensor const* const list =sensorList[next];
const SensorOffsets&sensorOffsets(gSensorOffsets);
jstring name =env->NewStringUTF(list->getName().string());
jstring vendor =env->NewStringUTF(list->getVendor().string());
env->SetObjectField(sensor,sensorOffsets.name, name);
env->SetObjectField(sensor,sensorOffsets.vendor, vendor);
env->SetIntField(sensor,sensorOffsets.version, 1);
env->SetIntField(sensor,sensorOffsets.handle, list->getHandle());
env->SetIntField(sensor,sensorOffsets.type, list->getType());
env->SetFloatField(sensor,sensorOffsets.range, list->getMaxValue());
env->SetFloatField(sensor,sensorOffsets.resolution, list->getResolution());
env->SetFloatField(sensor,sensorOffsets.power, list->getPowerUsage());
env->SetIntField(sensor,sensorOffsets.minDelay, list->getMinDelay()); next++;
return size_t(next) < count ? next : 0;
}

在这个函数将对应的c++层保存的sensor数据传给jobjectsensor。

java层SensorManager构造函数最后创建SensorEventPool和sSensorThread, 这两个对象干嘛用的?看名字就知道啦,一个是事件池,sensor 事件很频繁,如果对每一个事件都创建一个新对象,开销太大,弄一个事件池肯定是最好的选择;另外一个是sensor 线程,负责读取sensor 数据.

3.3 sensor数据读取

继续来看下应用层获取sensor数据的代码:

public classSensorActivity extends Activity, implements SensorEventListener {
private final SensorManagermSensorManager;
private final Sensor mAccelerometer;
public SensorActivity() {
//获取对应服务
mSensorManager =(SensorManager)getSystemService(SENSOR_SERVICE);
//获取指定sensor对象
mAccelerometer =mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
protected void onResume() {
super.onResume();
//注册listener用于数据回调
mSensorManager.registerListener(this,mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(this);
}
public void onAccuracyChanged(Sensorsensor, int accuracy) {
}
public void onSensorChanged(SensorEventevent) {
}
}

现在看这代码就很清楚了

1:(SensorManager)getSystemService(SENSOR_SERVICE)获取SensorManager对象,做了我们上面所介绍的初始化工作

2:mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),获取指定sensor对象,根据初始化获取的Sensor List。

3:mSensorManager.registerListener(this,mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);注册listener获取sensor数据

还记得上一部分说的sensor client与sensor service建立active connection来传递数据吗?service端创建connection是由client端也就是由应用端发起的; 上面1,2都是初始化工作,那真正发起的代码,肯定就是registerlistener了,下面根据代码详细分析:

    public booleanregisterListener(SensorEventListener listener, Sensor sensor, int rate) {
return registerListener(listener,sensor, rate, null);
}

直接调用重载函数

public booleanregisterListener(SensorEventListener listener, Sensor sensor, int rate,
Handler handler) {
if (listener == null || sensor == null){
return false;
}
boolean result = true;
int delay = -1;
switch (rate) {
case SENSOR_DELAY_FASTEST:
delay = 0;
break;
case SENSOR_DELAY_GAME:
delay = 20000;
break;
case SENSOR_DELAY_UI:
delay = 66667;
break;
case SENSOR_DELAY_NORMAL:
delay = 200000;
break;
default:
delay = rate;
break;
} synchronized (sListeners) {
// look for this listener in our list
ListenerDelegate l = null;
for (ListenerDelegate i :sListeners) {
if (i.getListener() ==listener) {
l = i;
break;
}
} // if we don't find it, add it tothe list
if (l == null) {
l = newListenerDelegate(listener, sensor, handler);
sListeners.add(l);
// if the list is not empty,start our main thread
if (!sListeners.isEmpty()) {
if(sSensorThread.startLocked()) {
if(!enableSensorLocked(sensor, delay)) {
// oops. there was an error
sListeners.remove(l);
result = false;
}
} else {
// there was an error,remove the listener
sListeners.remove(l);
result = false;
}
} else {
// weird, we couldn't addthe listener
result = false;
}
} else {
l.addSensor(sensor);
if (!enableSensorLocked(sensor,delay)) {
// oops. there was an error
l.removeSensor(sensor);
result = false;
}
}
} return result;

这个函数使用出现了两个新的变量,分别是sListeners和sSensorThread,对应的类型分别是ListenerDelegate和SensorThread,ListenerDelegate主要是对SensorEventListener做封装,从而使一个listener可以对应多个sensor,SensorThread则负责从sensor service读取sensor数据;该函数先判断lstener对应的ListenerDelegate是否已经创建,如果未创建,新建并将其添加入sListeners,然后查看Sensor Thread是否已经启动,如果没有启动,调用sSensorThread.startLocked()启动线程,接下去调用enableSensorLocked到service端enable对应的sensor.

先来看startlocked:

 boolean startLocked() {
try {
if (mThread == null) {
mSensorsReady = false;
SensorThreadRunnablerunnable = new SensorThreadRunnable();
Thread thread = newThread(runnable, SensorThread.class.getName());
thread.start();
synchronized (runnable) {
while (mSensorsReady ==false) {
runnable.wait();
}
}
mThread = thread;
}
} catch (InterruptedException e) {
}
return mThread == null ? false :true;
}

如果线程未创建,创建SensorThreadRunnable,然后初始化线程并启动,线程启动后SensorThreadRunnable.run会被执行:

private class SensorThreadRunnable implementsRunnable {
SensorThreadRunnable() {
} private boolean open() {
// NOTE: this cannotsynchronize on sListeners, since
// it's held in the main threadat least until we
// return from here.
sQueue = sensors_create_queue();
return true;
} public void run() {
//Log.d(TAG, "enteringmain sensor thread");
final float[] values = newfloat[3];
final int[] status = newint[1];
final long timestamp[] = newlong[1];
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY); if (!open()) {
return;
} synchronized (this) {
// we've open the driver,we're ready to open the sensors
mSensorsReady = true;
this.notify();
} while (true) {
// wait for an event
final int sensor =sensors_data_poll(sQueue, values, status, timestamp); int accuracy = status[0];
synchronized (sListeners) {
if (sensor == -1 ||sListeners.isEmpty()) {
// we lost theconnection to the event stream. this happens
// when the lastlistener is removed or if there is an error
if (sensor == -1&& !sListeners.isEmpty()) {
// log awarning in case of abnormal termination
Log.e(TAG,"_sensors_data_poll() failed, we bail out: sensors=" + sensor);
}
// we have no morelisteners or polling failed, terminate the thread
sensors_destroy_queue(sQueue);
sQueue = 0;
mThread = null;
break;
}
final SensorsensorObject = sHandleToSensor.get(sensor);
if (sensorObject !=null) {
// report thesensor event to all listeners that
// care about it.
final int size =sListeners.size();
for (int i=0 ;i<size ; i++) {
ListenerDelegate listener = sListeners.get(i);
if(listener.hasSensor(sensorObject)) {
// this isasynchronous (okay to call
// withsListeners lock held).
listener.onSensorChangedLocked(sensorObject,
values, timestamp, accuracy);
}
}
}
}
}
//Log.d(TAG, "exiting mainsensor thread");
}
}
}

run执行时,先调用open,open函数很简单,就调用sensors_create_queue()来创建数据队列,显然这个队列是用于sensor数据传输的,sensors_create_queue()是jni函数,接下去看其对应c++部分代码:

staticjint
sensors_create_queue(JNIEnv*env, jclass clazz)
{
SensorManager&mgr(SensorManager::getInstance());
sp<SensorEventQueue>queue(mgr.createEventQueue());
queue->incStrong(clazz);
returnreinterpret_cast<int>(queue.get());
}

调用SensorManager.createEventQueue来创建队列:

sp<SensorEventQueue>SensorManager::createEventQueue()
{
sp<SensorEventQueue> queue;
Mutex::Autolock _l(mLock);
while(assertStateLocked() == NO_ERROR) {
sp<ISensorEventConnection>connection =
mSensorServer->createSensorEventConnection();
if (connection == NULL) {
// SensorService just died.
LOGE("createEventQueue:connection is NULL. SensorService died.");
continue;
}
queue = newSensorEventQueue(connection);
break;
}
return queue;
}

调用mSensorServer->createSensorEventConnection()与server端建立连接,接着将获取的connection对象创建SensorEventQueue对象并返回。

sensors_create_queue函数接着调用queue.get()获取队列的指针,并返回给java层

回过头来继续看java调用sensors_create_queue的open函数:

 private boolean open() {
// NOTE: this cannot synchronize onsListeners, since
// it's held in the main thread at least untilwe
// return from here.
//将返回的SensorEventQueue指针保存到sQueue里
sQueue = sensors_create_queue();
return true;
}

将c++创建的SensorEventQueue对象地址保存到java层的一个变量里,这在android里面是很常见也很好用的方法

在open结束后,SensorThreadRunnable.run接下去调用sensors_data_poll来抓去sensor数据

staticnative int sensors_data_poll(int queue, float[] values, int[] status, long[]timestamp);

这个函数的第一个参数就是之前保存的c++层SensorEventQueue对象指针,看对应c++实现:

staticjint
sensors_data_poll(JNIEnv*env, jclass clazz, jint nativeQueue,
jfloatArray values, jintArray status,jlongArray timestamp)
{
//强制类型转换
sp<SensorEventQueue> queue(reinterpret_cast<SensorEventQueue*>(nativeQueue));
if (queue == 0) return -1;
status_t res;
ASensorEventevent;
//从队列中读取数据
res = queue->read(&event, 1);
if (res == -EAGAIN) {
res = queue->waitForEvent();
if (res != NO_ERROR)
return -1;
res = queue->read(&event, 1);
}
if (res < 0)
return -1;
jint accuracy = event.vector.status;
env->SetFloatArrayRegion(values, 0, 3,event.vector.v);
env->SetIntArrayRegion(status, 0, 1,&accuracy);
env->SetLongArrayRegion(timestamp, 0, 1,&event.timestamp);
return event.sensor;
}

先将java层传过来的对象地址强制类型转换成SensorEventQueue,然后调用 queue->read(&event, 1)读取sensor数据

ssize_tSensorEventQueue::read(ASensorEvent* events, size_t numEvents)
{
ssize_t size =mSensorChannel->read(events, numEvents*sizeof(events[0]));
LOGE_IF(size<0 && size!=-EAGAIN,
"SensorChannel::read error(%s)", strerror(-size));
if (size >= 0) {
if (size % sizeof(events[0])) {
// partial read!!! should neverhappen.
LOGE("SensorEventQueue partialread (event-size=%u, read=%d)",
sizeof(events[0]),int(size));
return -EINVAL;
}
// returns number of events read
size /= sizeof(events[0]);
}
returnsize;
}

关于数据的具体传输,上一部分已经详细介绍,这里就不再描述

Jni部分sensors_data_poll在获取到sensor数据并返回到java层,SensorThreadRunnable.run在得到sensor数据后,通过下面代码将数据通过listener回调

finalint size = sListeners.size();
for(int i=0 ; i<size ; i++) {
ListenerDelegate listener = sListeners.get(i);
if (listener.hasSensor(sensorObject)) {
// this is asynchronous (okay to call
// with sListeners lock held).
istener.onSensorChangedLocked(sensorObject,values,timestamp, accuracy);
}
}

就这样,通过registerlistener注册的listener就可以获取到想要的sensor数据,这样就可以了吗?还不行,上面只是说数据流是这么走的,SensorEventQueue::read现在读不到数据的,因为在sensor service那边,sensor还是inactive的,所以registerListener 在sSensorThread.startLocked()成功后,再调用enableSensorLocked来active指定sensor:

      private booleanenableSensorLocked(Sensor sensor, int delay) {
boolean result = false;
for (ListenerDelegate i : sListeners) {
if (i.hasSensor(sensor)) {
String name = sensor.getName();
int handle =sensor.getHandle();
result = sensors_enable_sensor(sQueue,name, handle, delay);
break;
}
}
return result;
}

sensors_enable_sensor,又一个jni函数,直接看对应c++函数:

staticjboolean
sensors_enable_sensor(JNIEnv*env, jclass clazz,
jint nativeQueue, jstring name, jintsensor, jint delay)
{
sp<SensorEventQueue>queue(reinterpret_cast<SensorEventQueue *>(nativeQueue));
if (queue == 0) return JNI_FALSE;
status_t res;
if(delay >= 0) {
res = queue->enableSensor(sensor,delay);
} else {
res = queue->disableSensor(sensor);
}
return res == NO_ERROR ? true : false;
}

继续看queue->enableSensor

  status_tSensorEventQueue::enableSensor(int32_t handle, int32_t us) const {
status_t err =mSensorEventConnection->enableDisable(handle, true);
if (err == NO_ERROR) {
mSensorEventConnection->setEventRate(handle, us2ns(us));
}
return err;
}

调用mSensorEventConnection->enableDisable(handle,true)将对应的sensor激活。

Ok,激活后,SensorThreadRunnable.run中sensors_data_poll就可以拿到数据,并回调给注册的listener.

本文乃原创,转载请注明出处,谢谢。

[置顶] Android Sensor系统剖析(4.0)(下)的更多相关文章

  1. [置顶] Android系统五大布局详解Layout

    我们知道Android系统应用程序一般是由多个Activity组成,而这些Activity以视图的形式展现在我们面前,视图都是由一个一个的组件构成的.组件就是我们常见的Button.TextEdit等 ...

  2. [置顶] Android开发笔记(成长轨迹)

    分类: 开发学习笔记2013-06-21 09:44 26043人阅读 评论(5) 收藏 Android开发笔记 1.控制台输出:called unimplemented OpenGL ES API ...

  3. Android sensor 系统框架 (一)

    这几天深入学习了Android sensor框架,以此博客记录和分享分析过程,其中难免会有错误的地方,欢迎指出! 这里主要分析KERNEL->HAL->JNI这3层的流程.主要从以下几方面 ...

  4. [置顶] Android源码分析-点击事件派发机制

    转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/17339857 概述 一直想写篇关于Android事件派发机制的文章,却一直没 ...

  5. [置顶] Android Provision (Setup Wizard)

    Android中很多框架性的设计都已经存在了,但在市场上的发布版本里却因为没有很好的理解Android的设计意图而进行自己的定制,或者自己又做一 个冗余的实现.Android中的Provision其实 ...

  6. [置顶] Android 高级开发 源码 UI 缓存 网络

    1.Android 源码剖析 性能优化  开源代码 2.Android UI效果源码 3.http://mzh3344258.blog.51cto.com/1823534/d-3 4.微信公众平台开发 ...

  7. [置顶] Android系统移植与调试之------->build.prop文件详细赏析

    小知识:什么是build.prop?   /system/build.prop 是一个属性文件,在Android系统中.prop文件很重要,记录了系统的设置和改变,类似於/etc中的文件.这个文件是如 ...

  8. [置顶] Android系统访问控制之Smack安全策略设计与实现

    1. 制定smack规则 “Zygote”进程由init进程创建,它负责创建系统服务进程“systemserver”.“radio”进程和APP进程.其中“radio”进程的uid是1001,它能够实 ...

  9. [置顶] android系统如何在静音模式下关闭camera拍照声音(2)

    之前写过一篇“android系统如何在静音模式下关闭camera拍照声音”的博客,今天来写他的续篇,继续探讨这个问题. 公司新需求,要求在camera应用中添加一个开关,可以进行拍照声音的关闭和开启. ...

随机推荐

  1. 序列化悍将Protobuf-Net

    序列化悍将Protobuf-Net,入门动手实录 最近在研究web api 2,看了一篇文章,讲解如何提升性能的, 在序列化速度的跑分中,Protobuf一骑绝尘,序列化速度快,性能强,体积小,所以打 ...

  2. Web Service学习笔记:动态调用WebService

    原文:Web Service学习笔记:动态调用WebService 多数时候我们通过 "添加 Web 引用..." 创建客户端代理类的方式调用WebService,但在某些情况下我 ...

  3. [DevEpxress]GridControl 显示Gif动画

    原文:[DevEpxress]GridControl 显示Gif动画 如果没有对进行设置,那么GridControl列中gif在编辑状态下,才能显示动画效果,如果要设置列自动显示动画效果,可以进行如下 ...

  4. Cocos2d-x 2.3.3版本 FlappyBird

    Cocos2d-x 2.3.3版本 FlappyBird   本篇博客基于Cocos2d-x 2.3.3, 介绍怎样开发一款之前非常火的一款游戏FlappyBird.本篇博客内容大纲例如以下:   1 ...

  5. SQL data reader reading data performance test

    /*Author: Jiangong SUN*/ As I've manipulated a lot of data using SQL data reader in recent project. ...

  6. 【iOS】Xib的使用与File&#39;Owner总结

    一.XIB的适用范围 xib(也叫Nib)与storyboard一样是用来描写叙述界面的. storyboard描写叙述的是比較大型的,大范围.适合描写叙述界面跳转等. 二.XIB的使用 Xib是小范 ...

  7. Linq to Sql : 动态构造Expression进行动态查询

    原文:Linq to Sql : 动态构造Expression进行动态查询 前一篇在介绍动态查询时,提到一个问题:如何根据用户的输入条件,动态构造这个过滤条件表达式呢?Expression<Fu ...

  8. 快速构建Windows 8风格应用4-FlipView数据控件

    原文:快速构建Windows 8风格应用4-FlipView数据控件 本篇博文主要介绍为什么使用FlipView控件.什么是FlipView控件.如何使用FlipView控件和FlipView控件最佳 ...

  9. jQuery EasyUI API - Grid - DataGrid [原创汉化官方API]

    最近在学习jQuery EasyUI,发现中文的文档好少,部分文档不错但它是鸟语的,为了大家也为了自己学习吧,汉化做一下笔记. 有没有说清楚的,或者翻译不正确的地方还请大家谅解指出.. 由于工作时间原 ...

  10. Canvas入门(3):图像处理和渲染文本

    资源:http://www.ido321.com/997.html 一.图像处理(非特别说明,全部结果均来自最新版Google) 在HTML 5中,不仅能够使用Canvas API绘制图形,也能够用于 ...