【Android架構GPS篇】之定位數據怎樣從GPS芯片到應用層


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信息的地方開始。

NMEA資料參見:衛星定位數據NMEA介紹

一、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);
}

onewaykeyword是用來修飾遠程調用行為。使用該關鍵詞時。遠程調用不是堵塞的,它僅僅是發送事物數據並馬上返回。

接口的終於實現是把普通的遠程調用依照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;
}
盡管不清楚JNI深入含義,但這個函數意思還是挺明顯的。我們判斷:
第5行:用來動態分配內存。nmea指向獲取到的內存區域,同一時候把nmea和nmeaArray進行關聯;
第6行:sNmeaStringLength指示一次從串口讀取到的字節長度
第7、8行:在Java中調用native_read_nmea()方法時指明了我們須要取的數據長度,所以,假設從串口實際讀取的數據長度大於我們須要的,我們對串口數據進行截取:即。僅僅取指定長度的數據;
第9行:從串口讀出的數據存在sNmeaString中。這里Copy到nmea指向的內存區域;
第10行:nmea指向的內存區域中的數據交給nmeaArray,然后釋放nmea指向的內存空間。

這里也能夠看到,函數調用是通過nmeaArray傳遞NMEA數據的


以下應該看sNmeaStringLength、sNmeaString的設置過程:

static void nmea_callback(GpsUtcTime timestamp, const char* nmea, int length)
{
    JNIEnv* env = AndroidRuntime::getJNIEnv();
    // The Java code will call back to read these values
    // We do this to avoid creating unnecessary String objects
    sNmeaString = nmea;
    sNmeaStringLength = length;
    env->CallVoidMethod(mCallbacksObj, method_reportNmea, timestamp);
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
method_reportNmea、、、有沒有熟悉的感覺?
對。在GpsLocationProvider類中見過reportNmea(long timestamp)函數。


以下的代碼片段表明,method_reportNmea()和reportNmea()是綁定在一起的。調用C/C++函數method_reportNmea,也就間接調用Java的reportNmea()方法。這中間的機制,就是JNI。

static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
    /* other definitions... */
    method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
    /* other definitions... */
}
而method_reportNmea是在nmea_callback()函數中被調用的。哪里又調用nmea_callback()函數呢?
Let's go to neXt Layer...

四、GPS定位HAL層的實現

所謂Android的HAL層。也就是是Linux的應用程序。至於串口詳細配置,比方寄存器配置、數據收發等芯片級實現,是在在Linux內核里的。

com_android_server_location_GpsLocationProvider.cpp文件里另外出現nmea_callback的地方是:

GpsCallbacks sGpsCallbacks = {
    sizeof(GpsCallbacks),
    location_callback,
    status_callback,
    sv_status_callback,
    nmea_callback,
    set_capabilities_callback,
    acquire_wakelock_callback,
    release_wakelock_callback,
    create_thread_callback,
    request_utc_time_callback,
};
GpsCallbacks結構體封裝了全部須要回調的函數( 確切的說是函數指針),sGpsCallbacks調用關系:
static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj)
{
    // this must be set before calling into the HAL library
    if (!mCallbacksObj)
        mCallbacksObj = env->NewGlobalRef(obj);

    // fail if the main interface fails to initialize
    if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
        return false;

	/* other codes */
    return true;
}
而android_location_GpsLocationProvider_init()在GpsLocationProvider類中調用native_init()時被調用:
static JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
	{"native_init", "()Z", (void*)android_location_GpsLocationProvider_init}
}
這里,我們找到了和上層的關系。和下層怎樣打交道呢?
以下須要貼一大段代碼:
/** Represents the standard GPS interface. */
typedef struct {
    /** set to sizeof(GpsInterface) */
    size_t          size;
    /**
     * Opens the interface and provides the callback routines
     * to the implemenation of this interface.
     */
    int   (*init)( GpsCallbacks* callbacks );
    /** Starts navigating. */
    int   (*start)( void );
    /** Stops navigating. */
    int   (*stop)( void );
    /** Closes the interface. */
    void  (*cleanup)( void );
    /** Injects the current time. */
    int   (*inject_time)(GpsUtcTime time, int64_t timeReference,
                         int uncertainty);
    /** Injects current location from another location provider
     *  (typically cell ID).
     *  latitude and longitude are measured in degrees
     *  expected accuracy is measured in meters
     */
    int  (*inject_location)(double latitude, double longitude, float accuracy);
    /**
     * Specifies that the next call to start will not use the
     * information defined in the flags. GPS_DELETE_ALL is passed for
     * a cold start.
     */
    void  (*delete_aiding_data)(GpsAidingData flags);
    /**
     * min_interval represents the time between fixes in milliseconds.
     * preferred_accuracy represents the requested fix accuracy in meters.
     * preferred_time represents the requested time to first fix in milliseconds.
     */
    int   (*set_position_mode)(GpsPositionMode mode, GpsPositionRecurrence recurrence,
            uint32_t min_interval, uint32_t preferred_accuracy, uint32_t preferred_time);
    /** Get a pointer to extension information. */
    const void* (*get_extension)(const char* name);
} GpsInterface;
GpsInterface結構體封裝了GPS實現的標准接口——接口,注意!

接口不就時用來連接兩端的嗎?一端是com_android_server_location_GpsLocationProvider.cpp文件中的實現,那還有一端就是。。。都探到這個地步了。還有一端應該是串口方式直接和GPS芯片打交道的Linux驅動了吧?
確是。可是還須要一個媒介:

