2014年12月26日金曜日

【ECキューブ】あらかじめの処理を伴うページやブロックを追加する

ECキューブのディフォルトのページには、ページのクラスとその拡張クラスとが紐付いていて、いろいろPHPで複雑なことが出来る。

大雑把に言って4つのPHPファイルがワンセットになってる。
■HTML側のPHP(URLに対応するもの)
 例:products/list.php
■テンプレートファイル
 例:data/Smarty/templates/default/products/list.tpl
■DATA側、ECキューブ標準の処理の書かれたクラスファイル
 例:data/class/pages/products/LC_Page_Products_List.php
■DATA側、ユーザーが拡張処理を書くための拡張クラスファイル
 例:data/class_extends/page_extends/products/LC_Page_Products_List_Ex.php

これらのファイルがあらかじめいろいろな処理をして、テンプレートで使える変数や配列を作っている。
管理画面から新規に作成したページで、他のページが使っているECキューブの変数が使えないのは、ECキューブ標準ページはこれらのファイルによってあらかじめ処理を経てきているから。
なので、新規追加ページでECキューブのいろいろな変数を使いたい場合、上記のクラスファイルや拡張クラスファイルを同様に追加して、処理を書いておかないといけない。


で、
そういう感じでPHPで色々なことを仕込んだ、ブロックも作りたい。

【方法】
管理画面からブロックを新規作成。

\public_html\frontparts\bloc に、block_任意の名前.php というファイル(※)を作成。
\public_html\data\class_extends\page_extends\frontparts\bloc に、LC_Page_FrontParts_Bloc_任意の名前_Ex.php という拡張クラスファイルを作成。その処理の内容は、同ディレクトリにある他のクラスを参考に。
このクラスファイルを、このHTMLディレクトリ側のPHPから呼び出す。

データベース管理画面に入り、「dtb_blocテーブル」を開く。
追加したブロックの「php_path」コラムに、「frontparts/bloc/block_任意の名前.php」と入力。(※)で作成したファイル。

LC_Page_FrontParts_Bloc_任意の名前_Ex.phpに、処理を書く!

2014年12月24日水曜日

【JavaScript】ポップアップで開いたウインドウを閉じた際、それを開かせた親のウインドウで何かする

情報入力をさせるWebページで、追加情報の入力のためにポップアップウィンドウを追加で開かせて、そこでの入力が完了してポップアップを閉じた際、開かせた親ページで表示更新なんかをさせたい場合。

window.openerというJavaScriptでのオブジェクトで、「自分を開かせた親のウィンドウ」を取得できる。
親のウィンドウ側のページにJavaScript関数(例えば「updateWhenRegistered()」みたいな名前で)を作っておいて、子ウィンドウの閉じるボタンあたりに


 onClick="javascript:window.close(); window.opener.updateWhenRegistered();"



というコードを仕込んでおけば、子ウィンドウから親ウィンドウのJavaScriptを実行できる。

2014年12月2日火曜日

データベース検索メモ NULLに注意

あるコラムを検索条件に入れるとして、そのコラムがNULLのとき、検索結果は問答無用でfaiseになってしまう。

NULLを取得するためには、「IS NULL」を使う。「col_a == null」とかでは拾えない。

2014年11月27日木曜日

【Android】アカウントマネジャーから取得するアカウント情報はGoogleのもの以外のもの含むらしい

--------------------------------------------

//アカウントの一覧を取得
AccountManager account_manager = AccountManager.get(getApplicationContext());
Account[] google_accounts = account_manager.getAccounts();

for(Account google_account : google_accounts){
    if(google_account.name.matches(".*@gmail.com")){
        System.out.println("gmail : " + google_account.name);
    }else{
        System.out.println("gmailではない : " + google_account.name);
    }//if
}//for
--------------------------------------------

Androidで、上記のような処理によって、端末からアカウント情報の一覧を取得するとする。
配列「google_accounts」には、Googleのアカウント(@gmail.com)だけが入るかと思ったら、実はそうでもない。

「SkyDrive」「au ID」のような文字列もまじる。

グーグルアカウントだけを抜き出したい場合は注意する。

--------------
追記:
getAccountsByType("com.google")
というメソッドを使えば Googleのアカウントだけ取れるらしい。

2014年11月26日水曜日

【ECキューブ】ECcube雑多メモ

仕事でECcubeを触ることになったのでいくつか細かいことをメモ。
前職でもECキューブは多少触ったのですが今の仕事ではよりプログラミング的な場所を扱っています。

【テンプレートで、配列のサイズを表示する】
<!--{配列|@count}-->
モディファイラ「count」を使い、さらに、その頭に「@」を付ける。


【独自プラグインを作る】
 \data\module\Smarty\libs\plugins のディレクトリに、「function.任意の関数名.php」の名前でファイルを作る。
ファイルの中身は、

-----------------------------
function smarty_function_任意の名前($params, &$smarty){
    //任意の処理
     return 任意の値;
}//function
-----------------------------
こういう感じ。

サンプルプラグインをGitHubにおいてみました。
https://github.com/ishiitakeru/Eccube_plugins/blob/master/function.dump.php


【プラグイン側からテンプレート変数を作る】
$smarty->assign("変数名", 値);

のように書くと、テンプレート側で
<!--{$変数名}-->
という変数になる


【繰り返し処理】
for($i = 0; $i<5; $i++){}//for

の書式をECキューブで表現する。

繰り返しにはsectionタグを使う
<!--{section name="count" loop=4 start=0 step=1 }-->
<!--{/section}-->

<!--{section
    name = "識別子" ループ内で「<!--{配列名[識別子]}-->」と書くと、ループ中のインデックス番号が入る。$は不要。クオーテーション忘れずに。単独では出力できず、配列のインデックスとして使う。
    start= スタート番号
    loop = ループの終わりインデックス番号(または配列のテンプレート変数)
    step = インデックス番号の増分
}-->


