2015年6月21日日曜日

Fragmentでバックキーイベントを拾う際の注意点


Fragmentでバックキーが押されたことを検知したくてこちらのサイトに紹介してある方法と同じように実装をしてみた。
しかし、バックキーが押されたイベントを拾うことができなかった。

 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.myfragment, container, false);

    view.setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN)
                Log.w(TAG, “on pressed back key”);
            return false;
        }

    });

    view.setFocusableInTouchMode(true);

    return view;
}

もっとよく調べてみると、バックキー等のハードウェアキーのイベントを拾うためにはそのViewにフォーカスが当たっている必要があることがわかった。
テキストエディット等はタッチすればフォーカスが当たるがFragmentは別の方法でフォーカスを当てる必要がある。

  • 方法1:レイアウトファイルに書いたFragmentの属性にandroid:focusable = "true"を入れる
  • 方法2:javaのコード上でrequestForcus()を呼ぶ

したがって、先ほどのコードは下記のように書く必要がある。

 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.myfragment, container, false);

    view.setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN)
                Log.w(TAG, “on pressed back key”);
            return false;
        }

    });

    view.setFocusableInTouchMode(true);
    view.requestFocus();

    return view;
}

2015年6月14日日曜日

Copy as cURL に感動

ウェブページの内容をスクリプトで解析したいということがよくある。
当然結果はcurl等のコマンドラインツールで実行したテキストの形式で受け取りたい。
なので、ブラウザで送るリクエストと同等のものをcurlのオプションや引数の設定をして作らなければならない。

いままではchromeの開発ツールにあるNetworkタグのリクエスト内容を見て位置からコマンドを作っていた。

ふと、リクエストを右クリックしてみると"Copy as cURL"なるものがあった。


まさかと思い試してみると、期待通りcurlのコマンドが!
curl 'http://seica.info/search/hinmoku.aspx' -H 'Cookie: ASP.NET_SessionId=hu2sjzkjdvf2ll3emwmw4xyv; __utmt=1; __utma=19271098.1453152246.1434261901.1434261901.1434261901.1; __utmb=19271098.16.10.1434261901; __utmc=19271098; __utmz=19271098.1434261901.1.1.utmcsr=naro.affrc.go.jp|utmccn=(referral)|utmcmd=referral|utmcct=/nfri/contens/open-db/' -H 'Origin: http://seica.info' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: ja,en-US;q=0.8,en;q=0.6' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' -H 'Cache-Control: max-age=0' -H 'Referer: http://seica.info/search/hinmoku.aspx' -H 'Connection: keep-alive' --data '__EVENTTARGET=&__EVENTARGUMENT=&__LASTFOCUS=&__VIEWSTATE=%2FwEPDwUKLTE2NzE0NTU5O...(略)

おそらくweb開発をやっている方からすると当たり前の機能なのでしょうか、
私は・・・感動しました!

2015年6月13日土曜日

AndroidアプリWhat's This公開

以前に作ったアプリの知見を生かしつつ、新しい技術を取り入れることを目標としてWhat's Thisというアプリを作りました。

【ざっくりとした使い方と機能】
・ユーザが事前に任意の画像に対してアプリより対象物の指定と名前、よみがなの設定を行う
・設定後の画像の対象物をユーザがタッチすると、名前が表示され、よみがなが音声として流れる


子供がものの名前を覚えるときに使ったり、人の名前を覚えるときに使って貰えればと思っています。
ご興味がありましたら是非使ってみてください。

https://play.google.com/store/apps/details?id=com.nsunrize.whatsthis&hl=ja






2015年5月9日土曜日

メモリを節約しながら画面サイズに合わせたBitmapの表示方法



CanvasにBitmap(画像ファイル)を表示させるときに、縮小もしくは拡大して表示したいということがある。 このときよく使われる方法はBitmap.createBitmapを呼び出してサイズを変更したBitmapを新しく作る方法であるが、 createという言葉の通り新しくBitmapオブジェクトが生成されることになるためメモリを食ってしまう。

 もっと良い方法が無いか試してみたところ、drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)を 使えばメモリを消費せずに描画できることがわかった。 
第二引数のRectはnullで良いらしいので、第三引数のRectに表示したい画像の座標を入れてやればOKで無事に メモリを節約した状態で拡大縮小表示ができた。

2015年5月7日木曜日

Gralleryから選択した画像のMD5値を取得する


GralleryにIntentを投げて選択した画像が過去に選択したものかを判断するためにMD5値を計算してそれを比べることにした。

まず、GralleryにIntentを投げる。
 @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(intent, 100);
    }

