2013年3月31日 星期日

使用 PhoneGap 開發 Andorid APP

PhoneGap 是讓開發者,可以使用 HTML 和 JavaScript 開發跨平台 APP 的工具。雖然開發時,是使用 HTML 和 JavaScript 做出想要功能,但發佈成 APP 時,還是要透過原開發平台的工具才能分別產生各平台的 APP,或是上傳到 https://build.phonegap.com/ 官網產生各平台的 APP,只是從官網線上免費的話只能發佈一個APP。

這裡則是介紹,如何在原本的 Android APP 開發環境,開發使用 PhoneGap 的 Andriod APP。

步驟:
  1. http://phonegap.com/download/ 下載 PhoneGap,我下載的是 phonegap-2.5.0.zip
  2. 解縮壓備用,例如解壓縮到 D:\phonegap-2.5.0
  3. 在 Eclipse 新開一個 android 專案。此時目錄結構如下圖。
  4. 複製 D:\phonegap-2.5.0\lib\android\cordova-2.5.0.jar 到 android 專案的 libs 資料夾底下。
    在 libs 資料夾上按右鍵,選「Build Path」->「Configure Build Path...」開啟設定視窗。按「Add JARs」,選擇 cordova-2.5.0.jar 加進去。
  5. 在andriod 專案的 assets 目錄底下,建立 www 目錄,此目錄即是用來放網頁的地方,
    複製 D:\phonegap-2.5.0\lib\android\cordova-2.5.0.js 到 assets\www 目錄底下。
  6. 複製 D:\phonegap-2.5.0\lib\android\xml 資料夾 到 android 專案的 res 資料夾底下。
    進行到這裡,此時目錄結構應該如下圖。
  7. 修改 AndroidManifest.xml 檔。可比較 D:\phonegap-2.5.0\lib\android\example\AndroidManifest.xml 和 andriod 專案產生的 AndroidManifest.xml 檔案,進行以下修改。
    (註:19~34 行的 APP 權限設定,若開發的 APP 沒使用這麼多功能,沒用到的權限設定可以刪除)
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.mytest"
        android:versionCode="1"
        android:versionName="1.0" >
    
        <uses-sdk
            android:minSdkVersion="8"
            android:targetSdkVersion="17" />
    
        <supports-screens
            android:anyDensity="true"
            android:largeScreens="true"
            android:normalScreens="true"
            android:resizeable="true"
            android:smallScreens="true"
            android:xlargeScreens="true" />
    
        <uses-permission android:name="android.permission.CAMERA" />
        <uses-permission android:name="android.permission.VIBRATE" />
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
        <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.INTERNET" />
        <uses-permission android:name="android.permission.RECEIVE_SMS" />
        <uses-permission android:name="android.permission.RECORD_AUDIO" />
        <uses-permission android:name="android.permission.RECORD_VIDEO" />
        <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
        <uses-permission android:name="android.permission.READ_CONTACTS" />
        <uses-permission android:name="android.permission.WRITE_CONTACTS" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.GET_ACCOUNTS" />
        <uses-permission android:name="android.permission.BROADCAST_STICKY" />
    
        <application
            android:allowBackup="true"
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme" >
            <activity
                android:name="com.example.mytest.MainActivity"
                android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale"
                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>
    
    </manifest>
      
  8. 修改 MainActivity.java,讓程式執行時,是經由 PhoneGap 去執行 assets/www/ 底下的網頁。
    package com.example.mytest;
    
    import android.os.Bundle;
    import org.apache.cordova.*;//新增此行
    
    public class MainActivity extends DroidGap {// extends Activity 改成 extends DroidGap
    
     @Override
     public void onCreate(Bundle savedInstanceState) {// protected void 改成 public void
      super.onCreate(savedInstanceState);
      // setContentView(R.layout.activity_main);//沒用到,註解
      super.loadUrl("file:///android_asset/www/index.html");// 新增此行
     }
    }
    
  9. 此時,設定的部份已經告一段落。
    下面的範例,是用網頁寫一個簡單的彈出訊息視窗。
    在 android_asset/www/ 資料夾底下新增一個 index.html 檔,內容如下:
    第 7 行:引入 PhoneGap 的 JS 檔
    第 9 行:確保 PhoneGap 已準備就緒,才執行 function onDeviceReady(){} 的程式碼,不過此例中,沒用到。
    第 15~20 行:使用 PhoneGap API,彈出 Notification.alert 視窗。
    http://docs.phonegap.com/en/2.5.0/cordova_notification_notification.md.html#notification.alert
    <!DOCTYPE HTML>
    <html>
        <head>
        <meta charset="utf-8"/>
        <title>test</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <script type="text/javascript" charset="utf-8" src="cordova-2.5.0.js"></script>
        <script type="text/javascript">
        document.addEventListener("deviceready", onDeviceReady, false);
        function onDeviceReady() {
    
        }
    
        function showAlert() {
            navigator.notification.alert(
                '訊息...',
                function(){},
                '標題',
                '按鈕'
            );
        }
        </script>
      </head>
      <body>
        <p><a href="#" onclick="showAlert(); return false;">訊息視窗</a></p>
      </body>
    
    </html>
    
  10. 執行結果

