ラベル android の投稿を表示しています。 すべての投稿を表示
ラベル android の投稿を表示しています。 すべての投稿を表示

2013年8月19日月曜日

GCM(Google Cloud Message) を PHP + PEAR Request2 + CodeIgniter で JSON で

タイトルの通り、CodeIgniter に PEAR の Request2 をインストールし、JSON形式でプッシュ送信を行う備忘録。

[前提条件]
  • CodeIgniter が使える
  • PEAR がインストールされている
  • Google より Project ID 取得済
  • Google より Server API Key 取得済
  • 通知したい端末の RegistrationID が取得済
[やる事]
  1. PEAR Request2 のインストール
  2. CodeIgniter へ PEAR Request2 のインストール
  3. プッシュ通知の実装(サンプル後述)
1.  PEAR Request2 のインストール

$ su
# pear install http_request2

PEAR Request でハマった。 これは非推奨となっているが、CodeIgniterでは使えない。
Request2 を使う。

2. CodeIgniter へ PEAR Request2 のインストール

2.1. PEAR用ディレクトリをCodeIgniterへ作成

色々サイトを見たが、 system/application/pear ディレクトリを作る、と良く見かける。これでまず少しハマる。
どうやら実際は "CIが配置された場所"/application/pear ディレクトリを作る。"system"の部分は"system"ディレクトリではない。アプリケーションのトップディレクトリ、というか、プロジェクトディレクトリの事を表している。
project dir/
    application/
        cache/
        config/
        controllers/
        core/
        helpers/
        hooks/
        language/
        libraries/
        logs/
        models/
        pear/
        third_party/
        views/

理解すると、他の場所にも移せると思うがとりあえず時間無いので確実に動く方法という事で。

$ cd <インストールされているディレクトリ>/application
$ mkdir pear

$ sudo chown apache pear

パーミッション変更は適宜。Mac OS X ユーザなら "apache"ではなく"_www" になると思う。
(apache を起動しているユーザが参照できれば良い)

2.2. PEARの一部を CodeIgniter へコピー

調べてみると、依存関係がどうのこうのとあったが、面倒なので、PEAR.php と HTTPディレクトリ、Net ディレクトリをコピーする。

pear を探す。
$ which pear
/usr/bin/pear
$
$ more /usr/bin/pear
#!/bin/sh
exec /usr/bin/php -C -d include_path=/usr/share/pear \
    -d output_buffering=1 /usr/share/pear/pearcmd.php "$@"
$

ちなみにこれは "CentOS release 6.4 (Final)" で実行した結果 /usr/share/pear にPEARがあった。Mac OS X(Mountain Lion) だと /usr/lib/php/pear
$ cd /usr/share/pear
$ cp -p PEAR.php /xxxxxxxx/application/pear
$ cp -pr HTTP /xxxxxxxx/application/pear
$ cp -pr Net /xxxxxxxx/application/pear

2.3. CodeIgniter 各種設定

/application/config/config.php
$config['enable_hooks'] = TRUE;

/application/config/hooks.php
$hook['pre_controller'][] = array(
        'class' => 'Pear_hook',
        'function' => 'index',
        'filename' => 'pear_hook.php',
        'filepath' => 'hooks'
);

include path の調整を行う為、ここで定数を用意しておく。
PEAR のファイルを CodeIgniter へコピーしたが、PEARにも同じファイルがあり、通常なら、元のPEARのへの include path が始めに参照されてしまう。
もし、CodeIgniter に配置した PEAR のファイルに手を入れるような事があった場合、混乱を防ぐ為、include path の順序を CodeIgniter 側の PEAR が先にくるように修正していく。

/web/index.php
define('TOPPATH', realpath(BASEPATH . '..') . '/');

/application/hooks/pear_hook.php
if (!defined('BASEPATH')) exit('No direct script access allowed');

class Pear_hook{
    function index(){
        // OS independent
        ini_set('include_path',TOPPATH.'application/pear/'.PATH_SEPARATOR.ini_get('include_path'));
        
        // on Apache
        // ini_set('include_path',ini_get('include_path').':'.BASEPATH.'application/pear/');
        // on Windows
        // ini_set('include_path',ini_get('include_path').';'.BASEPATH.'application/pear/');
    }
}



これで準備は整った。後は実装。
その前に、サーバから 443 ポートで外へ接続できるか確認しておく事。


$ curl https://www.google.co.jp

これで画面にHTMLが表示されれば、443ポートで外に出られるのが確認できる。

3. プッシュ通知の実装


class SampleController extends CI_Controller {
    /** Android 通知用プロジェクトID */
    const NOTIFY_ANDROID_PROJECT_ID = '6330938XXXXXX';
    /** Android プッシュ通知用サーバーAPIキー */
    const NOTIFY_ANDROID_SERVER_API_KEY = 'AIzaSyAc_IazhhxVfdCgaRsBesgXXXXXXXXXXXX';
    const NOTIFY_ANDROID_GCM_URL = 'https://android.googleapis.com/gcm/send';