ループ回数のインデックス番号は「<!--{$smarty.section.name属性値.index}-->」となる。
<!--{section
    name="CheckedItems"
    loop=10
}-->
    <p><!--{$smarty.section.CheckedItems.index}--></p>
<!--{/section}-->


【データベースをいじらないと使えない変数】
ECキューブが使ってるのに自分が書くと使えない、ECキューブの関数について。
こういうやつ
■<!--{$arrCartList}-->
■<!--{$tpl_login}-->

どうもこれらを使うには、データベースをいじらないといけないらしい。
該当テーブル「dtb_bloc」
該当コラム「php_path」


【$arrTree】
商品カテゴリの連想配列。
$arrTree を使うには frontparts/bloc/category.php がdtb_blocテーブルでこのブロックに関連付けられている必要がある。

$arrTree[0].level
$arrTree[0].category_id
$arrTree[0].category_name|h
$arrTree[0].product_count|default:0
みたいにつかう。


【クッキーの使い方】
<!--{$smarty.cookies.クッキー名}-->

【配列】
<!--{$配列名[インデックス番号]}-->

【連想配列】
<!--{$連想配列名.キー名}-->

【HOMEのURL】
<!--{$smarty.const.TOP_URL}-->

【選択中のデザインテンプレートのディレクトリ】
<!--{$TPL_URLPATH}-->
「/user_data/packages/default/」みたいな文字列

【サイトのルートディレクトリ】
<!--{$smarty.const.ROOT_URLPATH}-->
「/」みたいな文字列

【商品画像の保存パス】
<!--{$smarty.const.IMAGE_SAVE_URLPATH}-->

【商品の詳細ページ】
<!--{$smarty.const.P_DETAIL_URLPATH}-->

【ログイン状態を調べる】
ログイン状態を調べるプラグインを作成しました。
ログインブロックなどが使っている変数はデータベースをいじらないと他のブロックで使えないので。
https://github.com/ishiitakeru/Eccube_plugins/blob/master/function.getter_login_condition.php

2014年11月18日火曜日

【Android】Google Analyticsが動かない → analytics.setDryRun(true); に注意

開発中のアプリ、数カ月前に一度問題なく実装していたGoogle Analyticsのロジックを修正しないといけなくなり、久しぶりに触る。

なんと、トラッキングされない。
色々修正したり動いていた頃のコードを見なおしたりしてみても原因が判らず、半日費やしてしまった。

原因がわかった。

【Dry Run フラグ】
この SDK に備わっている dryRun フラグをセットすると、Google アナリティクスにデータが送られなくなります。
https://developers.google.com/analytics/devguides/collection/android/v4/advanced?hl=ja

コードを見ると
analytics.setDryRun(true);

自分でトラッキング無効の設定にしていたのだ!!!

AndroidのGoogle Analyticsが、コードは正しいはずなのにトラッキングが取れないという場合、ここに注意しましょうねという話でした。

2014年11月7日金曜日

【Android】アセットフォルダの中身を端末にコピーする

assetsフォルダの中身を端末にコピーして使うという技がよく登場する。
たとえばCで書かれたライブラリを使わないといけない場合など。AndroidNDKを使っての連携とかごちゃごちゃややこしいことをやらされる。
Cで書かれたコードからはアセットフォルダの中身にアクセス出来ないらしい。だから端末にコピーしたファイルのパスを渡してやる必要が生じる。

ややこしいけど、以下のふたつは別物と考える。
■アセットフォルダの中身(開発している時ローカルにファイルがある)
■端末にコピーされたファイル(インストール後、端末の保存領域に登場する=ローカルの時は存在しない)

WebViewなんかで読み込ませる時、上記ふたつはURLが違う。

アセットフォルダ内
    file:///android_asset/ファイル名
     →これは多分、AndroidにインストールされたAPKファイルの中に存在する。

端末にコピーしたもの
    file:///data/data/パッケージ名/files/ファイル名
    ※URLでなくファイルパスで言うなら「/data/data/パッケージ名/files/ファイル名」



今回のメモの中心の話題。
********
アセットフォルダの中身を、ディレクトリ構造を維持したまま、端末の 「/data/data/パッケージ名/files/」以下にコピーしたい。
********

要点だけいうと、
■ディレクトリだけあらかじめ端末内に作っておく。
■端末へのファイル出力は「/data/data/パッケージ名/files/」直下にしか出来ない。
■ファイルを書き出した直後に、 ここにあってほしいというディレクトリに、書き出したファイルを移動させる。

そのままコピペで使えるソースじゃなくて申し訳ないが、ファイル書き出し直後に次のようにして移動の処理を作ったらうまく行った。
***********
/////////////////////////////////////////
//書き出したファイルを適切なディレクトリに移動
File file_in_old_place    = new File(書き出したばかりのファイルの端末内ファイルパス);
//望ましい保存場所
File file_in_proper_place = new File(ファイルの、そこにあってほしいという端末内ファイルパス);

//リネーム(ディレクトリ変更が出来るメソッド)
file_in_old_place.renameTo(file_in_proper_place);
***********


1メガを超えるファイルを端末内にコピーしたい時、ZIPにしておいて解凍するというテクがあるけど、これにも応用できた。
■アセットフォルダ内に大きなZIPを置いておく。1ファイルで1zip。
■「/data/data/パッケージ名/files/」直下に解凍。
■移動先ディレクトリを作っておく。
■解凍されたファイルをrenameTo()で移動。


【Android】アンドロイドアプリのランチャーアイコン入れ替え

アンドロイドアプリのランチャーアイコンは、リソースの画像フォルダ内の「ic_launcher.png」を上書きすることで入れ替える。
複数のフォルダに入っているので注意が必要。それぞれリサイズして適切な大きさにして保存する。

drawable-xhdpi 96x96
drawable-hdpi  72x72
drawable-mdpi  48x48
drawable-ldpi  36x36

参考
https://sites.google.com/site/startandroidprograming/3-android-sdk-tool/ranchaaikonwo-bian-gengsuru

2014年11月6日木曜日