struct gps_device_t {
    struct hw_device_t common;
    /**
     * Set the provided lights to the provided values.
     *
     * Returns: 0 on succes, error code on failure.
     */
    const GpsInterface* (*get_gps_interface)(struct gps_device_t* dev);
};
然后,
static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
    int err;
    hw_module_t* module;
	/* other codes..*/
    err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
    if (err == 0) {
        hw_device_t* device;
        err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);
        if (err == 0) {
            gps_device_t* gps_device = (gps_device_t *)device;
            sGpsInterface = gps_device->get_gps_interface(gps_device);
        }
    }
	/* other codes..*/
}
static JNINativeMethod sMethods[] = {
     /* name, signature, funcPtr */
    {"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native},
}
GpsLocationProvider.java通過class_init_native的調用實現對C/C++文件里android_location_GpsLocationProvider_class_init_native的調用。
com_android_server_location_GpsLocationProvider.cpp通過gps_device_t獲取操作GPS芯片的接口。

How????
重點來了:GPS_HARDWARE_MODULE_ID
對。就是GPS_HARDWARE_MODULE_ID


往下看:

ardware\qcom\gps\loc_api\libloc_api\gps.c
struct hw_module_t HAL_MODULE_INFO_SYM = {
	.tag = HARDWARE_MODULE_TAG,
	.version_major = 1,
	.version_minor = 0,
	.id = GPS_HARDWARE_MODULE_ID,
	.name = "loc_api GPS Module",
    .author = "Qualcomm USA, Inc.",
	.methods = &gps_module_methods,
};
有木有?GPS_HARDWARE_MODULE_ID。
hardware\qcom\gps\loc_api\libloc_api\gps.c
extern const GpsInterface* gps_get_hardware_interface();

const GpsInterface* gps__get_gps_interface(struct gps_device_t* dev)
{
    return gps_get_hardware_interface();

}

static int open_gps(const struct hw_module_t* module, char const* name,
        struct hw_device_t** device)
{
    struct gps_device_t *dev = malloc(sizeof(struct gps_device_t));
    memset(dev, 0, sizeof(*dev));

    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = 0;
    dev->common.module = (struct hw_module_t*)module;
    dev->get_gps_interface = gps__get_gps_interface;

    *device = (struct hw_device_t*)dev;
    return 0;
}

static struct hw_module_methods_t gps_module_methods = {
    .open = open_gps
};
流程非常清楚了:
gps_get_hardware_interface()函數在驅動程序中實現
    ——在gps__get_gps_interface()中被調用
        ——在open_gps()被調用
            ——在gps_module_methods中例化
                ——HAL_MODULE_INFO_SYM


const GpsInterface* gps_get_hardware_interface()函數在其它C文件實現。該C文件是和Linux驅動打交道的應用程序。基本功能:

1、open處理器CPU和GPS芯片連接的串口。

2、read串口NEMA數據。並解析。

3、依據上層傳進來的回調函數。打包數據,調用對應Callback。進而發送到Android應用層。

static const GpsInterface  mGpsInterface = {
    .size =sizeof(GpsInterface),
	.init = gps_init,
		|--1、接收從上層傳下來的GpsCallbacks變量,用它初始化GpsState->callbacks成員	
		|--2、GpsState結構體的其它成員初始化
		|--3、GpsState->init狀態設置為:STATE_INIT
		|--4、最重要:啟動GPS線程,進行數據的讀取、處理:
		state->thread = state->callbacks.create_thread_cb("gps", gps_state_thread, state);
			--gps_create_thread create_thread_cb;
				--typedef pthread_t (* gps_create_thread)(const char* name, void (*start)(void *), void* arg);
								
								
    .start = gps_start,
		--設置GPS的狀態為開始:GPS_STATUS_SESSION_BEGIN
    .stop = gps_stop,		
		--設置GPS的狀態為結束:GPS_STATUS_SESSION_END
    .cleanup = gps_cleanup,	
		--退出須要進行的一些清理工作,如GpsState->init = STATE_QUIT,GpsCallbacks指針歸null。信號量回收
    .inject_time = gps_inject_time,	
		--可為空函數
    .inject_location = gps_inject_location,	
		--可為空函數
    .delete_aiding_data = gps_delete_aiding_data,	
		--可為空函數
    .set_position_mode = gps_set_position_mode,	
		--設置GPS工作模式:單GPS、單BD、GPS/BD雙系統
    .get_extension = gps_get_extension,	
		--定位之外的擴展功能實現
};

state->thread = state->callbacks.create_thread_cb("gps", gps_state_thread, state);							
	--static void gps_state_thread(void*  arg):
	  1、state通過arg參數傳入函數
	  2、創建了Time和Nmea數據處理兩個線程											
		state->nmea_thread = state->callbacks.create_thread_cb("nmea_thread", gps_nmea_thread, state);
			--static void gps_nmea_thread(void*  arg)
				--gps_opentty(state);
				   nmea_reader_init(reader);
					--nmea_reader_parse(NmeaReader*  r) {
						if (gps_state->callbacks.nmea_cb) {
							struct timeval tv;
							unsigned long long mytimems;
							gettimeofday(&tv,NULL);
							mytimems = tv.tv_sec * 1000 + tv.tv_usec / 1000;
							gps_state->callbacks.nmea_cb(mytimems, r->in, r->pos);
							D("reader_parse. %.*s ", r->pos, r->in );
						}
					}

我們是從APP層NMEA信息輸出自定向下分析的,APP層信息輸出的終於起始是:gps_state->callbacks.nmea_cb(mytimems, r->in, r->pos);


到這里還有個問題:GPS芯片和CPU連接,使用的是哪個串口?這個串口號怎么確定的呢?

打算貼個完整HAL層的實例,考慮到代碼非常多,下篇在說吧。

。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM