此Android程式練習是為成大資工系-電腦通訊網路課程 所設計的一個Code Lab,目的在介紹Android之間的裝置通訊,以及在Android上面如何實作一個聊天室應用,其中包含在Server端的HttpServerlet以及GCM Api的使用。
此App運用可以上安裝此App的所有使用者彼此間進行聊天。
課程時間:3小時
投影片
下載
Check Point 0 : Android 環境設定
安裝設定Android開發環境,以繼續接下來的練習
Step 1 : 下載並安裝Android ADT Bundle
Step 2 : 下載ChatRoom Project ,並進行解壓縮
- Do not publish this code to Google Play !!
Step 3 : 在新的Workspace 中,匯入ChatRoom Project
- File> Import > General> Existing Project into Workspace
- 選擇Project路徑
- 按下Select All 按鈕
- 勾選 Copy project into workspace
若匯入後有錯,請檢查Project name右鍵 > Properties > Google > AppEngine > AppEngine SDK路徑是否正確
Step 4 : 設定Project Build Target為Android 4.3
- Project上按右鍵 > Properties > Android > Project Build Target > Google APIs Level 18
Step 5 : 註冊下載Genymotion Android 模擬器 / 或使用Android 手機
Step 6 :執行Genymotion並建立一個Galaxy Nexus with Google App API17
Step 7 :嘗試在模擬器上執行專案
*中文字如果出現亂碼請將編碼改為UTF-8 (File > Properties > Resource > Text file encoding)
*如果有任何錯誤,請坐下列檢查
- ADT > Project > clean > clean all
- Project右鍵 > property > Android > 檢查Build Target 及 Library
Check Point 1 : 下載App Engine SDK
Step 1 : 安裝Eclipse 外掛
- ADT > Help > install new software > 輸入 http://dl.google.com/eclipse/plugin/4.2
- 勾選(1)Google App Engine Tools for Android、(2) SDKs / Google App Engine Java SDK 及(3)SDKs / Google Web Toolkit > 按下Next開始安裝 (4) Google plugin for Eclipse
- https://developers.google.com/appengine/downloads?hl=zh-TW&csw=1
- 在GAE-ChetRoom按右鍵 > Properties > Google > App Engine > Use App Engine
註: app engine sdk 1.8.6 需要以JDK 7 來編譯,JDK 6會有錯
Step 2: 建立Web Application專案
- File > new > Web application project > 只勾選Use Google App Engine
- Project Name 設為TEST
- Package 設為com.xxx.gae.test
- 執行新建立的專案,並在瀏覽器開啟http://localhost:8888/,看是否正常顯示
- 觀察此專案內容結構
Check Point 2 : 在Google Api Console 開啟GCM服務
Step 1 : 連到Google API Console 並建立一Project
Step 2 : 取得API Key
- API Access > Create Server Key
- 填入whitelist ip, 此處保持空白就好
- 按下Create
- 將API_KEY記錄下來,之後會用到
Check Point 3 : 在Server上建立App Engine Project,並上傳專案
Step 1 : Create GAE project
- https://appengine.google.com/ > create project
- 輸入一個Application Identifier,此名稱必須沒有被用過。http://[applicaton identifier].appspot.com 為你所申請的server domain name
Step 2 : 將Web Application上傳
- GAE_ChatRoom project > 右鍵 > Properties > Google > App Engine > Application Id 填入Step 1 所設定的Application Identifier
- GAE_ChatRoom project > 右鍵 > Google > Deploy to App Engine
Step 3 : 測試
- 嘗試 http://[applicaton identifier].appspot.com
Check Point 4 : 建立GcmRegister.java HttpServlet處理註冊
Step 1 : 建立GcmRegister來定義DataStore欄位
- 建立GcmRegister其中包含regid的String欄位
GcmRegister.java
package com.whilerain.gae.chatroom.db;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable
public class GcmRegister {
@PrimaryKey
private String regid;
public GcmRegister(String id){
regid = id;
}
public String getRegid() {
return regid;
}
public void setRegid(String regid) {
this.regid = regid;
}
}
|
Step 2 : 在GAE_ChatRoom當中建立PMF類別來處理DataStore的存取作業。
PMF.java
package com.whilerain.gae.chatroom.db;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;
public final class PMF {
private static final PersistenceManagerFactory pmfInstance = JDOHelper.getPersistenceManagerFactory("transactions-optional");
public static PersistenceManagerFactory get() {
return pmfInstance;
}
}
|
Step 3 : 在DoGcmRegister.java當中,儲存所上傳的registration_id
DoGcmRegister.java
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String jsonState = URLDecoder.decode(req.getParameter("statement"));
PersistenceManager pm=PMF.get().getPersistenceManager();
JSONObject jsonResp = new JSONObject();
try {
JSONObject jsonObject = new JSONObject(jsonState);
String regid = jsonObject.getString("regid");
GcmRegister reg = new GcmRegister(regid);
pm.makePersistent(reg);
jsonResp.put("result", "ok");
resp.setContentType("text/json");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().println(jsonResp.toString());
} catch (Exception e) {
resp.setContentType("text/plain");
resp.getWriter().println(e.getMessage());
e.printStackTrace();
}finally{
pm.close();
}
}
|
Step 4 : 宣告Servlet路徑
- 在war/WEB-INF/web.xml加入servlet路徑宣告,在<web-app>標籤之間加入
<servlet>
<servlet-name>GCMREG</servlet-name>
<servlet-class>com.whilerain.gae.chatroom.DoGcmRegister</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GCMREG</servlet-name>
<url-pattern>/gcmreg</url-pattern>
</servlet-mapping>
|
step 5 : 上傳目前版本,並測試之
Check Point 5 : 建立SendMessage.java HttpServlet處理訊息的推送
Step 1 : 在SendMessage.java實作doPost()來處理取得Http Post參數
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String jsonState = URLDecoder.decode(req.getParameter("statement"));
PersistenceManager pm = PMF.get().getPersistenceManager();
JSONObject jsonResp = new JSONObject();
try {
JSONObject jsonObject = new JSONObject(jsonState);
castMessage(pm, jsonObject);
jsonResp.put("result", "ok");
resp.setContentType("text/json");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().println(jsonResp.toString());
} catch (Exception e) {
resp.setContentType("text/plain");
resp.getWriter().println(e.getMessage());
e.printStackTrace();
} finally {
pm.close();
}
}
|
Step 2 :
在war/WEB-INF/web.xml加入servlet路徑宣告,在<web-app>標籤之間加入
<servlet>
<servlet-name>SendMessage</servlet-name>
<servlet-class>com.whilerain.gae.chatroom.SendMessage</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>SendMessage</servlet-name>
<url-pattern>/sendmsg</url-pattern>
</servlet-mapping>
|
Step 3 : 上傳目前版本
Check Point 6 : 將訊息丟給GCM廣播至裝置
Step 1 : 在GAE_ChatRoom專案加入gcm-server.jar參考 (已加入Project中)
- 將gcm-server.jar複製到war/WEB-INF/lib
- 並且在CoWallet-GAE右鍵 > properties > java build path > Library > add JAR 選擇gcm-server.jar
Step 2 : 在GAE_ChatRoom專案加入json-simple-1.1.1.jar (已加入Project中)
- 將json-simple-1.1.1.jar複製到war/WEB-INF/lib
Step 3 : 在GAE_ChatRoom專案加入設定API_KEY
public static String API_KEY = "AIzaSyBY0AYX9RdPpvsAmnyjuwm9hl1-Xh...";
|
Step 4 : cast to GCM 程式碼
- 在SendMessage.java, 加入castMessage()來將所有regid取出,並依序將message丟給GCM
private void castMessage(PersistenceManager pm, JSONObject jsonReq) throws IOException {
Query queryRegid = pm.newQuery(GcmRegister.class);
List<GcmRegister> reg_results = (List<GcmRegister>) queryRegid.execute();
for (GcmRegister gcmReg : reg_results) {
GCM.cast(pm, gcmReg.getRegid(), jsonReq.toString(), gcmReg.getMember());
}
}
|
Step 5 : 在GCM.java當中實作Cast to GCM
public static void cast(PersistenceManager pm, String regid, String content) throws IOException{
Sender sender = new Sender(API_KEY);
Message.Builder builder = new Message.Builder();
builder.addData("timestamp", String.valueOf(System.currentTimeMillis()));
builder.addData("statement", URLEncoder.encode(content,"UTF-8"));
Message message = builder.build();
Result result = sender.send(message, regid, 5);
if (result.getMessageId() != null) {
String canonicalRegId = result.getCanonicalRegistrationId();
if (canonicalRegId != null) {
// same device has more than one registration ID: update
// database
GcmRegister reg = pm.getObjectById(GcmRegister.class, regid);
pm.deletePersistent(reg);
reg = new GcmRegister(canonicalRegId);
pm.makePersistent(reg);
}
} else {
String error = result.getErrorCodeName();
if (error.equals(Constants.ERROR_NOT_REGISTERED)) {
// application has been removed from device - unregister from
// database
GcmRegister reg = pm.getObjectById(GcmRegister.class, regid);
pm.deletePersistent(reg);
}
}
}
|
Check point 7 : Android端取得裝置GCM Registration id,並註冊到Server上
Step 1 : 修改SENDER_ID
- 將MainActivity.java中的SENDER_ID改為Google API Console的 Project number
String SENDER_ID = "36994572588";
|
Step 2 : 修改Server URL
private static final String URL_GCMREG = "http://your-gae-projectname.appspot.com/gcmreg";
|
Step 3 : 在MainActivity.java中收到regid後,使用http post註冊到server上
private boolean sendRegistrationIdToBackend(String regid) {
ArrayList<NameValuePair> paramList = new ArrayList<NameValuePair>();
try {
JSONObject statement = new JSONObject();
statement.put("action", "GcmReg");
statement.put("regid", regid);
paramList.add(new BasicNameValuePair("statement", statement.toString()));
String result = Util.sentHttpPost(URL_GCMREG, paramList, Util.getHttpClient());
if (result == null) {
return false;
} else {
JSONObject json = new JSONObject(result);
if (json.get("result").equals("ok")) {
return true;
} else {
return false;
}
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
|
Check point 8 : Android傳送訊息到Server上,廣播給其他使用者
Step 1 : 實作SendMessageTask
- 在SendMessageTask.java當中,修改URL_SENDMESSAGE參數為你的Server
private static final String URL_SENDMESSAGE = "http://[project-identifier].appspot.com/sendmsg";
|
- 在SendMessageTask.java當中送出Http Post到Server
@Override
protected Boolean doInBackground(Void... params) {
ArrayList<NameValuePair> paramList = new ArrayList<NameValuePair>();
try{
JSONObject statement = new JSONObject();
statement.put("action", "SendMessage");
statement.put("message", mMessage);
statement.put("member", mName);
paramList.add(new BasicNameValuePair("statement", statement.toString()));
String result = Util.sentHttpPost(URL_SENDMESSAGE, paramList, Util.getHttpClient());
if (result == null) {
return false;
} else {
JSONObject json = new JSONObject(result);
if(json.get("result").equals("ok")){
return true;
}else{
return false;
}
}
}catch(Exception e){
e.printStackTrace();
return false;
}
}
|
Step 2 : 送出SendMessage Http Post要求
- 在MainActivity中當使用者按下傳送按鈕後,將訊息透過SendMessageTask送出
protected void doSend(String name, String message) {
SendMessageTask task = new SendMessageTask(name, message, new OnSendMessageListener() {
@Override
public void onPostExecute(Boolean ok) {
try {
String msg;
if (ok) {
msg = "Message send";
} else {
msg = "ohhh! something wrong";
}
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
});
task.execute();
}
|
Check point 9 : 取得其他使用者的廣播內容
Step 1 : 接收與處理GCM訊息,並以LocalBroadcast廣播
- 在GcmIntentService.java中,當收到來自GCM的訊息時,將內容以LocalBroadcast廣播給MainActivity來更新訊息列表內容
GcmIntentService.java
...
if (intent.getStringExtra("statement") != null) {
String statement = URLDecoder.decode(intent.getStringExtra("statement"));
String timestamp = URLDecoder.decode(intent.getStringExtra("timestamp"));
try {
JSONObject castevent = new JSONObject(statement);
String event = castevent.getString("action");
if (event.equalsIgnoreCase("SendMessage")) {
String member = castevent.getString("member");
String message = castevent.getString("message");
sendLocalBroadcast(member, message);
Log.i(TAG,"Completed work @ " + SystemClock.elapsedRealtime());
sendNotification("Received: " + extras.toString());
Log.i(TAG, "Received: " + extras.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
|
Step 2 : 傳送LocalBroadcast
private void sendLocalBroadcast(String member, String message) {
Intent localIntent = new Intent("com.whilerain.chatroom.MESSAGE");
localIntent.putExtra("member", member);
localIntent.putExtra("message", message);
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
}
|
Step 3 : 實作BroadcastReceiver物件
- 在MainActivity中實作BroadcastReceiver,接收來自GcmIntentService的LocalBroadcast,並更新訊息列表
BroadcastReceiver mLocalBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String member = intent.getStringExtra("member");
String message = intent.getStringExtra("message");
ChatMessage msg = new ChatMessage(member, message);
mList.add(msg);
mListAdapter.notifyDataSetChanged();
}
};
|
Step 4 : 註冊與取消註冊BroadcastReceiver
- 在MainActivity.onCreate()中註冊BroadcastReceiver,
LocalBroadcastManager.getInstance(this).registerReceiver(mLocalBroadcastReceiver, new IntentFilter("com.whilerain.chatroom.MESSAGE"));
|
- 並在MainActivity.onDestory()中取消註冊
LocalBroadcastManager.getInstance(this).unregisterReceiver(mLocalBroadcastReceiver);
|
沒有留言:
張貼留言