備註:
  • 如果要畫面看起來更像一般的手機 APP,可以使用 jQuery Mobile 來做網頁,預設的一些樣式、效果, 看起來更符合手機。
  • 雖然用 PhoneGap 可以將網頁包成 APP,但 PhoneGap API 沒提供的手機功能,便要看有無其他人寫的 Plugin,不然就要自己寫了。
  • 某些情況,可能用HTML和JS達不到。例如我為了使用 GCM 推播訊息,找了Plugin https://github.com/marknutter/GCM-Cordova。我想在訊息來時有響鈴和震動,所以在 JS 寫了"navigator.notification.beep(2);" 和 "navigator.notification.vibrate(2000);",一般情況都正常,但重開機後,雖然有收到通知訊息(通知訊息我另外寫在JAVA裡),卻沒音效和震動。
    後來發現是掛在 GCMPlugin:sendJavascript  javascript:null 這裡,原來是,推播訊息來了,雖然Plugin的JAVA部份有正常執行,但要送給JS部份執行"navigator.notification.beep(2);" 和 "navigator.notification.vibrate(2000);"時,就失敗了。原因是,重開機後,APP沒有開啟,當然JS沒辦法執行。後來我的震動、響鈴,就都改寫到 JAVA 了。
  • PhoneGap 1.9 之後,可以使用 CordovaWebView 方式,取代 DroidGap 方式執行。
    但我看官方的CordovaWebView說明文件,似乎有點簡略,例如官方寫前前兩個步驟
    「1.Use bin/create to fetch the commons-codec-1.6.jar」
    「2.cd into /framework and run ant jar to build the cordova jar (it will create the .jar file in the form cordova-x.x.x.jar in the /framework folder)」
    看不到framework資料夾在哪,後來才發現是要到 http://cordova.apache.org/ 下載source code。
    而執行 bin/create.bat 要安裝 JDKAndroid SDKApache ant,JDK、Android SDK應該都有裝好了。Apache ant也不難裝,解壓縮,設環境變數即可
    怪的是
    bin/create.bat 少什麼東西,它不會詳細跟你說,每次都說少了 JDK、Android SDK、Apache ant 其中之一。但我明明都裝了,還是不行。只好看 bin/create.bat怎麼寫,原來是判斷有沒有 JAVA_HOME 環境變數,有沒有java.exe javac.exe ant.bat android.bat 4個指令。把這些執行檔路徑、環境變數設好,就可以編譯了。
    編譯完,才
    知道這兩個步驟只是要產生 cordova-2.5.0.jar、cordova-2.5.0.js、example...這些東西。
    後來又遇到一些怪問題,官網找不到,google後,雖然一一解決,簡單的APP也可跑,但看LogCat有出現一些錯誤訊息,暫時沒時間研究,所以目前我還是先用 DroidGap 的方式。

