码迷,mamicode.com
首页 > 移动开发 > 详细

【Android架构篇】之定位数据如何从GPS芯片到应用层(一)

时间:2015-08-12 16:56:09      阅读:4907      评论:0      收藏:0      [点我收藏+]

标签:gps   android   jni   framework   

Android:V4.2.2
Source Insight

写在前面

在漫长的Android源码编译等待过程中,想起之前写过一部分的Android定位实现的探究小品,于是继续探究。

:代码都是片段化的代码,用来提纲挈领的说明问题。

定位的基础知识
1、定位芯片和CPU之间通过串口进行通信
2、串口和CPU之间传输的是ASCII格式的NMEA(National Marine Electronics Association)信息,如:

$GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,,,,0000*1F
$GPGLL,4250.5589,S,14718.5084,E,092204.999,A*2D
$GPGSV,3,1,10,20,78,331,45,01,59,235,47,22,41,069,,13,32,252,45*70
$GPRMC,092204.999,A,4250.5589,S,14718.5084,E,0.00,89.68,211200,,*25
基于以上两点,要探知定位数据从GPS芯片到应用层的流程,最好的途径就是从应用层输出NEMA信息的地方开始。

一、GPS定位的应用层实现

Luckily,在应用层我们可以通过onNmeaReceived()方法获取到NMEA信息,如下Code Fragment:

public class GpsTestActivity extends ActionBarActivity {
	/* Other Codes */
	
	/** 获取系统的定位服务,记得在AndroidManifest中赋予定位方面的权限:
     * <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     * <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
     * <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
	 */
	LocationManager mLocationService = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
	mLocationService.addNmeaListener(mNmeaListener);
	
	private GpsStatus.NmeaListener mNmeaListener = new NmeaListener() {
		@Override
		public void onNmeaReceived(long timestamp, String nmea) {
			System.out.println(nmea + "\n");
		}
	};
}

二、GPS定位的Framework层实现

GpsStatus.NmeaListener是一个接口类,来自GpsStatus.java文件:

frameworks\base\location\java\android\location\GpsStatus.java
/**
 * Used for receiving NMEA sentences from the GPS.
 * NMEA 0183 is a standard for communicating with marine electronic devices
 * and is a common method for receiving data from a GPS, typically over a serial port.
 * See <a href="http://en.wikipedia.org/wiki/NMEA_0183">NMEA 0183</a> for more details.
 * You can implement this interface and call {@link LocationManager#addNmeaListener}
 * to receive NMEA data from the GPS engine.
 */
public interface NmeaListener {
	void onNmeaReceived(long timestamp, String nmea);
}
在上述App中,我们的应用程序实现了该方法,一旦NMEA数据到来,onNmeaReceived()方法就被调用一次,我们在Console上可以看到原始的NEMA信息。
那么接下来,就要寻找nmea数据的来源了。

mNmeaListener通过LocationManager类的addNmeaListener()方法进行注册(register):

frameworks\base\location\java\android\location\LocationManager.java
/**
 * Adds an NMEA listener.
 *
 * @param listener a {@link GpsStatus.NmeaListener} object to register
 *
 * @return true if the listener was successfully added
 *
 * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
 */
public boolean addNmeaListener(GpsStatus.NmeaListener listener) {
	boolean result;

	/* mNmeaListeners是LocationManager类的成员变量:
	 * private final HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport> mNmeaListeners =
     *      new HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport>();
	 */
	if (mNmeaListeners.get(listener) != null) {
		// listener is already registered
		return true;
	}
	try {
		GpsStatusListenerTransport transport = new GpsStatusListenerTransport(listener);
		result = mService.addGpsStatusListener(transport);
		if (result) {
			mNmeaListeners.put(listener, transport);
		}
	} catch (RemoteException e) {
		Log.e(TAG, "RemoteException in registerGpsStatusListener: ", e);
		result = false;
	}

	return result;
}
这里,先检测定义的NmeaListener有没有被注册过,若果没有,注册之。
注册到哪里去了呢?
由mNmeaListeners成员的定义可知,和GpsStatus.NmeaListener进行关联的是GpsStatusListenerTransport,而它是LocationManager类的一个内部类。
只看相关的部分:

