2014年3月16日日曜日

AndroidでのSDカードへの画像保存

Androidでプロジェクト内部の画像をSDカードに保存する方法を書きます。ちなみに今回はdrawableフォルダに置かれたbg_dot.pngという画像をSDカードに保存します。

String saveDir = Environment.getExternalStorageDirectory().getPath()+"/test";
    File file = new File(saveDir);
    if(!file.exists()){
     if(!file.mkdir()){
      Log.e("debug","Make Dir Error");
     }
    }
    //SDカードに画像保存
    String imgPath = saveDir+"/"+"bt_dot.jpg";
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg_dot);
    try {
     FileOutputStream output = new FileOutputStream(imgPath);
     bitmap.compress(Bitmap.CompressFormat.PNG, 100, output);
     output.close();
    } catch (FileNotFoundException e) {
     e.printStackTrace();
    } catch (IOException e) {
     e.printStackTrace();
    }
    
    //画像をギャラリーに反映
    String[] paths = {imgPath};
    String[] mimeTypes = {"image/png"};
    MediaScannerConnection.scanFile(getApplicationContext(), paths, mimeTypes, new OnScanCompletedListener(){
     @Override
     public void onScanCompleted(String path, Uri uri) {
      Log.d("MediaScannerConnection", "Scanned " + path + ":");
      Log.d("MediaScannerConnection", "-> uri=" + uri);
     }
    });

最初のfile.mkdir()で保存するディレクトリを作成しています。その後、ファイルをBitmapオブジェクトとして取り出して保存します。

FileOutputStreamを利用して画像の保存処理をしただけでは、保存した画像は再起動しないとギャラリーに反映されません。23行目以降のMediaScannerConnection以降の処理を行うことで再起動することなく画像をギャラリーに反映できます。
また、AndroidManifest.xmlには次のパーミッション記述が必要です。
結果はこんな感じ。SDカード内にtestフォルダが作られ、画像が保存されました。

参考:https://northerndarkstg.at.webry.info/201207/article_3.html

2014年3月5日水曜日

リワード広告の仕組み(2)

さて、いよいよ実際にリワードシステムを作ります。

とは言ったものの、セッションやクッキーに対する理解が浅かったので勉強し直してきました。詳しくはGoogle先生が教えてくれるでしょうから簡単に説明します。

クッキーとは、ブラウザ側にデータを保存するものです。一度ログインしたHPに二度目にアクセスするとIDやパスワードが保存されてますね、あれがクッキーです。語源はそのまんまお菓子のクッキーらしいのでブラウザにクッキーを食べさせる、というイメージなのでしょうか。もぐもぐ。
反対に、セッションはサーバー側に保存するデータになります。ブラウザ側はそのセッションIDのみをクッキーに保存しておきます。サイトにアクセスすると、ブラウザに保存されているセッションIDからサーバーに保存されているデータを呼び出します。


ではコードを交えて解説を。
セッションはsession_start()で開始されます。セッションがまだ開始されていない状態でこの関数が呼び出されるとセッションIDが生成されます。すでにセッションが開いている時にこの関数が呼ばれると、前のセッションが呼び出されるようです。
<?php
  session_start();
?>
セッションIDはブラウザにクッキー名PHPSESSIDで保存されます。セッション変数は$_SESSION[変数名]で管理されています。セッションIDを確認するのであれば
  session_start();
  print($_SESSION[PHPSESSID]);
で見ることができます。ページへの訪問回数を数えるのであれば
  session_start();
  if(isset($_SESSION["visited"])){
 $_SESSION["visited"]++;
 print($_SESSION["visited"]."回目の読み込みです");
  }else{
 $_SESSION["visited"] = 0;
 print("初めての読み込みです");
  }
とすればセッションID訪問回数をカウントすることができます。セッションIDを持つクッキーは基本ブラウザが閉じられると破棄されるのですが、クッキーの生存時間を任意に指定することもできます。

基本はこれを利用してできそうです。全部書くとキリがないのもあるので僕が詰まったとこや要点だけ。

まず、GooglePlayへの遷移ですが、リンク先を
https://play.google.com/store/apps/details?id=com.android.chrome
としてしまうとGooglePlayへのアクセス方法をユーザが選べてしまいます。ブラウザを起動するのかGooglePlayのアプリを起動するのかユーザが選べてしまいます。そこまで困りはしないのですが、できれば強制的にGooglePlayアプリへ遷移させたい。
なので、リンク先は
market://details?id=com.android.chrome
としてあげれば問答無用でGooglePlayのアプリがAndroid端末で起動します。もちろんこれはPCブラウザでは反応しません。