onActivityResultにて帰ってきたIntentからMD5値を計算する。
 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == 100 && resultCode == RESULT_OK) {
            try {
                ContentResolver contentResolver = getContentResolver();

                // Intentより画像ファイルのパスを取得する
                String[] columns = { ImageColumns.DATA };
                Cursor cursor = contentResolver.query(data.getData(), columns, null, null, null);
                if(cursor==null) return;
                cursor.moveToFirst();
                String path = cursor.getString(cursor.getColumnIndex(ImageColumns.DATA));
                
                // ファイルを読んでMD5値を計算する
                FileInputStream fis = new FileInputStream(path);

                MessageDigest md = MessageDigest.getInstance("MD5");
                DigestInputStream dis = new DigestInputStream(fis, md);

                byte [] buffer = new byte[1024];
                while(dis.read(buffer) == 1024) { }

                byte[] digest = md.digest();
                StringBuilder md5 = new StringBuilder();
                for (int i = 0;i < digest.length;i++) {
                    md5.append(Integer.toHexString(digest[i] >> 4 & 0xf));
                    md5.append(Integer.toHexString(digest[i]& 0xf));
                }
                Log.v(TAG, md5.toString());

                fis.close();
                cursor.close();
            } catch (Exception e) {

            }
        }
    }

2015年3月29日日曜日

Bundleに頼らないBitmapの扱い方

必要になった背景

  • Nudiaという画像編集アプリを作ったが、!!! FAILED BINDER TRANSACTION !!!が頻発した
  • 画像を利用するActivityやFragmentそれぞれでメンバ変数にBitmapを保持しており、それら全てでBundleにBitmapを格納していたことが原因
Bundleに保存していると、アプリがシステムから落とされた場合でも、データをリストアすることできるのでとても便利ではある。しかし、Bungleに格納できるデータ容量は有限のため、Bundleに頼らないBitmapの保持方法を考えなければいけない。

考えた案

  1. 編集都度ファイルに保存し、必要な度ファイルからBitmapを読み込む
  2. Bitmapをホストするシングルトンクラスを作りそこを通してアクセスするようにする
1については頻繁に更新読み込みをするため現実的ではないので不採用。消去法的に2になるが一つ問題が出てきた。
その問題とはシングルトン自体がシステムから解放されてしまった場合、ホストされているBitmapも一緒にメモリから消えてしまうことだった。


この問題に対して次の図のように対応をした。
シングルトンとホストされるBitmapは変わらないが、メモリが少なくなってきたこと等をトリガとしてアプリのデータ領域にBitmapのキャッシュを持つようにした。
また、アプリのシステムからの終了後の復帰時にはホストするBitmapがnullであった場合にキャッシュからデータを読み込む様にした。

いまのところ差し当たり問題はないが、今後他のユースケースで問題が出てくるかもしれないので注意が必要。


2015年3月25日水曜日

著作権違反によるアプリリジェクト

そういえば、アプリを公開した後いつまで経ってもGooglePlayの検索結果に現れないためおかしいと思ってデベロッパーコンソールを見てみたところ・・・


リジェクトされていましたwww

以下Googleからの通告内容


下記の問題を解決し、修正したバージョンを公開してください。
削除の理由: コンテンツ ポリシーの知的財産権条項となりすましまたは虚偽の振る舞い条項への違反。詳しくは、知的財産権の侵害なりすましに関するポリシーについてのヘルプ記事をご覧ください。
  • アプリ、または Google Play ストアに掲載されている情報(タイトル、説明、ロゴ、プロモーション用スクリーンショットなど)で、第三者に帰属する保護対象の著作物を不正に使用してはなりません
  • アプリのアイコンやプロモーション用スクリーンショットに、既存のサービスに類似した紛らわしい画像を使用してはなりません
保護対象の著作物には一般に、商品名、ブランド、画像、ロゴ、音楽などの作品が含まれます。
ポリシーに準拠するように、アプリやその掲載情報を変更してください。そのためには、アプリやその掲載情報から、保護対象の著作物に該当する可能性のあるすべてのコンテンツを削除します。
こうしたコンテンツを利用する許可を得ている場合は、Google Play ヘルプセンターからご連絡いただき、許可内容が確認できる正式な証明書をお送りください。
削除はすべて追跡の対象となり、削除が繰り返されるとアプリの公開停止につながります。アプリの停止はデベロッパー アカウントのステータスに影響を与える要因となり、Google Play を利用できなくなる可能性がある点にご注意ください。
この通知は、ご自身が公開中の他のアプリに対する事前通知でもあります。カタログ内の他のアプリ全てが上記のポリシー(ただしこれに限定されません)に違反していないことを直ちに確認することで、今後他のアプリで同様のアップロードの不承認やアプリの停止が生じることを回避できます。アプリを公開する前に、アプリがデベロッパー販売 / 配布契約コンテンツ ポリシーに準拠していることをご確認ください。
今回の判断が誤りであると思われる場合は、こちらの Google Play ヘルプセンターの記事をご利用ください。

どうやらアプリを開いたときに出るトップ画面に操作説明のための入れていた画像が著作権違反となったようでした。
別の著作権が寛容な画像に差し替えたところ無事に公開されました。

Appleのように人手では審査はしていないと思われますので、おそらくアップしたApkを展開して中の画像をGoogleお得意の画像検索をして著作権違反を自動的に調べているのでしょうか?

まあひとまず公開ができたので良かったですが、著作権のあたりもしっかりケアしないといけないという良い教訓になりました。