【Android】「Android NDK でjni下のソースをコンパイル」ってなんだ?

仕事で使おうとしているライブラリのインストールマニュアルを読んでいて、こういうのにぶつかった。

Android NDK でjni下のソースをコンパイルしてください。



この項目のところで詰まった。
Android NDK でjni下のソースをコンパイルってなんだ?

うまく行ったらしいのでやったことをメモ。

AndroidNDKのインストール:
AndroidSDKのインストールされているフォルダに、AndroidNDKのファイルをひと通りダウンロード&解凍済み。
AndroidNDKはまずファイルを一揃いダウンロードして、然るべきディレクトリに解凍することろから。
http://developer.android.com/tools/sdk/ndk/index.html

こういう階層になっていた。「ndk」というのは多分解凍フォルダをリネームしたもの。
C:\Users\takeru\AppData\Local\Android\android-sdk\ndk

環境変数のPathにNDKインストールディレクトリを追加
コントロール パネル\すべてのコントロール パネル項目\システム
システムの詳細設定>環境変数
Pathの項目に
C:\Users\takeru\AppData\Local\Android\android-sdk\ndk;
を追加。「;」が区切り文字

「jni」フォルダが鍵。
■CやC++で書かれたネイティブのソースがここに入っている
■コンパイルの際にコンパイル命令の詳細を定義する「Android.mk」「Application.mk」が入っている。
■コンパイルのコマンドである「ndk-build」は、ウィンドウズのコマンドプロンプトから実行するもの。
■コンパイルのコマンドである「ndk-build」は、この「jni」ディレクトリまでcdコマンドで移動してから、ここで行う。

つまり、CやC++で書いたソースをAndroidで使いたい場合、
■Androidプロジェクト内に「jni」ディレクトリを作成
■「jni」ディレクトリ内に「Android.mk」「Application.mk」を作成(内容の書き方は詳しくはわからないけど)
■AndroidNDKをインストールし、環境変数のPathにNDKディレクトリを通して「ndk-build」コマンドを使用可能にし、「jni」ディレクトリにて実行
→Androidと連携できるようにコンパイルされるということらしい。

【重要】
コマンドプロンプトで「jni」ディレクトリまで現在位置を移動してから「ndk-build」コマンドを実行すること。
CドライブからDドライブに移動するには「cd /d D:」


----------
基礎知識
----------
Android NDKとは?
    Android Native Development Kitの略称である。
    Androidアプリケーションの一部または全部をC/C++言語で開発するためのキットです。
----------
JNI とは
    Java ネイティブインターフェイス ( Java Native Interface ) のこと。
    Java プログラミング言語で書かれたコードに対し、C や C++ で書かれた関数があるネイティブコードと相互にやり取りする手段を定義するもの。
----------

2014年10月31日金曜日

メモ:報告メッセージをストックしておくクラス

実行結果やエラー結果を、処理の完了後に報告できるようにしたい場合。
各種報告メッセージを格納していくカスタムクラスを作ればどうか。
するとフォーマットが常に一定になるのでわかりやすそう。

class ResultReport{
    private $type; //種別
    private $result; //成功失敗フラグ
    private $input; //インプット内容
    private $output; //アウトプット結果
    private $massage; //報告文(エラーの場合はその詳細原因とか)

    //各種ゲッター

    //各種セッター
} //class


みたいな。

2014年10月25日土曜日

【Android】アニメーション終了を検知する

アプリの起動時に表示されるスプラッシュ画面なんかをフェイドアウトなどのアニメーションをしてから遷移させたい場合。
アニメーションの終了を検知するリスナーメソッドがあるのでそれを使うといい。

■該当のフラグメントクラスに AnimationListener を implements させる。
■必須メソッドを実装する。
 ◆onAnimationStart(Animation animation)
 ◆onAnimationRepeat(Animation animation)
 ◆onAnimationEnd(Animation animation) →終了時の動作をここに書く
■該当のアニメーションインスタンスに .setAnimationListener(this) とする。
 ここでの引数は「AnimationListener を implements したクラスのインスタンス=自分自身のフラグメントクラスのインスタンス」

こういう感じ。


-----------
//ImageViewのオブジェクト取得
ImageView splash02 = (ImageView)rootView.findViewById(R.id.splash02_animation_imageview);

//アニメーションで使う、フェードアウトにかかる時間(ミリ秒)
int duration_animation = 1*1000;

//複合アニメーション作成
//【1】インスタンスを生成
AnimationSet animation_set = new AnimationSet(true);
splash02_container = (ViewGroup) getActivity().findViewById(R.id.splash02_container);

//【2】基本のアニメーションを生成
//フェードアウト
AlphaAnimation fade_out_animation = new AlphaAnimation(1.0f, 0.0f);
//拡大
ScaleAnimation expand_out_animation = new ScaleAnimation(
 1.0f,
 20.0f,
 1.0f,
 20.0f,
 (splash02.getWidth())/2,
 (splash02.getHeight())/2
);

//【3】生成したアニメーションを追加
animation_set.addAnimation(fade_out_animation);
animation_set.addAnimation(expand_out_animation);

//アニメーションを適用
animation_set.setDuration(duration_animation);

//アニメーション終了感知リスナーを登録 onAnimationEnd()メソッドが実行される
animation_set.setAnimationListener(this);

//アニメーションをスタート
splash02_container.startAnimation(animation_set);

-----------

2014年10月23日木曜日

【concrete5】toolsのPHPのURL

concrete5でAjaxを使いたい場合、toolsディレクトリに入れたPHPだったら、
■URLを持てる
■concrete5の定数やメソッドが使える
■ヘッダーとか背景とかの余計なレンダリングはされない
というふうになる。

ただ、URLがわかりにくい。

パッケージ内のPHPのURL
/index.php/tools/packages/パッケージ名/PHPファイルの拡張子を取った名前


確認の仕方
■パッケージ名:my_package
■PHPファイル名:my_test.php
■つまり「/packages/my_package/tools/my_test.php」の場合

