智慧手機正在逐漸統治整個手機世界,這是一個顯而易見的事實。自從GPS裝置被普遍的內嵌到智慧手機中,它給那些利用地理定位的應用程式提供了極大的幫助。 其中一類程式是採用基於定位的服務(Location Based Service),它是利用資料來計算使用者所在位置的。程式通常用的技術是geocoding(從地理資料中尋找相關的地理座標,如一條街的位置)和reverse geocoding(根據提供的座標來提供資訊)。另一類程式則採用Proximity Alerts。像它的名字那樣,當使用者的位置接近某個特定的Point of Interest(POI)時會進行提示。隨著大量的程式打算應用這項技術,並伴有宣傳精彩的示例,Proximity alert將會是接下來幾年的熱點。在這個教程中,我將展示怎樣去利用Android的內嵌Proximity alert功能。
在開始之前,簡單的瞭解一下基於定位的程式或geocoding會對接下來的閱讀帶來極大的幫助。你可能需要要讀一下我之前的幾篇教程,如 “Android Location Based Services Application – GPS location”和“Android Reverse Geocoding with Yahoo API – PlaceFinder”。另一個需要提醒的是,這篇教程是受“Developing Proximity Alerts for Mobile Applications using the Android Platform”這篇文章啟發而生。 這篇文章分為四個部分,對於初學者一些地方可能會稍顯複雜並帶來一些困難。基於這些原因,我決定寫一篇相對更短更易懂的教程。
我們將會建立一個簡單的程式,它儲存的一個點座標會被使用者觸發並且當使用者接近這個點推送訊息。 當使用者到達那個點座標時會根據需求獲取訊息。
我們首先建立一個Eclipse專案開始,並命名為“AndroidProximityAlertProject”。接下來,為程式建立一個Main Activity然後命名為ProxAlertActivity。 下面是程式的主頁面:
這裡是UI介面的佈局,命名為main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
; html-script: false ] <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <EditText android:id="@+id/point_latitude" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginLeft="25dip" android:layout_marginRight="25dip" /> <EditText android:id="@+id/point_longitude" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginLeft="25dip" android:layout_marginRight="25dip" /> <Button android:id="@+id/find_coordinates_button" android:text="Find Coordinates" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/save_point_button" android:text="Save Point" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> |
現在我們開始做一些有意思的東西。首先,我們需要一個到LocationManager class的引用,提供系統的位置服務。你可以通過getSystemService方法從activity中獲得。然後,(如果使用者的位置發生改變)可以通過requestLocationUpdates方法來獲得通知。在開發Proximity Alerts的時候,這並不是必須的要求,但是在這裡我需要用它計算POI和使用者位置之間的距離。在我們的示例中,裝置在任何時候都可以呼叫getLastKnownLocation方法獲取某個指定提供者的最後位置。最後,我們會用到addProximityAlert方法設定一個proximity alert。它可以指定你想要座標(緯度、經度)和半徑。 如果我們想要在一段特定的時間監視某個alert,還可以給那個alert設定過期時間。我們還可以提供PendingIntent,當裝置進入或者離開一個監測到的alert區域時發出intent。
示例程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
; html-script: false ] package com.javacodegeeks.android.lbs; import java.text.DecimalFormat; import java.text.NumberFormat; import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class ProxAlertActivity extends Activity { private static final long MINIMUM_DISTANCECHANGE_FOR_UPDATE = 1; // in Meters private static final long MINIMUM_TIME_BETWEEN_UPDATE = 1000; // in Milliseconds private static final long POINT_RADIUS = 1000; // in Meters private static final long PROX_ALERT_EXPIRATION = -1; private static final String POINT_LATITUDE_KEY = "POINT_LATITUDE_KEY"; private static final String POINT_LONGITUDE_KEY = "POINT_LONGITUDE_KEY"; private static final String PROX_ALERT_INTENT = "com.javacodegeeks.android.lbs.ProximityAlert"; private static final NumberFormat nf = new DecimalFormat("##.########"); private LocationManager locationManager; private EditText latitudeEditText; private EditText longitudeEditText; private Button findCoordinatesButton; private Button savePointButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, MINIMUM_TIME_BETWEEN_UPDATE, MINIMUM_DISTANCECHANGE_FOR_UPDATE, new MyLocationListener() ); latitudeEditText = (EditText) findViewById(R.id.point_latitude); longitudeEditText = (EditText) findViewById(R.id.point_longitude); findCoordinatesButton = (Button) findViewById(R.id.find_coordinates_button); savePointButton = (Button) findViewById(R.id.save_point_button); findCoordinatesButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { populateCoordinatesFromLastKnownLocation(); } }); savePointButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { saveProximityAlertPoint(); } }); } private void saveProximityAlertPoint() { Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (location==null) { Toast.makeText(this, "No last known location. Aborting...", Toast.LENGTH_LONG).show(); return; } saveCoordinatesInPreferences((float)location.getLatitude(), (float)location.getLongitude()); addProximityAlert(location.getLatitude(), location.getLongitude()); } private void addProximityAlert(double latitude, double longitude) { Intent intent = new Intent(PROX_ALERT_INTENT); PendingIntent proximityIntent = PendingIntent.getBroadcast(this, 0, intent, 0); locationManager.addProximityAlert( latitude, // the latitude of the central point of the alert region longitude, // the longitude of the central point of the alert region POINT_RADIUS, // the radius of the central point of the alert region, in meters PROX_ALERT_EXPIRATION, // time for this proximity alert, in milliseconds, or -1 to indicate no expiration proximityIntent // will be used to generate an Intent to fire when entry to or exit from the alert region is detected ); IntentFilter filter = new IntentFilter(PROX_ALERT_INTENT); registerReceiver(new ProximityIntentReceiver(), filter); } private void populateCoordinatesFromLastKnownLocation() { Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (location!=null) { latitudeEditText.setText(nf.format(location.getLatitude())); longitudeEditText.setText(nf.format(location.getLongitude())); } } private void saveCoordinatesInPreferences(float latitude, float longitude) { SharedPreferences prefs = this.getSharedPreferences(getClass().getSimpleName(), Context.MODE_PRIVATE); SharedPreferences.Editor prefsEditor = prefs.edit(); prefsEditor.putFloat(POINT_LATITUDE_KEY, latitude); prefsEditor.putFloat(POINT_LONGITUDE_KEY, longitude); prefsEditor.commit(); } private Location retrievelocationFromPreferences() { SharedPreferences prefs = this.getSharedPreferences(getClass().getSimpleName(), Context.MODE_PRIVATE); Location location = new Location("POINT_LOCATION"); location.setLatitude(prefs.getFloat(POINT_LATITUDE_KEY, 0)); location.setLongitude(prefs.getFloat(POINT_LONGITUDE_KEY, 0)); return location; } public class MyLocationListener implements LocationListener { public void onLocationChanged(Location location) { Location pointLocation = retrievelocationFromPreferences(); float distance = location.distanceTo(pointLocation); Toast.makeText(ProxAlertActivity.this, "Distance from Point:"+distance, Toast.LENGTH_LONG).show(); } public void onStatusChanged(String s, int i, Bundle b) { } public void onProviderDisabled(String s) { } public void onProviderEnabled(String s) { } } } |
在onCreate方法中,我們用了location manager並加入一個自定義類,它實現了LocatioListener介面。可以讓我們通過onLocationChanged方法得到位置變換的通知。接下來,看一下怎樣去處理更新。我們會使用不同的UI控制元件給按鈕新增onClickListeners。
當使用者想要得到目前的座標時,populateCoordinatesFromLastKnownLocation方法會被呼叫。在方法內部,使用getLastKnownLocation方法來得到Location物件,位置資訊將會被填充到EditText控制元件。
同樣的,當位置資訊第一次被獲取時,使用者想要儲存座標並向saveProxityAlertPoint提供alert(新增POI)。我們可以將經度和緯度以preference data的方式用SharedPreferences類來儲存下來,詳情請見SharedPreferences.Editor。最後,我們通過getBrodcast靜態方法來建立一個PendingIntent。對於這種封裝過的intent,我們建立一個intentFilter並使用registerReceiver方法來繫結一個自定義BroadcastReceiver和intentfiler。需要提醒的是,這種繫結還可以通過manifest來實現。
現在,我們來看一下如何來處理使用者位置變化。在實現介面的MyLocationListener類裡,我們從SharedPreference裡獲取儲存的位置資訊(retrievelocationFromPreferences)。然後,通過distranceTo方法計算兩個位置(POI和現在位置)的距離。通過這個計算,我們才會知道是否真正進入了某個區域。
第一步,處理進入POI事件。 可以通過繼承BroadcastReceiver的類(ProximityIntentReceiver)來實現。當proximity alert被觸發,會返回一個自定義intent(之前繫結的locationManager)。處理過程發生在onReceive方法裡,它會被事件(進出POI區域)呼叫。在方法內部,我們從相關intent獲取KEY_PROXIMITY_ENTERING key(用來定義proximity alert型別是進入-true,還是離開-false))。
示例程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
; html-script: false ] package com.javacodegeeks.android.lbs; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.location.LocationManager; import android.util.Log; public class ProximityIntentReceiver extends BroadcastReceiver { private static final int NOTIFICATION_ID = 1000; @Override public void onReceive(Context context, Intent intent) { String key = LocationManager.KEY_PROXIMITY_ENTERING; Boolean entering = intent.getBooleanExtra(key, false); if (entering) { Log.d(getClass().getSimpleName(), "entering"); } else { Log.d(getClass().getSimpleName(), "exiting"); } NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, null, 0); Notification notification = createNotification(); notification.setLatestEventInfo(context, "Proximity Alert!", "You are near your point of interest.", pendingIntent); notificationManager.notify(NOTIFICATION_ID, notification); } private Notification createNotification() { Notification notification = new Notification(); notification.icon = R.drawable.ic_menu_notifications; notification.when = System.currentTimeMillis(); notification.flags |= Notification.FLAG_AUTO_CANCEL; notification.flags |= Notification.FLAG_SHOW_LIGHTS; notification.defaults |= Notification.DEFAULT_VIBRATE; notification.defaults |= Notification.DEFAULT_LIGHTS; notification.ledARGB = Color.WHITE; notification.ledOnMS = 1500; notification.ledOffMS = 1500; return notification; } } |
程式碼簡潔明瞭。當我們定義了一個進入或離開的proximity alert,就可以提供一個自定義的推送了。要實現它,首先需要引用一個合適的Service,比如NotificationMananger。通過這個Service,我們傳送通知給使用者,並封裝合適的推送物件。推送的方式是可定製的,如包括震動或者閃光燈等。我們還可以新增特定的圖示在狀態列顯示。如果只是簡單新增標題和資訊,適合呼叫setLastestEventInfo方法。 你可以在這裡檢視關於notification的詳細內容。除此之外,我們可以通過PendingIntent來引導Activity進行下意識地點選推送。 為了讓程式碼更通俗易懂,這裡我就不這樣做了。
我們最後得到的manifest程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
; html-script: false ] <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.javacodegeeks.android.lbs" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".ProxAlertActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="3" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.VIBRATE" /> </manifest> |
別忘了,新增相應的Permission到Manifest裡:
現在可以測試我們的程式了。啟動Eclipse配置,然後跳轉到DDMS介面檢視Emulation Control標籤,可以發現一個Location Controls選項。它可以給模擬器傳送模擬的位置資料。在Manual標籤裡,點選Send按鈕,這樣就能生成一些座標。
在此之後,GPS提供者將會知道最後一次請求的已知位置。所以,點選“find Coordinates”按鈕你將會得到:
點選“Save Point”來宣告當前位置為POI。 當前位置座標會被儲存到SharedPreference並且Proximity Alert會被新增到location manager中。
接下來,讓我們模擬使用者接近指定位置的事件。我們會更改座標值,例如更改維度(從37.422006到37.522006)。現在我們離的POI很遠。現在我們假裝我們正在接近它,所以把座標改的近一點(從37,52206到37.423006)。
當兩點之間的半徑達到預警要求(之前設定為1000米)時會得到提示。這時,建立並通過notification manager推送訊息。推送的訊息如下:
好了,大功告成。本文通過一個簡單的示例介紹瞭如何使用Android Platform來實施Proximity alerts。你可以從這裡下載示例專案的Eclipse專案程式碼。