それと、サーバー側でのセッションIDの保存方法。僕はここでかなり躓きましたし実際今も完璧に理解できていない気がします。セッションIDはサーバー側で保持するものですが、その保存方法がいくつかあるようです。
これらはphp.iniのsession.save_handlerで設定できます。また保存場所はsession.save_pathに絶対パスを指定することで変更できます。
しかし、共用サーバーなどの場合サーバー全体で設定が変わってしまうため作業フォルダに.htaccessファイルを作成しました。ここに設定を記述することで.htaccessを置いたフォルダ以下のPHPプログラムにその設定が反映されます。

保存方法そのいち
memcached(メモキャッシュディーと読む?)を利用する方法。使ってるサーバーのphp.iniの設定がこれになっていたのですが、memcachedというキャッシュシステムを使ってサーバーに値を保存する方法。
.htaccessで
php_value session.save_handler = memcache
php_value session.save_path = "tcp://host:11211"
と記述することで保存形式をmemcachedにできます。

保存方法そのに
ファイルで保存する方法。session.save_handlerの値をfilesにすることで設定可能です。
php_value session.save_handler = files
php_value session.save_path = "フォルダの絶対パス"

そのさん
データベースを使う方法
データベースに値を保存する方法です。セッションデータの共有などを行うのであればこの方法が便利。session.save_handlerの値をuserにします。session.save_pathは指定しません。
php_value session.save_handler = users

ちなみに、躓いた経緯はこんな感じ。

最初memcacheで深く考えずにセッション関係の関数を使用していたのですが、最初何回かはうまくいくのに途中からセッションが保持されなくなってしまった。memcachedの存在も調べて初めて知ったのでよくわからない。。。
じゃあファイルで保存できるらしいしそうしよう。
そんな気持ちで設定を変え、保存するフォルダパスを指定しました。
実行テスト
一回目

 いい感じ。ちゃんとセッションIDが発行されています。
更新ボタンを押すと、二回目って出るはず。
二回目
アイエエエエエエエ!?ショカイ!?ショカイナンデエエ!?
更新されません。もいっかい押したら
あれ、更新される。。。。

その後も何回か試してみましたが、必ず二回更新しないとセッションの値が更新されないことに。

ログを見てもなにも出てなかったのですが、ひたすらネットで情報を漁っているといくつか似たような事例が。間違っているかもしれませんが、ファイルシステムがロックされていることが関係している模様。一度アクセスすると30秒ほどはファイルにアクセスできないみたい。

実際30秒間隔を空けてあげるときちんと更新されました。しかこんな自由意思に頼ったシステムで納品するわけにもいかず、先ほど調べているうちに知ったDBを使ったセッションの管理を試してみることに。