    function _notifyAndroid($registrationId, $message) {
        if( !$registrationId ) {
            return FALSE;
        }

        $this->load->library('pearloader');
        $https_request = $this->pearloader->load('HTTP', 'Request2');
        $https_request->setUrl(SampleController::NOTIFY_ANDROID_GCM_URL);
        $https_request->setMethod(HTTP_Request2::METHOD_POST);
        $https_request->setConfig('ssl_verify_peer', false);
        $https_request->setHeader('Content-Type', 'application/json');
        $https_request->setHeader('Authorization', 'key=' . SampleController::NOTIFY_ANDROID_SERVER_API_KEY);

        // 端末レジストレーションID
        if(!is_array($registrationId)) {
            $registrationId = array($registrationId);
        }
        $data['registration_ids'] = $registrationId;
        // メッセージグループ
        //$message['collapse_key'] = '';
        // デバイスがアイドルではない場合すぐには送らない設定
        //$message['delay_while_idle'] = 'true';
        // デバイスがオフラインの時、メッセージを保持しておく時間(秒)
        $data['time_to_live'] = 600;
        // メッセージ内容
        $data['data'] = array('message' => $message);
        $https_request->setBody(json_encode($data));

        $response = $https_request->send();

        $resDecoded = json_decode($response->getBody());
        if( !$resDecoded->success ) {
            //Logger::error(__FILE__, __LINE__, 'GCM response' . $response->getBody());
            return FALSE;
        }
        return $resDecoded->success;
    }
}


見ての通り、"_"から始まるメソッド名なので、外から叩けない。
パラメータ"$registrationId" は、1つの文字列でも、配列に入った複数でも問題ない。
Registration ID は端末毎に取得し、サーバ側に何らかの状態で保持しておく必要がある。

一つ注意が必要な場所があった。
端末一つでのテストしかしていないので、40行目の success は複数端末を指定した場合、何になるか分からない。
ちなみに、端末一台にプッシュ通知を行った結果 Google サーバから返るJSONのサンプルは以下。
※ "success" : 1 ... のところが、複数端末へ実行した時何が返るか試してません。なのでサンプルコードの if文の所の調整が必要になるかもしれない。