// get files called "my_test" located in /packages/my_package/tools/
echo Loader::helper('concrete/urls')->getToolsURL('my_test', 'my_package');
こうなる。

【concrete5】データベース使用時にプリペアステートメントを使う

こんなかんじで書いた。

////////////////////
//データベースインスタンス
$db = Loader::db();


//プリペアステートメントの使用
$prepare = $db->Prepare("
    SELECT COUNT(*)
    FROM  Users
    WHERE uName = ?
");

$values = array("admin");
$search_result = $db->GetOne($prepare, $values);

2014年10月8日水曜日

【Android】.setVisibility(View.INVISIBLE) と .setVisibility(View.GONE) との違い

ビュー要素を見えなくしたい時、以下のようなコードを書く。

◆ビュー要素.setVisibility(View.INVISIBLE)
◆ビュー要素.setVisibility(View.GONE)

ふたつの違いは、「その要素が入るべきスペースが詰められるかそうでないか」である。
◆ビュー要素.setVisibility(View.INVISIBLE)
→見えなくなるだけで場所は占領したまま。

◆ビュー要素.setVisibility(View.GONE)
→見えなくなるし、存在自体が消え去ったようにスペースも詰められる。

2014年9月30日火曜日

備忘録 : Linuxのコマンド(Apacheの再起動)、phpからLinuxのコマンドを実行する方法

PHPの案件をやっていると、どうしても、サーバーの設定に悩まされ続けることになる。

覚悟を決めてサーバーに接続してややこしい設定に挑むことがある。
サーバーへの接続には、接続のためのソフトを使う。私の会社PCでは、PuTTYというソフトを使っているみたい。Telnetとかターミナルとか呼ばれるやつと同系統なのかな?
開発の会社で、サーバーに詳しい人が建てた会社のサーバーなら、上記のソフトで接続して設定をいじれるけれど、レンタルサーバーなんかでは多分出来ない。

メモしておきたいのは以下の一行(サーバーのApacheを再起動する命令)

apachectl restart

これもついでにメモっておこうかな。
ターミナルでテキストファイルを編集する時、「vi」というテキスト編集ソフトでファイルを開くんだけど、そのコマンド。

コマンドモードと挿入モードとの切り替え
  コマンドモード→挿入モード: a i o のどれか
  挿入モード→コマンドモード: Escキー
ファイルの保存、終了: :wq
保存せずに強制終了: :q!


また、Linuxのコマンドは、PHPスクリプトから実行できる。

exec("Linuxコマンドの文字列");

ただし、実行されるのがPHPの命令ではなくLinuxの命令なので、いくつか注意点がある。
■戻り値がきれいに取れない
■Linuxの命令の実行は別スレッドでの非同期処理になるらしい

つまり、 Linuxで巨大なファイルを複製するとして、そのコマンドをPHPから実行した直後に、PHPの処理でその複製されたファイルにアクセスしようとしても、そのファイルはまだ存在してない可能性があるということみたいだ(PHPはLinuxコマンドの実行完了を待ってから次の処理に行くわけではないらしい)。

2014年9月26日金曜日

【TortoiseSVN】DAV request failed: 411 Content length required. のエラー

現在やっている案件、ファイルのバージョン管理システムにサブバージョンを使っています。
 
サブヴァージョンを使ってのコミットが簡単そうなんですがうまくいきませんでした。
「TortoiseSVN」というクライアントを使っていました。

苦しんだエラーメッセージ:
Error: Commit failed (details follow):  
Error: DAV request failed: 411 Content length required. The server or an intermediate  
Error:  proxy does not accept chunked encoding. Try setting 'http-chunked-requests' to  
Error:  'auto' or 'no' in your client configuration. 
 
 
解決した時にやったこと:
ウィンドウズのエクスプローラーで、サブバージョンと紐付いたフォルダにおいて右クリック
→TortoiseSVN
→Settings
→Network
→Subversion server file : Edit
→「servers」というテキストファイルが開く
→「
http-chunked-requests = no
」という行を追加して保存。

サーバー関係の設定ファイルをいじるようなノリのことをやったらうまく行ったという話でした。

【PHP】wgetを使ってログイン後のページのHTMLを文字列として取得したい

PHPで次のようなことがやりたい。

■ブログの記事のように動的に生成されているページを静的HTMLファイルとして保存する。

■ダウンロードするのではなくサーバー上にHTMLファイルを複製する。

■該当のページは、システムにログインしている状態でアクセスした場合とログインしていない状態でアクセスした場合とで表示内容が異なるが、ログインした状態での表示内容を取得したい。

■サーバーの環境的にcURLの機能が使えない。

■複製するHTMLファイルは、リンクや画像へのパスなど、内容を一部書き換える。

---------------------------------------------

実装方針は以下のようになる。

■対象のURLに、ブラウザでアクセスするようなノリでプログラムからアクセスし、レンダリングされたHTMLを取得する。
 つまり、CMS(コンテンツマネジメントシステム=ワードプレスなどようなコンテンツ作成、管理システム / 今回はconcrete5)の動作を解析して、「このブロックはこのデータベーステーブルのこのコラムから値をとって」みたいな内部処理を再現するのではなく、システムの外側からアクセスした時の表示内容を取得する。

■「システムにログインする」というのをPHPプログラムで再現しないといけない。
 URLを読み込むだけならPHPの「file()」や「file_get_contents()」を使えばいいのだが、それらだとログインの挙動を再現できない。
 検索するとよく出てくるのはcURLを使っての実装だが(例: http://web-prog.com/php/curl-login-scraiping/ )、今回の環境ではこれは使えないということになった。
 そこで代わりに使うのは、Linuxのコマンドである「wget」をPHPの「exec()」メソッドで実行させるという方法である。

■wgetコマンドは対象のURLの内容を変数に取得するものではなくファイルに出力するものなので、出力されたファイルをさらに読み込みにいって変数に格納する。wgetで出力されたファイルは読み込んだ後は削除する。


---------------------------------------------

URLの内容を取得するのが難しかったのですが、それなりに上手くいったので、きちんと動いたソースコードの一部を転載用に書き換えて以下に載せます。
今回(仕事で取り組まねばならなかったシステム)は幸運にも、ログインフォームには動的に生成されるアクセストークンなどのめんどくさい値はなかったので、そこはすっきりいきました。

---------------------------------------------
サンプルプログラムソース
ファイルの保存文字コードはUTF8
---------------------------------------------
<?php

class ModelGetterContentFromUrl{
  //////////////////////////////////////////////
  /**
   * wgetを使い、システムへのログイン後のURLの内容を取得する
   *
   * @param  String 対象URL
   * @return String 取得したコンテンツ
   */
  public static function getContentFromUrlUsingWget($url){
    //引数チェック
    if(
      (isset($url) == false)or
      ($url == "")
    ){
      return;
    }//if

    /////////////////////////////////////
    //定数定義(サンプルプログラムなのでここに書いてますが)
    //本来ならコンフィグ用のファイルなどで行ってください

    //一時的な作業フォルダ、ファイル
    define("OUTPUT_DIR_WORK_TEMP" , $_SERVER["DOCUMENT_ROOT"]."/work_temp"); //作業用フォルダ。あらかじめ作成しておく必要がある。
    define("TEMP_FILE_COOKIE"     , OUTPUT_DIR_WORK_TEMP."/cookie.txt");     //クッキー保存用
    define("TEMP_FILE_HTML"       , OUTPUT_DIR_WORK_TEMP."/temp.html");      //wgetで作成されるHTMLファイル

    //ログインページURL
    define("URL_LOGIN_PAGE", "ログインページのURL");
    //ログイン処理を実行するスクリプトのURL(ログインページのフォームのアクションで指定されたURL)
    define("URL_LOGIN_PROCESS", "ログイン処理を実行するスクリプトのURL");

    //ログインID
    define("LOGIN_FORM_NAME_ID", "name"); //ログインフォームのID入力部分のname属性
    define("LOGIN_ID", "admin");          //ログインID
    //ログインパスワード
    define("LOGIN_FORM_NAME_PW", "password"); //ログインフォームのパスワード入力部分のname属性
    define("LOGIN_PW", "password");           //ログインパスワード


    /////////////////////////////////////
    //PHPプログラムをログインさせ、適切なクッキーファイルを作成し、
    //目当てのURLアクセス時にそのクッキーファイルを利用させる

    /////////////////////////////////////
    //処理の三段階の説明
    //1.ログインページにアクセスし、クッキーを取得・保存
    //2.form actionに値をPOSTし、ログイン完了ページへ
    //3.取得したいページへアクセスし、ソース取得

    //クッキー保存ファイルを作成
    touch(TEMP_FILE_COOKIE);

    /////////////////////////////////////
    //第1段階 クッキーを取得
    //該当URL : URL_LOGIN_PAGE
    $linux_command = ""
      ." wget "
      ." --save-cookies='".TEMP_FILE_COOKIE."' "  //クッキー保存指定
      ." --keep-session-cookies "                 //クッキーの内容を漏らさず保存しろという指定
      ." -p DirPath='".OUTPUT_DIR_WORK_TEMP."/' " //書き出しディレクトリ指定
      ." --delete-after=on "                      //ダウンロード後にファイル削除
      ." --no-cache "
      ." '".URL_LOGIN_PAGE."'"
    ;
    exec($linux_command);

    //※このログインページには、システムが動的に生成するアクセストークンなどの値が
    //  hiddenフィールドに存在しないものと想定している。
    //  もしシステムが動的に生成するアクセストークンなどのhiddenフィールドが
    //  ログインページにある場合、「--delete-after=on」を外し、書きだされたHTMLファイル
    //  の内容を解析し、アクセストークンのnameとvalueとを取得する必要がある。


    /////////////////////////////////////
    //第2段階 ログイン完了ページヘ
    //該当URL : URL_LOGIN_PROCESS
    $linux_command = ""
      ." wget "
      ." --load-cookies='".TEMP_FILE_COOKIE."' "  //クッキー読み込み指定
      ." --save-cookies='".TEMP_FILE_COOKIE."' "  //クッキー保存指定
      ." --keep-session-cookies "                 //クッキーの内容を漏らさず保存しろという指定
      ." -p DirPath='".OUTPUT_DIR_WORK_TEMP."/' " //書き出しディレクトリ指定
      ." --delete-after=on "                      //ダウンロード後にファイル削除
      ." --no-cache "
      ." --post-data '".LOGIN_FORM_NAME_ID."=".LOGIN_ID."&".LOGIN_FORM_NAME_PW."=".LOGIN_PW."' "
      ." '".URL_LOGIN_PROCESS."'"
    ;
    exec($linux_command);

    //※もしシステムが動的に生成するアクセストークンなどのhiddenフィールドが
    //  ログインページにある場合、「--post-data 'key=value&key=value'」の行に、
    //  「&アクセストークンのhiddenフィールドのname=アクセストークンの値」を追加


    /////////////////////////////////////
    //第3段階 取得したいページヘアクセスし、コンテンツを取得
    $linux_command = ""
      ." wget "
      ." --load-cookies='".TEMP_FILE_COOKIE."' " //クッキー読み込み指定
      ." --no-cache "                            //キャッシュさせずに常に最新のデータを取得する
      ." -O '".TEMP_FILE_HTML."' "               //書き出しディレクトリとファイル名との指定
      ." '".$url."' "                            //対象URL
    ;
    exec($linux_command);

    //出力したファイルからHTMLの内容を読み取る
    $html_string = file_get_contents(TEMP_FILE_HTML);

    //UTF8に文字コード変換
    mb_language("Japanese");
    $html_string = mb_convert_encoding($html_string, "UTF-8", "auto");

    //一時ファイルの削除
    unlink(TEMP_FILE_COOKIE);
    unlink(TEMP_FILE_HTML);

    //取得したHTMLを文字列に格納したものを戻す
    return $html_string;

  }//function
}//class

---------------------------------------------
/サンプルプログラムソース
---------------------------------------------

2014年9月16日火曜日

【Android】PDFをアプリ内で表示させたい PDFビューアーライブラリ

仕事で作成しているAndroidアプリにおいて、PDFをアプリ内で表示したいという要求があり、いい方法ないかなと調べていました。
Androidは標準ではPDF表示の機能がないし、いいライブラリもあんまり無いようなのですね。

調べた結果、次のブログで紹介されていた「android-pdfview」というのが、現状では組み込んだ自分のアプリ内では動作しています。
http://www.takemisousaku.com/?p=846#more-846

配布元(海外の個人のサイトみたい)
http://joanzapata.com/android-pdfview/

【導入】
例によって、エクリプスから、「ファイル>新規>その他>既存コードからのAndroidプロジェクト」で、ダウンロードして解凍した 「android-pdfview」を指定して、ワークスペースに読み込ませます。
パッケージエクスプローラでそのライブラリを使いたい作成中のアプリのパッケージ名で右クリック>プロパティ>Android>ライブラリ>追加で、「android-pdfview」を追加。

ところが、2014/09/16現在でこの作業をすると、「android-pdfview」がエラーを吐きまくっていて動きませんでした。
エラー内容を調べてみると どうも、解凍したファイルたちのフォルダ構成がパッケージ名と一致していないということが原因らしい。

すんなり解凍するとフォルダ構成は以下のようになってる:
android-pdfview\src\main\java\com\joanzapata\pdfview
android-pdfview\src\main\java\org\vudroid

ところが、各ファイルに書いてあるパッケージ名には、「\main\java」の記載はない。

そこで、「com」「org」のふたつのディレクトリを、「android-pdfview\src」にカット&ペーストした。
したら動くようになりました。

つまりこうです:
android-pdfview\src\com\joanzapata\pdfview
android-pdfview\src\org\vudroid


「android-pdfview」自体は、その他の特別なライブラリなんかは必要としていなかったように見えました。
 

2014年9月11日木曜日

【Android】HTTPSのWebAPIにAndroidで接続するとエラーになる

Androidで、「https://」ではじまるURLにHTTPリクエストを送って、データを取得したい。

HttpsURLConnectionのクラスを使うらしい。
HttpURLConnectionを使い、「http://」から始まるURLに接続すると、特に困難なくデータが取得できたのに、「https://」の接続先だとうまくいかない。
接続先URLにブラウザで直接アクセスすると、証明書がないからセキュリティ的に怪しいんだけどそれでも接続しちゃっていいの?という画面が表示されて、例外を認める操作をすると接続できるようになる。

Androidが吐くエラーは以下のとおりだった。
09-11 17:16:37.886: W/System.err(3801): javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

どうも、サーバーに信用できる証明書がインストールされてないからこの端末からの接続はしたくないんだ!じゃあね!ということらしい。

開発中のアプリの場合、サーバーも開発用のサーバーで証明書なんて無いことが普通なので、これでは困る。

対処法。

サーバーに信用できる証明書がインストールされて無くても無理やりつないでしまえ、という処理を実装するらしい。

参考ページへのリンクを貼っておきます。
http://symfoware.blog68.fc2.com/blog-entry-1165.html

http://blog.kinjouj.net/self-signed-cerificate-https-server-connection-from-java.html

検索の際のキーワード: X509TrustManager オレオレ Android

リンク先が消えた時のため、キモのところだけ、自分のアプリで動いたものをコピペしておきます

---
(AsyncTask継承クラス内の、doInBackground()内)


//////////////////////////////////////////////
//オレオレ証明書によるSSLサーバー接続でもエラーをスルーできるようにする
SSLContext sslcontext = null;

try {
    //証明書情報 全て空を返す
    //証明書情報 全て空を返す
    TrustManager[] tm = {
        new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }//function
            @Override
            public void checkClientTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {
            }//function
            @Override
            public void checkServerTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {
            }//function
        }//class
    };
    sslcontext = SSLContext.getInstance("SSL");
    sslcontext.init(null, tm, null);
    //ホスト名の検証ルール 何が来てもtrueを返す
    HttpsURLConnection.setDefaultHostnameVerifier(
        new HostnameVerifier(){
            @Override
            public boolean verify(String hostname,
                    SSLSession session) {
                return true;
            }//function
        }//class
    );
} catch (Exception e) {
    e.printStackTrace();
}//try