// This class is used to send GPS status events to the client's main thread.
private class GpsStatusListenerTransport extends IGpsStatusListener.Stub {
	private final GpsStatus.NmeaListener mNmeaListener;

	// This must not equal any of the GpsStatus event IDs
	private static final int NMEA_RECEIVED = 1000;
	private class Nmea {
		long mTimestamp;
		String mNmea;

		Nmea(long timestamp, String nmea) {
			mTimestamp = timestamp;
			mNmea = nmea;
		}
	}
	private ArrayList<Nmea> mNmeaBuffer;

	//G psStatusListenerTransport(GpsStatus.Listener listener){} 
	GpsStatusListenerTransport(GpsStatus.NmeaListener listener) {
		mNmeaListener = listener;
		mListener = null;
		mNmeaBuffer = new ArrayList<Nmea>();
	}

	@Override
	public void onNmeaReceived(long timestamp, String nmea) {
		if (mNmeaListener != null) {
			synchronized (mNmeaBuffer) {
				mNmeaBuffer.add(new Nmea(timestamp, nmea));
			}
			Message msg = Message.obtain();
			msg.what = NMEA_RECEIVED;
			// remove any NMEA_RECEIVED messages already in the queue
			mGpsHandler.removeMessages(NMEA_RECEIVED);
			mGpsHandler.sendMessage(msg);
		}
	}

	private final Handler mGpsHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			if (msg.what == NMEA_RECEIVED) {
				synchronized (mNmeaBuffer) {
					int length = mNmeaBuffer.size();
					for (int i = 0; i < length; i++) {
						Nmea nmea = mNmeaBuffer.get(i);
						mNmeaListener.onNmeaReceived(nmea.mTimestamp, nmea.mNmea);
					}
					mNmeaBuffer.clear();
				}
			} else {
				// synchronize on mGpsStatus to ensure the data is copied atomically.
				}
			}
		}
	};
}
在GpsStatusListenerTransport类中:
定义一个Nmea类型的链表mNmeaBuffer,一旦onNmeaReceived()接收到NMEA数据,新数据被加载到链表mNmeaBuffer中(mNmeaBuffer.add(new Nmea(timestamp, nmea))),然手置消息标志为NMEA_RECEIVED(msg.what = NMEA_RECEIVED)。
mGpsHandler对上述NMEA_RECEIVED消息进行处理,最终把传过来的NMEA数据发往应用层GpsTestActivity中的onNmeaReceived()。
那么,GpsStatusListenerTransport类中onNmeaReceived(long timestamp, String nmea)方法的nmea数据有谁提供呢?

GpsStatusListenerTransport类继承自IGpsStatusListener,由类前的字符"I"我们得知,它是一个扩展名为.aidl的文件。
注:
AIDL:AIDL机制用来完成在进程之间进行通信(在Android中不允许进程间共享数据),它的详细知识另外Google之。
这里,我们再次见到了onNmeaReceived():
rameworks\base\location\java\android\location\IGpsStatusListener.aidl
oneway interface IGpsStatusListener
{
    void onGpsStarted();
    void onGpsStopped();
    void onFirstFix(int ttff);
    void onSvStatusChanged(int svCount, in int[] prns, in float[] snrs, in float[] elevations, in float[] azimuths, int ephemerisMask, int almanacMask, int usedInFixMask);
    void onNmeaReceived(long timestamp, String nmea);
}

oneway关键字是用来修饰远程调用行为。使用该关键词时,远程调用不是阻塞的,它只是发送事物数据并立即返回。接口的最终实现是把普通的远程调用按照Binder线程池的调用规则来接收,如果oneway是使用在本地调用上,那么不会有任何影响,并且调用依然是异步的。
下面,探究必须进入第三层。