24 則留言:

  1. 針對備註所提到的GCMPlugin:sendJavascript javascript:null我也有遇過 發生的時機在已註冊後沒有執行window.GCM.register,造成gECB這個值是null,所以我增加一個方法window.GCM.foo,同時更新GCMPlugin.java,在已註冊時去執行另一個方法GCMPlugin.java execute foo,讓"gECB"這個值不會是null,所以當收到通知時,就不會有javascript:null的問題, PS: 也是使用phonegap 2.5

    回覆刪除
    回覆
    1. 您好,我沒看到 window.GCM.register 說
      是指 window.plugins.GCM.register 嗎 ?

      如果是指 window.plugins.GCM.register
      我研究的結果,似乎先執行 JS 的 window.plugins.GCM.register 後
      然後才會去執行 GCMPlugin.java 裡面的 GCMRegistrar.register,進行註冊。

      忽然想到,官網提供的 JS 範例裡面的 unregister,
      寫 window.GCM.unregister(GCM_Success, GCM_Fail);
      我之前測試,應該是錯的,沒辦法運作,應該改成
      window.plugins.GCM.unregister(GCM_Success, GCM_Fail);

      另外我正式使用時,phonegap 是使用跟官網一樣的版本 2.1

      刪除
  2. 對 是我打錯了:P
    我補充說明我的做法,在已註冊的情況時,
    if (!CheckDeviceRegistered()) //檢查是否已註冊
    {

    window.plugins.GCM.register(appID, "GCM_Event", GCM_Success, GCM_Fail);
    }
    else {
    window.plugins.GCM.GetReadyformessage("GCM_Event", GCM_Success, GCM_Fail); //自己加進去的程式
    }



    GCMPlugin.js

    GCM.prototype.getreadyformessage = function (eventCallback, successCallback, failureCallback) {

    return cordova.exec(successCallback, //Callback which will be called when directory listing is successful
    failureCallback, //Callback which will be called when directory listing encounters an error
    'GCMPlugin', //Telling Cordova that we want to run "DirectoryListing" Plugin
    'getreadyformessage', //Telling the plugin, which action we want to perform
    [{ ecb: eventCallback }]); //Passing a list of arguments to the plugin,
    };


    在GCMPlugin.java
    加入
    public static final String GETREADYFORMESSAGE="getreadyformessage";

    在execute中加入以下的判斷,由javascript呼叫時on起來
    else if (GETREADYFORMESSAGE.equals(action)) {
    try{

    JSONObject jsonObj=new JSONObject(data.toString().substring(1, data.toString().length()-1));

    gwebView = this.webView;
    gECB = (String)jsonObj.get("ecb"); //gECB就不會是null,也就不會造成 sendJavascript javascript:null

    result = true;
    }
    catch(JSONException e){


    }



    }

    依以下版本改寫
    https://github.com/marknutter/GCM-Cordova

    回覆刪除
    回覆
    1. 感謝分享,遇到類似情況時,就可以參考了。

      我也是有記錄目前是否已註冊(用html5 localStorage),
      判斷已註冊就不執行 window.plugins.GCM.register
      (不過我還另外做了藉由 server 返回的訊息,可以強制再重新執行 window.plugins.GCM.register)
      因為我不想使用者每次開啟 APP,就執行 window.plugins.GCM.register,感覺會多耗一點資源
      但我之前測試時,已註冊後,還是可以正常執行 window.plugins.GCM.register
      ,重覆註冊

      另外您第一篇提到"收到通知,javascript:null"的問題
      之前研究的結果,收到通知時,
      訊息的處理,似乎是執行 GCMIntentService.java 的 protected void onMessage
      處理成 json 後,
      再丟給 GCMPlugin.java 的 public static void sendJavascript
      好像跟目前有無註冊較無關聯
      可能我們的主程式細節有些差異,造成不同的結果。

      刪除
  3. 請問xyz版大,何時能造福

    寫個phonegap GCM推播教學呢?

    回覆刪除
    回覆
    1. 您好,
      其實當初有想要寫,但東西有點多、我有點懶 :p...就沒寫了
      我之前主要是參考這個網站
      http://devgirl.org/2012/10/25/tutorial-android-push-notifications-with-phonegap/
      您可以試看看,如果有問題,再討論囉

      刪除
  4. 再次請問一下

    我有個phonegap的專案 怎結合eclipse開啟的gcm 專案呢?

    gcm專案我是用google提出的範例 現在想把兩個結合在一起 請問版大xyz


    我該如何做呢?卡好幾天了=ˇ=

    回覆刪除
    回覆
    1. 您好,因內容蠻多的,我先大概分成三個部分,簡單說明如下,您先看看是哪部分卡住。

      1.在 google developers console 新增專案,並記下專案的數字ID「sender ID」(Project Number)
      APIS & AUTH->APIS,開啟 Google Cloud Messaging for Android
      APIS & AUTH->Credentials->Create new key->取得Key for server applications裡面的「API key」
      「sender ID」用在手機端程式,可用來註冊這隻手機已經安裝此APP
      「API key」用在server端程式,用來將訊息發給有註冊的手機
      http://developer.android.com/google/gcm/gs.html

      2.手機程式(client端、APP)
      因為用PhoneGap,所以要用 PhoneGap 的 GCM plugin
      PhoneGap plugin for Google C2DM (GCM-Cordova)
      https://github.com/marknutter/GCM-Cordova
      第一步驟的「sender ID」,填在 assets\www\CORDOVA_GCM_script.js (第36行)
      window.plugins.GCM.register("sender ID", "GCM_Event", GCM_Success, GCM_Fail );

      這步驟正常的話,應該可以送出這隻手機的「registration ID」,
      「registration ID」要送到 server 端。

      3.server端程式
      將接收到的「registration ID」,記錄下來(例如存到資料庫),這樣就可以知道哪些手機有安裝此APP。
      要推播訊息的時候,就撈出存在資料庫的所有「registration ID」發送(我自己怕有容量限制,所以是分批發送)。
      正常的話,手機上面的 APP 就可以接收到訊息。

      刪除
    2. xyz版大 我下載了https://github.com/marknutter/GCM-Cordova

      用eclipse 匯入

      發現src 的 com.plugin.GCM 內的 GCMPlugin.java 內的 import有錯
      import org.apache.cordova.api.CallbackContext;
      import org.apache.cordova.api.CordovaPlugin;
      我改成
      import org.apache.cordova.CordovaPlugin;
      import org.apache.cordova.CallbackContext;

      import org.apache.cordova.*;
      import org.apache.cordova.api.*;

      底下繼承的public class GCMPlugin extends CordovaPlugin 都有錯

      刪除
    3. 如圖~

      https://drive.google.com/file/d/0B0_P7nGYhft_RW1SdzhEWkRjVUE/edit?usp=sharing

      刪除
    4. 您好,圖片的分享權限沒開,看不到喔

      刪除
  5. 抱歉XYZ大

    因為高雄氣爆比較晚回(剛好住附近=ˇ=)

    以上的問題已經解決的 Client端已經處理完畢

    非常感謝

    另外請問一下

    關於Server端

    我用 gcm-demo-appengine 去發送消息

    有辦法可以更改預設的訊息嗎

    都收到From GCM:you got message!

    回覆刪除
    回覆
    1. 沒關係,安全比較重要。
      gcm-demo-appengine 我沒用過,不過我看 "From GCM: you got message" 出現在
      https://code.google.com/p/gcm/source/browse/samples/gcm-demo-client/res/values/strings.xml
      但這好像是 client 端,感覺不像是 server 送出的訊息內容,不過我沒很確定。

      我之前是使用 GCM HTTP Connection Server
      http://developer.android.com/google/gcm/http.html

      server 端依照規定的資料格式 POST 給 https://android.googleapis.com/gcm/send 即可。
      PHP 的範例,大概如下
      ----------
      define("GOOGLE_API_KEY", "專案的API KEY"); //API KEY

      //registration ID
      $registrationIDs = array();
      $registrationIDs[] = '要接收訊息的手機 registration ID 1';
      $registrationIDs[] = '要接收訊息的手機 registration ID 2';

      //要推播的訊息
      $payload = array('title' => '標題', 'msg' => '推播訊息內容');
      //titile、msg 這兩個名稱可以隨便設,因為是 client 自己要解析的
      //像 PhoneGap plugin for Google C2DM (GCM-Cordova) 是用 message、msgcnt
      //https://github.com/marknutter/GCM-Cordova/blob/master/src/com/cordova2/gcm/GCMIntentService.java
      //https://github.com/marknutter/GCM-Cordova/blob/master/assets/www/CORDOVA_GCM_script.js

      //要 POST 給 https://android.googleapis.com/gcm/send 的資料
      $headers = array('Authorization: key=' . GOOGLE_API_KEY, 'Content-Type: application/json'); //HTTP header
      $fields = array('registration_ids' => $registrationIDs, 'data' => $payload); // HTTP body
      //使用 curl POST 資料
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, 'https://android.googleapis.com/gcm/send');
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //HTTP header
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true );
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields)); //HTTP body 傳送時轉成 JSON 格式
      $result = curl_exec($ch);
      curl_close($ch);
      ----------

      POST 到 https://android.googleapis.com/gcm/send 後
      google 那邊也會將每一個Registration ID 的發送結果,以 JSON 格式回傳
      http://developer.android.com/google/gcm/http.html#response
      然後便可以視需要做處理
      例如回傳訊息中,某一個 Registration ID 是 NotRegistered,
      表示手機未註冊,可能使用者移除 APP 了,那我會把這個 Registration ID 在 server 端移除。

      刪除
    2. 感謝XYZ版大 已經都解決嚕
      除了震動和鈴聲外....
      還有點擊推播訊息要打開該應用程式 也還沒完成XD

      刪除
  6. 請問xyz大大 不好意思又來麻煩您

    安裝app後的regIds gcm demo都存在 local的gae 只要重新啟動server 裡面的DB就會被清空

    請問您怎解決的呢?

    回覆刪除
    回覆
    1. 您好,我沒用過 GAE (Google App Engine?)
      如果是指 Registration ID 儲存的問題,我是在 APP 向 Google GCM 註冊成功後,再將 Registration ID 傳送到其他 Server 儲存,Server 上則是用 MySQL 儲存 (不知這不是您要問的問題)。
      另外,Server 將 Registration ID 儲存成功後,我會在 APP 記錄已完成註冊,因為是用 PhoneGap,所以我直接用 HTML5 的 localStorage 記錄。或是也可以用 Android 的 SharedPreferences 來記錄。下次啟動 APP 時,如果記錄中,是尚未註冊成功,我才會再執行一次註冊的程序。

      刪除
  7. 非常感謝您 那我試試看 用jdbc寫到mssql好了 :D

    回覆刪除
  8. 目前都成功了 非常感謝:D

    只是現在一個頭兩個大

    用phonegap 點了推播訊息不知道該怎樣打開那樣消息

    只知道該點了要執行哪個class..

    回覆刪除
    回覆
    1. 我是用 android 的 Notification 接收訊息,上方狀態列出現 Notification 後,往下滑就直接顯示訊息內容。點擊訊息內容,就開 APP。
      可參考 http://nkeegamedev.blogspot.tw/2013/02/gcm-gcm2.html 裡面的第4點。

      刪除
  9. xyz大大
    我一樣用eclipse 匯入

    發現src 的 com.plugin.GCM 內的 GCMPlugin.java 內的 import有錯
    import org.apache.cordova.api.CallbackContext;
    import org.apache.cordova.api.CordovaPlugin;

    請問大大該如何解決!?

    回覆刪除
    回覆
    1. 可能是沒加入 cordova-2.5.0.jar,可先檢查 cordova-2.5.0.jar 是否有加到 libs 裡

      刪除
  10. XYZ版大,你好

    我是PhoneGap的初學者,對於整合推播的元件,一直測不出來
    比如說我依DEMO的方式,在index.html
    也有載入了 CORDOVA_GCM_script.js
    可是在addEventListener中,似乎沒有執行到
    就如您文中所提到的navigator.notification.alert
    也是沒有反應,我實在沒有其它的資源可以詢問
    我可以提供我目前測試的文件,
    可否麻煩你指點迷津呢
    打擾之處,還請見諒

    謝謝

    回覆刪除
    回覆
    1. 您好,因為我已許久未碰PhoneGap,所以不確定是否跟版本差異有關,先提供幾點您先測試看看,若依然不能解決,歡迎再討論。

      1.本文的demo,僅是單純使用PhoneGap顯示一個navigator.notification.alert訊息,並未整合推播功能。所以您若navigator.notification.alert亦有問題,可以先不要整合推播的功能,測試最單純navigator.notification.alert是否能正常執行。以釐清是哪部分有問題。

      2.如果第一點的PhoneGap基本執行沒問題,再測試推播的部分。
      下載整合的PhoneGap推播元件測試
      https://github.com/marknutter/GCM-Cordova
      直接用 eclipse 開啟下載後的專案,然後改 CORDOVA_GCM_script.js,
      將裡面 window.plugins.GCM.register(...) 第一個 senderId 參數,改成您申請的 GCM senderId。
      再來要找出你手機的 registration id,
      可以參考
      http://devgirl.org/2012/10/25/tutorial-android-push-notifications-with-phonegap/
      裡面 "You should also see the following trace in your console",底下那一段,
      在 eclipse 觀察執行過程輸出的訊息,找到手機的 registration id 複製下來。
      再來測試推播功能前,
      您要先另外用您會的server端語言,寫一個發送訊息的程式(之後要放在server端),
      寫好 server 端發訊息的程式後,將訊息發送到剛剛的手機 registration id,沒問題的話,該手機即能收到推播訊息。如果手機沒收到,這時可觀察server 端程式發送訊息後,接收到的返回訊息,裡面應該會有發送失敗之類的原因,便可針對失敗原因加以排除。

      刪除
    2. 謝謝,我再試試,如有問題,再向您請教

      刪除