////////////////////////////////////////////////////////
//APIに接続
//接続URLを作成
String url_string = "ここにhttps://のURL";

////////////////////////////////////////////////////////
URL url = null;
try {
    url = new URL(url_string);
} catch (MalformedURLException e1) {
}

////////////////////////////////////////////////////////
//HTTPコネクションの作成
HttpsURLConnection connection = null;
try {
    connection = (HttpsURLConnection)url.openConnection();
} catch (IOException e) {
}//try

//オレオレ証明書によるSSLサーバー接続でもエラーをスルーできるようにする
connection.setSSLSocketFactory(sslcontext.getSocketFactory());

////////////////////////////////////////////////////////
//リクエストするパラメータの設定
try {
    connection.setRequestMethod("GET");
} catch (ProtocolException e) {
}//try

////////////////////////////////////////////////////////
//通信の開始
try {
    connection.connect();
} catch (IOException e) {
}//try

---

2014年8月29日金曜日

【Android】「ホームボタンが押されてアプリがバックグラウンドに移ったとき」を検知するActivity.onUserLeaveHint()

Androidでは、アクティビティやフラグメントのライフサイクルを意識して処理の順番や初期化のタイミングなんかを整理しないといけない。

非同期処理やAPI接続など、複雑で長い処理(「一連の処理」のつながりが長い処理)は、途中で中断される可能性にも対応しなくてはならない。


