Android XMPP Service (Google Talk) + Google Map == Follow Me

Android XMPP Service (Google Talk) + Google Map == Follow Me

分類: Mobile
2007/11/27 10:22

我在 Android SDK 上寫了一個簡單的程式 (姑且把她叫 Follow Me),來測試 Android 的 XMPP Services (Google Talk) & Google Map 整合,我希望可以透過 Google Talk,來追蹤 Contact Lists 現在的位置及互相通訊。例如可以透過 FollowMe ,我就可以很順利的開車跟在領導車的朋友後面,因為 FollwMe 會在地圖上追蹤及顯示共同出遊的車子(包含領導車及自己)的位置,一路順暢的共同到達目的地。畫面如下圖,我 (soccercheng) 在追蹤 [email protected] 的行蹤。



要寫這個程式,必須要對下圖中,Application Framework 的一些項目進行了解,如:

  • XMPP Service
  • Google Map
  • Notification Manager
  • Location Manager
  • Content Providers

如果只看 Android 提供的 Google APIs & Services 文件,很難看的懂他在寫甚麼,MapView 還好,XMPP 就不好懂,所以我把在測試 XMPP 的經驗和大家分享一下:

首先,我們必須讓 GPhone 的 XMPP 掛載上 Google Talk (This means U have to apply an Google Talk account),在 Dev Tools --> XMPP Settings (如上圖),用你的帳號讓登入 Google Talk。

接下來,介紹一下 Android XMPP Programming 的基本概念:

Listen to XMPP Notifications

(建議你先看一下 Anatomy of an Android Application,不然你會看不懂)

XMPP Services 是透過 Notification 的方式,來發佈消息,所已入你的程式想知道 XMPP Services 的訊息,就必須掛載 IntentReceiver & IntentFilter,文件中並未記載 XMPP Services 到底發布了哪些 Notifications,相關的定義在 com.google.android.xmppService.XmppConstants 中,不過文件上並沒有這個class 的說明,我是直接在 eclipse 看這個 XmppConstants 的說明,XmppConstants 最主要定義了三個:

  • XmppConstants.ACTION_XMPP_CONNECTION_STATE,當你程式 bind 到 XMPP Services 時,XMPP Service 會透過這個 Action 告知你現在的 state。
  • XmppConstants.ACTION_ROSTER_OR_PRESENCE_CHANGED,我測試的結果是當你的聯絡人在 Google Talk 上的狀態有變化時 (忙碌、線上、下線、...),XMPP Service 會透過這個 Action 告知你。
  • XmppConstants.ACTION_NEW_CHAT

另外,我有定義了兩個 Action,讓我的程式透過 XMPP Service ,來告知我是否有人跟我要我現在位置,會是跟我回報他的位置:

  • ACTION_REQUEST_LOCATION
  • ACTION_REPORT_LOCATION

註冊 & 掛載 IntentReceiver & IntentFilter

註冊方式基本上有兩種,一種是在 AndroidManifest.xml 中定義,或是寫程式向系統註冊,兩者最主要的差異是:

  • 如果你是在 AndroidManifest.xml 中定義,則你的 IntentReceiver 是由系統來 instantiate ,當系統呼叫完你的 IntentReceiver 的 onReceiveIntent(...) 之後,這個物件 IntentReceiver Object 隨時可能被 Garbage Collector 收回。
  • 由程式註冊,比較有彈性,我可以在Activity's onStart() 註冊,onStop() 時取消註冊。

public class FollowMe extends MapActivity
implements View.OnClickListener {

....

/** Called when the activity is becoming visible to the user. */
/* For example, you can register an IntentReceiver in onStart()
* to monitor for changes that impact your UI, and unregister it in onStop()
* when the user an no longer see what you are displaying.
* The onStart() and onStop() methods can be called multiple times,
* as the activity becomes visible and hidden to the user.
*/
@Override
protected void onStart() {
super.onStart();

// Register XMPP Data MessageReciver
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(XmppDataMessageReceiver.ACTION_REQUEST_LOCATION);
intentFilter.addAction(XmppDataMessageReceiver.ACTION_REPORT_LOCATION);
intentFilter.addAction(XmppDataMessageReceiver.ACTION_NEW_CHAT);
intentFilter.addAction(...ACTION_XMPP_CONNECTION_STATE...);
intentFilter.addAction(...ACTION_ROSTER_OR_PRESENCE_CHANGED...);

....

this.registerReceiver(xmppDataMessageReceiver, intentFilter);
}

@Override
protected void onStop() {
super.onStop();

// unregister XMPP Data MessageReciver
this.unregisterReceiver(xmppDataMessageReceiver);
}

....

}