三、GPS定位的Lib层实现

和IGpsStatusListener接头的是GpsLocationProvider类:

frameworks\base\services\java\com\android\server\location\GpsLocationProvider.java
public class GpsLocationProvider implements LocationProviderInterface {
	// 此处省略1000+N行
	private ArrayList<Listener> mListeners = new ArrayList<Listener>();
	
	private final class Listener implements IBinder.DeathRecipient {
        final IGpsStatusListener mListener;

        Listener(IGpsStatusListener listener) {
            mListener = listener;
        }

        @Override
        public void binderDied() {
            if (DEBUG) Log.d(TAG, "GPS status listener died");

            synchronized (mListeners) {
                mListeners.remove(this);
            }
            if (mListener != null) {
                mListener.asBinder().unlinkToDeath(this, 0);
            }
        }
    }
	
	/**
     * called from native code to report NMEA data received
     */
    private void reportNmea(long timestamp) {
        synchronized (mListeners) {
            int size = mListeners.size();
            if (size > 0) {
                // don't bother creating the String if we have no listeners
                int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);
                String nmea = new String(mNmeaBuffer, 0, length);

                for (int i = 0; i < size; i++) {
                    Listener listener = mListeners.get(i);
                    try {
                        listener.mListener.onNmeaReceived(timestamp, nmea);
                    } catch (RemoteException e) {
                        Log.w(TAG, "RemoteException in reportNmea");
                        mListeners.remove(listener);
                        // adjust for size of list changing
                        size--;
                    }
                }
            }
        }
    }
}
GPS定位功能最终需要调用硬件实现,操作硬件就必须通过C/C++完成,GpsLocationProvider中包含许多native方法,采用JNI机制为上层提供服务。
在上面的Code Frame中,通过调用本地方法native_read_nmea()获取到NMEA数据,然后传数据到IGpsStatusListener接口类的onNmeaReceived()方法。
reportNmea()是被JNI方法回调的方法,在 JNI 的实现中,通过这些方法的回调来传递JNI层的执行结果。

源码编译出错,解决问题去。。。

native_read_nmea()在GpsLocationProvider类中定义:

private native int native_read_nmea(byte[] buffer, int bufferSize);
native指明它是本地方法,和它对应的C/C++文件的实现是:
static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj, jbyteArray nmeaArray, jint buffer_size);
How?Next...

frameworks\base\services\jni\com_android_server_location_GpsLocationProvider.cpp
static JNINativeMethod sMethods[] = {
    /* name, signature, funcPtr */
    /* other members... */
    {"native_read_nmea", "([BI)I", (void*)android_location_GpsLocationProvider_read_nmea},
    /* other members... */
};
JNINativeMethod是Android中采用的Java和C/C++函数的映射方式,并在其中描述了函数的参数和返回值:

typedef struct {
    const char* name;		// Java文件中的本地方法
    const char* signature;	// 述了函数的参数和返回值
    void*       fnPtr;		// 指针,指向具体的C/C++函数
} JNINativeMethod;
详细内容这里还是不展开了。
来看android_location_GpsLocationProvider_read_nmea()的实现:
static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj,
                                            jbyteArray nmeaArray, jint buffer_size)
{
    // this should only be called from within a call to reportNmea
    jbyte* nmea = (jbyte *)env->GetPrimitiveArrayCritical(nmeaArray, 0);
    int length = sNmeaStringLength;
    if (length > buffer_size)
        length = buffer_size;
    memcpy(nmea, sNmeaString, length);
    env->ReleasePrimitiveArrayCritical(nmeaArray, nmea, JNI_ABORT);
    return length;
}




待续。。。。。

版权声明:本文为博主原创文章,未经博主允许不得转载。

【Android架构篇】之定位数据如何从GPS芯片到应用层(一)

标签:gps   android   jni   framework   

原文地址:http://blog.csdn.net/u013686019/article/details/47444839

(0)
(1)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!