「長い処理がどこまで進んだのか」を保持しておいて、再開時に続きから実行する、という処理が難しい場合、そもそも処理を初めからやり直させるほうが確実だしエラーが少ない。時間はかかるけど。

しかし、ユーザーが端末のHOMEボタンを押してHOME画面に遷移したとしても、アプリにとってはそれは「完全終了」扱いにならない。

通常だと、アクティビティもフラグメントも、ライフサイクル上のもっと短いサイクルを回るだけである。
HOMEボタンを押す→onPause()を通る
HOME画面から再度アプリに戻ってくる→onResume()を通る

そこで、「この処理が終わってない状態の時、HOMEボタンが押されたらきちんとアプリそのものを終了させて、再開時はライフサイクルの頭から再びきちんと処理をやり直させる」という仕組みがほしい。

Activityクラスには onUserLeaveHint() というメソッドがある。これはHOMEボタンを押すなど、「ユーザーの操作でアプリがバックグラウンドに移るときに呼ばれる」処理だということ。

http://developer.android.com/reference/android/app/Activity.html#onUserLeaveHint%28%29

Called as part of the activity lifecycle when an activity is about to go into the background as the result of user choice.
意訳:ユーザーの選択によってアクティビティがバックグラウンドに移る時、アクティビティのライフサイクルの一環として呼ばれる。