在 IntentReceiver 中接收 Notification

public class XmppDataMessageReceiver extends IntentReceiver {

....

@Override
public void onReceiveIntent(Context context, Intent intent) {

String action = intent.getAction();

if(action.equals(ACTION_REQUEST_LOCATION)) {
Bundle bundle = intent.getExtras();
if(bundle != null) {
String requester = bundle.getString("requester");
//showMessage(context, requester);
if(requester != null)
activity.reportLocation(requester);
}
}else if(action.equals(ACTION_REPORT_LOCATION)) {
Bundle bundle = intent.getExtras();
if(bundle != null) {
String reporter = bundle.getString("reporter");
int latitude = Integer.parseInt(bundle.getString("latitude"));
int longitude = Integer.parseInt(bundle.getString("longitude"));
activity.setUserLocation(reporter, latitude, longitude);
}
}else if(action.equals(ACTION_XMPP_CONNECTION_STATE)) {
Bundle bundle = intent.getExtras();
int state = bundle.getInteger("state").intValue();
if(state == com.google.android.xmppService.ConnectionState.REQUESTED_ROSTER) {
// Get the base URI for IM contacts.
try {
ContentURI myPerson = new ContentURI("content://im/contacts");

// Query for this record.
Cursor cur = activity.managedQuery(myPerson, null, null, null);
String[] names = cur.getColumnNames();
// TODO There is no document for IM Contact operation,
// 2 hard for me to hacking it
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} else if(action.equals(ACTION_ROSTER_OR_PRESENCE_CHANGED)) {
//showMessage(context, ACTION_ROSTER_OR_PRESENCE_CHANGED);
} else if(action.equals(ACTION_NEW_CHAT)) {
//showMessage(context, ACTION_NEW_CHAT);
}
}

....

}

Reporting Location

public class XmppDataMessageReceiver extends IntentReceiver {

....

private Intent getRequestIntentToSend() {
Intent intent = new Intent(XmppDataMessageReceiver.ACTION_REQUEST_LOCATION);
intent.putExtra("requester", userName);

return intent;
}

private Intent getResponseIntentToSend() {
Intent intent = new Intent(XmppDataMessageReceiver.ACTION_REPORT_LOCATION);
intent.putExtra("reporter", userName);

//Bundle bundle = intent.getExtras();

int i = locationIndex++ % 19;
intent.putExtra("latitude", Integer.toString(locations[i][0]));
intent.putExtra("longitude", Integer.toString(locations[i][1]));
//intent.putExtra("location", new Point(locations[i][0], locations[i][1]));

return intent;
}

public void reportLocation(String requester) {
Intent intent = getResponseIntentToSend();

if (mXmppSession == null) {
showMessage(getText(R.string.xmpp_service_not_connected));
return;
}

try {
mXmppSession.sendDataMessage(requester, intent);
} catch (DeadObjectException ex) {
showMessage(getText(R.string.found_stale_xmpp_service));
mXmppSession = null;
bindXmppService();
}
}

....

}

Binding to XMPP Service

如何 bind to XMPP Service,在現在接露的 document 中找不到,還好在 SDK 的 sample 中,有兩個應用 (XmppDataMessageReceiver.java &XmppDataMessageSender.java),有用到一些 undocumented classes,其中接露了不少秘密。

public class XmppDataMessageReceiver extends IntentReceiver {

....

private void bindXmppService() {
bindService((new Intent()).setComponent(
com.google.android.xmppService.XmppConstants.XMPP_SERVICE_COMPONENT),
null, mConnection, 0);
}

private IXmppSession mXmppSession = null;

private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the XmppService has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
IXmppService xmppService = IXmppService.Stub.asInterface(service);

// Increment the event monitor reference count. When application UI
// is alive and listening for the various XMPP event intent
// broadcast (i.e. incoming chat, muc invitation, subscription invitation),
// call this so XmppService will send the appropriate intent broadcast
// instead of posting a titlebar notification.
try {
xmppService.incrementEventMonitorRef();
} catch (DeadObjectException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();

return;
}

try {
mXmppSession = xmppService.getDefaultSession();

if (mXmppSession == null) {
// this should not happen.
showMessage(getText(R.string.xmpp_session_not_found));

return;
}

//if()
mXmppSession.requestRosterAndSendInitialPresence();
userName = mXmppSession.getUsername();
} catch (DeadObjectException ex) {
//Log.e(LOG_TAG, "caught " + ex);
showMessage(getText(R.string.found_stale_xmpp_service));

return;
}

okButton.setEnabled(true);
}

public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mXmppSession = null;
okButton.setEnabled(false);
}
};

....

}

你可能感兴趣的:(eclipse,android,UI,Google,mobile)