{"multicast_id":772188850101XXXXXXXX,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1376887251914339%352f836XXXXXXXXXX"}]}


2012年2月9日木曜日

Android のログレベル

ログレベルについて軽く調べたところによると、特にマニュフェストファイルの
<application android:debuggable>
属性に連動しているわけではないという事がわかった。

知らないうちにLog.d()などを沢山埋めている場合、
<application android:debuggable="false">
にしたところで、勝手に抑止されるわけではないので、以下のようなクラスを用意すると良いだろう。

public class Log {

 private Log() {
 }

 public static void i(final String tag, final String msg) {
  i(tag, msg, null);
 }

 public static void i(final String tag, final String msg, final Throwable th) {
  // info は常に出す。
  if (null == th) {
   android.util.Log.i(tag, msg);
   
  } else {
   android.util.Log.i(tag, msg, th);
  }
 }

 public static void d(final String tag, final String msg) {
  d(tag, msg, null);
 }

 public static void d(final String tag, final String msg, final Throwable th) {
  if (!AppMan.DEBUG_MODE) {
   return;
  }
  if (null == th) {
   android.util.Log.d(tag, msg);
  } else {
   android.util.Log.d(tag, msg, th);
  }
 }

 public static void e(final String tag, final String msg) {
  e(tag, msg, null);
 }

 public static void e(final String tag, final String msg, final Throwable th) {
  // error レベルは常に出す
  if (null == th) {
   android.util.Log.e(tag, msg);
  } else {
   android.util.Log.e(tag, msg, th);
  }
 }

 public static void w(final String tag, final String msg) {
  w(tag, msg, null);
 }

 public static void w(final String tag, final String msg, final Throwable th) {
  // warning も常に出す事にする。
  if (null == th) {
   android.util.Log.w(tag, msg);
  } else {
   android.util.Log.w(tag, msg, th);
  }
 }

 public static void v(final String tag, final String msg) {
  v(tag, msg, null);
 }

 public static void v(final String tag, final String msg, final Throwable th) {
  if (!AppMan.DEBUG_MODE) {
   return;
  }
  if (null == th) {
   android.util.Log.v(tag, msg);
  } else {
   android.util.Log.v(tag, msg, th);
  }
 }
}


後は、各クラスでeclipseの ctrl + shift + o でimport定義を更新して、上記クラスを利用するようにすれば、簡単にマニュフェストファイルに連動して抑止する事ができる。

ただし、上記ソースを見て頂ければわかると思いますが、AppMan.DEBUG_MODE がマニュフェストファイルと連動していなくてはならない。
アプリケーション起動の際、この定数をマニュフェストファイルを参照して初期化する必要がある。

マニュフェストファイルの android:debuggable 属性を参照するコードは以下。

public class Utils {
 private Utils() {
 }

 public static boolean  isDebugAble(Context ctx){
  PackageManager manager = ctx.getPackageManager();
  ApplicationInfo appInfo = null;
  try {
   appInfo = manager.getApplicationInfo(ctx.getPackageName(), 0);
  } catch (NameNotFoundException e) {
   Log.e(e.toString());
   return false;
  }
  if((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == ApplicationInfo.FLAG_DEBUGGABLE) return true;
  return false;
 }
}

このソースは以下から貰いました。ありがとうございます。

http://www.taosoftware.co.jp/blog/2009/03/android_apireleasedebug.html

後は、アプリケーション起動時、一番最初に表示されるActivityなどで、独自のAppManクラスのDEBUG_MODE定数に上記ユーティリティの戻り値をセットすれば良い。

2010年11月17日水曜日

Activity クラスのコンストラクタ

Activityクラスのコンストラクタについてちょっとだけ調べてみた。
ただし、検証機は

xperia
Android 2.1

での話。

MAINのアクティビティが起動される際、そのアプリケーションについて1度でも起動した事があるとか、設定が完了していないと使う事ができないようなアプリケーションの場合、起動時に確認が必要になる。
Activityのライフサイクルではじめに呼ばれる

onCreate()

でチェックしても良いが、余計なオーバーヘッドをなるべく減らすために、onCreate() よりも呼ばれる頻度が低く、はじめに呼ばれるメソッドにチェックするコードを書きたいと思い、それは、、、結局もうコンストラクタしかない。

アプリケーションのMAINに指定してあるActivityのコンストラクタがどんなタイミングで呼ばれるか調べた。
検証した内容は

  • アプリケーション起動後、ホーム キーを押し、また起動
  • アプリケーション起動後、戻るキーを押して、アクティブじゃなくなった後、また起動
  • アプリケーション起動後、ホーム キー、電源ボタンを1度押し、サスペンド状態にして、再び起動
  • アプリケーション起動後、戻るキーを押して、アクティブじゃなくなった後、電源ボタンを1度押しサスペンド状態にして、再び起動
検証結果は、
  • 「戻る」で非アクティブした場合(その後サスペンドにしてもしなくても)、次回起動時、ほぼ必ずコンストラクタが呼ばれる。(今んとこ100%)
  • ホーム キーで非アクティブにした場合(その後サスペンドにしてもしなくても)、次回起動時はほぼ必ずコンストラクタが呼ばれない(今んとこ100%)
って結論になったよ。という話。

ただし、xperia、android 2.1 でしか試してないので、他のプラットホームでは、どうなるかは分からない。

おそらく、その時の空きメモリ容量などの都合によるかもしれないので、残りが少ない時は、毎回コンストラクタが呼ばれるかもしれない。

とりあえず、自分が使いたいと思った目的には合ってるから、これで良いかなという事で。

それからandorid を利用する際、なるべくメモリに残さないように非アクティブにしたいのであれば、ホーム キーより、戻るキーの方が良いみたい、って事がわかった。


2010年11月7日日曜日

Android ローカライズメモ

やりたい事

  • 指定のない場合の言語を英語にする
  • 端末の言語設定が日本語の場合、日本語のテキストを表示


やる事
  1. /res/values/strings.xml には英語用のテキストを書く
  2. /res/values-ja フォルダ作成し、strings.xmlファイルを作る
  3. ja のstrings.xml には、日本語のテキストを書く
これだけ。

端末の言語設定にかかわらずアプリ個別で言語を指定する方法はよくわからない。
このページ(Localization)を全部読んだらわかるかも知れない、、がとりあえず保留。

とりあえず、xperiaでは、端末の言語設定に従ってテキストが英語、日本語に切り替わるのは確認済み。

他言語と言語を表す2文字はここ(ISO-639-2_utf-8.txt)

ListPreference サンプル

今では色々サンプルが転がってますが、完結してなくて結局いくつかを見て回ることになるので記録を残しておこうかなと。

/res/xml フォルダを作成し、setting.xml(ファイル名は適宜)を作成



    
        
        
    


strings.xml に以下のように記述

設定
 
 選択
 設定を選びます。
 以下から選んでください。
 
 
        選択1
        選択2
    
    
        1
        2
    

javaサンプル

public class Main extends PreferenceActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
  
        addPreferencesFromResource(R.xml.setting);
    }
}