なので、該当アクティビティのonUserLeaveHint() メソッドに finish() を書いておけば、HOMEボタンが押された時に完全に終了させることができるようになる。
その処理は条件付きで行いたいという場合、プリファレンスなどを使って条件を参照できるようにしておき、「これこれの時に限っては、HOMEボタン押したらアプリの終了とするからね」という処理にするといい。

Activity.finish()は、アプリをキレイに終わらせるメソッドで、該当のアクティビティとそれに連動しているフラグメントたちはそれぞれきちんとonDestroy() まで通って終了してくれるようだ。

参考:
http://blog.livedoor.jp/tattyamm/archives/3640900.html

そうか、戻るボタンの挙動にも対応しないといけないのか…

2014年8月26日火曜日

【Android】AsyncTaskLoader と AsyncTask

WebAPIからデータを取得する処理。

通信はメインスレッドでやってはいけないので 、非同期処理を作ることになる。

AsyncTaskLoaderを使うのが推奨されているらしいので、それを使って実装していた。
AsyncTaskLoaderを継承したクラスを動作させるためにはLoaderManagerクラスのインスタンスを経由しないといけない。
LoaderManagerクラスのインスタンスを取得するには、Activityを主語にして getLoaderManager() を実行しないといけない。
→アクティビティが使える場所じゃないといけない。


今回、サービスから処理がスタートして、アプリ本体が画面に表示されていなくてもAPIに接続してデータを取得しそれをプリファレンスに書き込むという処理を作ることになった。
サービスからスタートしているし、画面にアプリ本体が表示されていなくても実行できないといけないので、アクティビティが使えない=AsyncTaskLoaderが使えない。

そこで、APIに接続する処理を AsyncTaskクラスを継承したクラスで行うようにした。
これならアクティビティを必要としない。

本番のAPIではなく、仮のURLに接続してテストしたところ、値が取得できたので動きそう。

2014年8月21日木曜日

【Android】JAVAでXMLを使う際の親子関係に注意


WebAPIなどから取得したXMLを解析したいことがよくある。

<key>value</key>
この「key」と「value」とは同じ世代のように見える。

node.getNodeName();  →「key」が取得できる時
node.getNodeValue();  →「value」が取得できそうに見える

しかしそうではない。

<親>子</親>
なんとこいつらの間にも階層がある。

node.getNodeName();
→「key」が取得できる時、

node.getFirstChild().getNodeValue();
→「value」を取得するならもうひと段階子孫に行かねばならない。

【Android】アプリが画面に表示されてない時に呼び出してはいけない処理

スプラッシュ画面などにおいて、タイマータスクでフラグメントを取り除くなどView要素を操作する場合。
画面にそのアプリが表示されていなければその操作は実行できないので、画面に表示中かどうかを確認した上で動作させるようにする。
または、タイマーをonPause()でキャンセルさせる。両方行うと良い。

View要素を操作するタイマーは、onResume()でセットして、onPause()でキャンセルさせるといいみたい。こうすればHOME画面の表示中に実行されることがない。

【Android】インナークラスから外側のクラスにアクセスする

インナークラスにおいて、「外側のクラス名.this」と書けば良い。

参考:
http://www.ne.jp/asahi/hishidama/home/tech/java/class_use.html

こんな感じ:
********************************************************

public class OuterClass{
    public void testMethod(){
        System.out.println("OuterClassのtestMethod()です。");
    }//function

    //////////////////////////////////
     /**
     * インナークラス
     */
    public class InnerClass{
        public void testMethodInner(){
            //ここで外側のクラスを主語にしたい時、

            //「外側のクラス名.this」とする。
            OuterClass.this.testMethod();
        }//function
    }//class

}//class

********************************************************

【Android】Serviceライフサイクルコピペ用

    //サービスクラスを作成したらマニフェストへの登録を忘れずに!

    ///////////////////////////////////////////////////////////
    //フィールド インナークラスでも使いたい変数はフィールドに
    ///////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////
    //メソッド ライフサイクル順
    //サービスを開始させたスレッドで処理されるので、
    //重たい処理は非同期処理にする工夫をするのが良い
    ///////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル01:onCreate()
     * はじめてサービスが起動される時
     * ※複数のサービスが起動されるときは初回だけ呼ばれる
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル02:onStartCommand(Intent intent, int flags, int startId)
     * 開始:startServiceメソッドでサービスが開始する時
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル03:onBind(Intent arg0)
     * バインド:bindServiceメソッドでサービスとバインドする時
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル04:onUnbind()
     * バインド解除:サービスとのバインドを解除する時
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル05:onRebind()
     * 再バインド:サービスと再度バインドする時
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル06:onDestroy()
     * 停止:停止状態から破棄される直前
     * アクティビティのstopServiceや、ServiceのstopSelf()メソッドが呼ばれたとき
     */


    ///////////////////////////////////////////////////////////
    //クラス内クラス
    //タイマー処理などとの連携用
    ///////////////////////////////////////////////////////////
    /**
     *
     *
     */