これには、あらかじめセッション保存用のテーブルを用意してあげて
session_set_save_handler("open","close","read","write","destroy",gc");
としてセッションの保存に関連するメソッド(open(),close(),read(),write(),destroy(),gc())を定めてあげればいいだけです。僕はsession_hanlder.phpという形で一つのファイルにしてしまったのですがだいたいこんな感じになります。
<?php
        //データベース情報
 $dsn='mysql:dbname=xxx;host=xxx.xxx.xxx.xxx';
        $user='user';
        $password='password';


    //データベースへ接続
 function connect_db(){
 global $dsn,$user,$password;
  try{
   $dbh = new PDO($dsn,$user,$password);
   //print('接続に成功しました
');
  }catch(PDOException $e){
   //print('Error:'.$e->getMessage());
  }
  return $dbh;
 }

 function open(){
  //データベース接続

                return true;
 }
 
 function close(){
  return true;
 }

 function read($id){
  //データベース読み込み
 
  return $data;
 } 
 
 function write($id,$data){
  //データベースへの書き込み・あるいは更新
 }

 function destroy($id){
  //データベースからデータを削除
 }

 function gc($maxlifetime){
  //ガーベッジコレクションの処理
 }

 session_set_save_handler("open","close","read","write","destroy","gc");
  
?>

これでようやく問題なくセッションの管理ができるようになりました。そんなに難しいことじゃないんだけど、苦労した。。。

学んだこととしては


  • アプリDLなどを跨いでのユーザの認証にセッションを使うこと
  • セッションファイルはmemcached,ファイル,DBなどで管理ができること

の二点が大きかったです。あとはセキュリティとか、めんどくさry...いろいろですね。なんとかなって良かったです。

2014年3月1日土曜日

リワード広告の仕組み(1)

唐突に、社でリワード広告システムを作ることになりその勉強をしていました。

仕組み自体は最初全く知らなかったため、「つらいにゃん...つらいにゃん..」とか言いながら苦闘していたのですが・・・結果的にいろいろと理解できて、勉強になったためここにまとめたいと思います。


 そもそも、リワード広告システムとはなにか。
ここで登場するのはユーザ・広告掲載社・広告提供社の三役です。 掲載社をA社・提供社をB社とします。 僕が関わったのはBの立場からです。 

リワード広告システムとは、近年増えてきた所謂ポイントアプリ・お小遣い稼ぎアプリなどで多く利用されているシステムです。
A社のポイントアプリAにおいて、ユーザがB社の出しているアプリBの広告をクリック・それをダウンロード後起動するとアプリAでポイントが貯まる、といった仕組みになります。
最終的にアプリAで貯まったポイントはAmazonやらiTunesのギフト券に換金できる、と。

アプリAを出している会社にしてみれば広告料をもらえるし、B社にしてみれば自社アプリの宣伝になるし、ユーザにしてみればアプリをダウンロードだけでお金に変わるポイントが手に入るし...と一見みんな幸せに見れるような仕組みになっています。



 こっからがシステムの話。 そのときどんな動きをしているのか

ユーザがアプリAの広告をクリックした際にA社からは管理用(キックバック用)のidが付与されます。つまりB社のCGIプログラムにGETでidが送られます。http://B社のページ?id=xxxという感じですね。
idは後で成果報酬を返すための値で、クリックしたユーザの端末情報などではないです。
そのidを受け取った上でB社はユーザをアプリBのDLページ(今回はGooglePlay)へ誘導します。ユーザがアプリBをダウンロード・起動したらその成果をA社に返すことになります。
つまり,広告をクリックしたユーザとアプリBをDLしたユーザが一緒かどうか判別して、確かに同じユーザだと特定できたら成果をA社に返せればいいわけです。

はぁ

最初聞いて疑問に思ったのはユーザの認証方法。
ユーザNが広告をクリックするとA社からid=nnnが送られてくるのですが、Nがアプリをダウンロード、起動した後でそのアプリを起動したユーザがid=nnnのユーザと同一だと認証するにはどうすれば良いのか。だってid一連の流れの中でずっと保持できないし。
解決方法として真っ先に思い浮かんだのは端末の固有情報を利用すること。Androidでは端末からそこそこ情報を抜き取れるのでそれと照らし合せられないかなーと思ったのです。
【参考:TechBooster Androidのシステム情報を取得する】
ただ、サーバ側にidを持ってアクセスしてきた時点での端末情報の取得が難しいようで。ガラケーだったらアクセス情報からキャリア・端末情報を抜き取ることができたみたいなんですけど、スマホから取れたのはAndroidのバージョンくらいでした。これじゃ判別はできない。
そもそも、端末の固有情報を抜き取るのは個人情報保護法の観点からもよろしくないようです。(そりゃそーだ)
iOSではそういった端末情報を取得することができないように規制をかけてるみたいですね。

「じゃあユーザ特定できないじゃん、無理じゃん。無理ゲーじゃん。」なんて悲しみに沈みながら尚ネットの海を泳いでたら、とある記事が目にとまりました。
【アフィリエイト広告業界におけるシステム間の連携ってなんだろう(仕組み編)】
・・・これならでき、そう?

ということでブラウザのセッションを利用するという方向で調べていくと、なんかできそう。
具体的には、
(1)ユーザが広告をクリックしてB社へ転送された時にセッションIDを発行し
(2)ユーザがアプリをダウンロード、起動した後にブラウザを起動させ、セッションIDで認証

という方式をとればうまくいけそうです。上手くいかせなければいけないのです。

ということで次の投稿でクッキーとセッションの話、PHPを使ってどのように実装するか、を簡単に書いていきます。