【Android】Fragmentライフサイクルコピペ用


    ///////////////////////////////////////////////////////////
    //フィールド インナークラスでも使いたい変数はフィールドに
    ///////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////
    //メソッド コンストラクタ
    ///////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////
    //メソッド ライフサイクル順
    ///////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル01:onAttach()
     * フラグメントがアクティビティから最初に取り付けられた時に
     * 呼び出される。
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル02:onCreate()
     * システムがフラグメントを作成した時に呼び出される。
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル03:onCreateView()
     * フラグメントが画面描画をはじめて行ったタイミングで
     * 呼び出される。
     *
     * 【重要】
     * フラグメントを使用する時、クラスに記述するべき
     * 中心的メソッド。
     *
     * inflater.inflate()で取得するViewを戻す。
     * そのとき、リソースのレイアウトXMLを指定する。
     *
     * 戻り値がViewになっている。ここで返されたViewが描画される。
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル04:onActivityCreated()
     * 呼び出し元になるActivityのonCreateメソッドが完了したら
     * 呼び出される。
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル05:onViewStateRestored()
     * フラグメントのビュー階層の状態が復元されるときに
     * 呼び出される。
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル06:onStart()
     * フラグメントがユーザーに見えるように生成された
     * タイミングで呼び出される。
     *
     * アニメーションの開始など、表示に関わる初期化処理を行う。
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル07:onResume()
     * アクティビティがバックグラウンドからフォアグラウンドに
     * 移るタイミングで呼び出される。
     *
     * イベントリスナーの登録、タイマーの開始やデータの読み込みを行う。
     *
     * ホームボタンでホームに戻ってから改めてアプリに戻ってきた時もここは通る
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル08:onPause()
     * Activityがバックグラウンドに移ったか、もしくは
     * アクティビティ内のフラグメントを変更する操作を行うことで
     * ユーザーとの対話がされなくなった場合に呼び出される。
     *
     * ホームボタンでホームに戻るときもここは通る
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル09:onStop()
     * アクティビティが停止したか、もしくはアクティビティ内の
     * フラグメントを変更する操作を行うことでユーザーに表示され
     * なくなった場合に呼び出される。
     *
     * メモリを食うインスタンスの開放など
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル10:onDestroyView()
     * フラグメントのリソースをクリアする場合に呼び出される。
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル11:onDestroy()
     * フラグメントの状態が初期化される場合に呼び出される。
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル12:onDetach()
     * フラグメントがアクティビティから剥がされる直前に
     * 呼び出される。
     */


    ///////////////////////////////////////////////////////////
    //メソッド 非ライフサイクル
    //抽象クラスの継承やインターフェイスの実装により
    //オーバーライドが義務化されるメソッドなど
    ///////////////////////////////////////////////////////////
    /**
     *
     *
     */

    ///////////////////////////////////////////////////////////
    //クラス内クラス
    //タイマー処理などとの連携用
    //外側のクラスへのアクセス表記 : 外側のクラス名.this
    ///////////////////////////////////////////////////////////
    /**
     *
     *
     */

【Android】Activityライフサイクルコピペ用


    ///////////////////////////////////////////////////////////
    //フィールド
    ///////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////
    //メソッド ライフサイクル順 アクティビティ用
    ///////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル01:onCreate()
     * 最初に呼び出される。リソースの初期化処理を行う。
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル02:onRestart()
     * Activityの停止後、再開する直前に呼び出される。
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル03:onStart()
     * 画面が表示される直前に呼び出される。
     * アニメーションの開始など、表示に関わる初期化処理を行う。
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル04:onResume()
     * ユーザーからの入力が可能となる直前に呼び出される。
     * タイマーの開始やデータの読み込みを行う。
     *
     * ホームボタンでホームに戻ってからアプリに戻ってきた時もここは通る
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル05:onPause()
     * Activityから抜けようとした時に呼び出される。
     *
     * ホームボタンでホームに戻るときもここは通る
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル06:onStop()
     * Activityが非表示になった時に呼び出される
     */

    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル07:onDestroy()
     * Activityが破棄される時に呼び出される
     */


    ///////////////////////////////////////////////////////////
    /**
     * ライフサイクル追加:onUserLeaveHint()
     * ユーザーがHOMEボタンを押すなどして、アプリがバックグラウンドに
     * 移った時に呼び出される
     */
 


    ///////////////////////////////////////////////////////////
    //メソッド オプションメニュー用
    //onCreateOptionsMenu()
    //onOptionsItemSelected()など
    ///////////////////////////////////////////////////////////
    /**
     *
     *
     */


    ///////////////////////////////////////////////////////////
    //メソッド 非ライフサイクル
    //抽象クラスの継承やインターフェイスの実装により
    //オーバーライドが義務化されるメソッドなど
    ///////////////////////////////////////////////////////////
    /**
     *
     *
     */


    ///////////////////////////////////////////////////////////
    //クラス内クラス
    //タイマー処理やフラグメントなどとの連携用
    //外側のクラスへのアクセス表記 : 外側のクラス名.this
    ///////////////////////////////////////////////////////////
    /**
     *
     *
     */


【Android】データベースインスタンスはその場で作ってすぐに閉じる

Androidでデータベースを触らないといけない時。
データベースインスタンス(SQLiteDatabaseクラスなど)は、SQLを実行する直前で作成し、SQLを実行した直後にclose()してインスタンスの変数にもnullを入れてしまうこと。

データベースインスタンスはフィールドに入れて保持しておき、複数の場所で使い回す、ということをするべきではない。
非同期処理などで、同一のインスタンスが思わぬタイミングで使われたり閉じられたりするとエラーの原因になるためである。