Hello Swift
Social.frameworkとAcount.framework使ってTwitter投稿とTableViewへのタイムライン取得&表示でSwiftをハローワールドしました。
xcodeでコード補完が殆ど効かなくて苦行でした。
コードをおいときます。(実行環境はxcode6-betaなので、いつ動かなくなってもしらんです) https://github.com/i2key/HelloSwift
こんな感じね。
AndroidのBitmapを扱う際のTIPSメモ
Androidの実装をOSのバージョンが2.xの頃からしておらず最近のコーディングの仕方をキャッチアップしているので、メモを残して行きます。
- 画像キャッシュ
- メモリキャッシュ
- ディスクキャッシュ
- 大きなBitmapをメモリに読み込むときに気をつけること
- inPreferredConfigで画像の質を下げメモリ使用量を減らす
- inPurgeableでGC時に解放されるようにする
- inJustDecodeBoundsとinSampleSizeでメモリ読み込み時にメタ情報のみを先に読み込み、1/nサイズにしたものをロードする
- Bitmapオブジェクトのリサイクル
画像キャッシュ
メモリキャッシュ
Twitterのタイムラインを実装するような時に、APIレベル12以前は下記のように画像キャッシュをしていたと思います。WeakHashMapのがいいかもですが。
public class ImageCache { private static HashMap<String,SoftReference<Bitmap>> cache = new HashMap<String,SoftReference<Bitmap>>(); private ImageCache() { } public static Bitmap get(String key) { if (cache.containsKey(key)) { Log.d("cache", "cache hit!"); return cache.get(key).get(); } return null; } public static void put(String key, Bitmap image) { cache.put(key, new SoftReference<Bitmap>(image)); } public static void remove(String key){ cache.remove(key); } }
しかしながら、APIレベル12からLruCacheというものが導入されたことにより、画像キャッシュはもっぱらそっちのようです。同じクラスを書き換えてみます。
public class ImageCache { private static final int CACHE_SIZE_BASE = Build.VERSION.SDK_INT > 10 ? 5 : 1; private static final int CACHE_SIZE = CACHE_SIZE_BASE * 1024 * 1024; private static LruCache<String, Bitmap> sLruCache; static { sLruCache = new LruCache<String, Bitmap>(CACHE_SIZE) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } }; } private ImageCache() { } public static Bitmap get(String key) { return sLruCache.get(key); } public static void put(String key, Bitmap bitmap) { if (get(key) == null) { sLruCache.put(key, bitmap); } } public static void remove(String key) { sLruCache.remove(key); } }
ディスクキャッシュ
GoogleがDiskLruCacheのサンプルコードを公開しています。こちらを参考にしましょう。
http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html Use a Disk Cache The sample code of this class uses a DiskLruCache implementation that is pulled from the Android source. Here’s updated example code that adds a disk cache in addition to the existing memory cache:
大きなBitmapをメモリに読み込むときに気をつけること
写真系のアプリを作っていて画像をメモリに読み込むことが多いのですが、何も考えずに作るとすぐメモりが一杯になり落ちる(OutOfMemory)。そんなときの対策。
BitmapFactory.Optionsでいろいろやりくりする
http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html
inPreferredConfigで画像の質を下げメモリ使用量を減らす
ARGB_8888からRGB_565にすると大体メモリは半分くらいになる。
http://developer.android.com/reference/android/graphics/Bitmap.Config.html#ARGB_8888
EnumValue | Desc |
---|---|
ALPHA_8 | Alphaのみ8bit |
ARGB_4444 | A,R,G,Bそれぞれ4bit ※クオリティがPoorという理由でAPIレベル13から非推奨 |
ARGB_8888 | A,R,G,Bそれぞれ8bit |
RGB_565 | R,G,Bを5,6,5bit |
inPurgeableでGC時に解放されるようにする
decodeFileやdecodeResourceをするときに指定しておくと、ここで生成されたBitmapはAndroid上のメモリが足りなくなった場合に解放されるようになります。
inJustDecodeBoundsとinSampleSizeでメモリ読み込み時にメタ情報のみを先に読み込み、1/nサイズにしたものをロードする
Googleの公式ページに乗っています。
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
Setting the inJustDecodeBounds property to true while decoding avoids memory allocation, returning null for the bitmap object
下記のように、inJustDecodeBounds = trueにすることでDecode時にメモリに画像がアロケートされるのを防ぐことができるようです。
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
これで、まずは画像のメタ情報(縦横幅等)のみ取得し、画像を何分の1にリサイズして取得するかを計算します。下記、calculateInSampleSizeメソッド参照。 そこで取得したinSampleSizeをOptions.inSampleSizeにセットすることで、次にDeocodeするタイミングで1/inSamplesizeにリサイズされた画像データがメモリにロードされます。 コードは以下。(Google本家サイトから引用)
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; }
Bitmapオブジェクトのリサイクル
Bitmapオブジェクトを使う場合は、使い終わったらrecycleをすること。これをしないとメモリ上から解放されない。これはBitmapFactoryでdecodeする際の実装が、2.x系ではNativeHeap領域に画像メモリを確保するため、それを解放するための明示的なコールになる(3系からはJaveHeap領域に画像メモリを確保するようになった)。
つまり、Bitmapオブジェクトのメモリ解放処理は、DalvikのGCとC++レイヤでのdeallocの併用なので、recycleメソッドで明示的に教えてあげないとダメという理解。(間違ってたら教えて下さい・・・)
http://developer.android.com/reference/android/graphics/Bitmap.html#recycle()
コードでは下記のようにリサイクル。
//インスタンス化 Bitmap bitmap = BitmapFactory.decodeXXXXX(); //解放 if(bitmap != null){ bitmap.recycle(); bitmap = null; } //もっかい使う if(bitmap.isRecycled()){ bitmap = BitmapFactory.decodeXXXXXX() }
Play2.1.3でplay testをしてもテストを認識してくれない不具合について
Play2.1.3 Javaで発生したけど、こうするとなおるよ。Play2.1.3 Scalaはしらぬ。
@kara_d さんに教えてもらたのでメモ。
Build.scalaのmainに以下を追加
testOptions in Test ~= { args => for { arg <- args val ta: Tests.Argument = arg.asInstanceOf[Tests.Argument] val newArg = if(ta.framework == Some(TestFrameworks.JUnit)) ta.copy(args = List.empty[String]) else ta } yield newArg }
こんな感じ
val main = play.Project(appName, appVersion, appDependencies).settings( // Add your own project settings here javaOptions in Test ++= Seq("-Xmx1024m","-XX:MaxPermSize=128m", "-Dfile.encoding=UTF-8", "-Dconfig.file=conf/application_dev.conf"), testOptions in Test ~= { args => for { arg <- args val ta: Tests.Argument = arg.asInstanceOf[Tests.Argument] val newArg = if(ta.framework == Some(TestFrameworks.JUnit)) ta.copy(args = List.empty[String]) else ta } yield newArg } )
スマホアプリ向けのAPIサーバの品質ガイドライン
ふと思い立ってかいた。
スマホアプリを開発する際、mixiさんが公開しているようなスマホ向けガイドラインと同様にAPIサーバでも、ある程度の確認用のガイドラインを個人的に設けていますので、メモとして公開してみます。クラウド環境の登場により誰でも簡単にサービス公開できるようになりましたが、結局のところインフラ構築コストが無くなるだけであり、サーバ品質は自身で担保しないとなりません。
前提
- 本ガイドラインの目的
- 対象
- 環境
- 前提
- 結合試験レベルまでの機能レベルテストは完了していること。条件網羅系試験、境界値試験、同値分割、フルパス等一般的な試験は実施済みであることが前提です。
全体的な機能確認
- 実データ確認
- 本番環境、若しくはそれに近い実データでオンライン処理、バッチ処理を実行させ基本機能が実現できることを確認
- オンライン・バッチ連動
- 業務処理・運用処理連動
- マスタメンテ等の運用系機能からデータを設定し、それがオンライン処理側で利用できることが確認できていること。
- アプリ連携
- アプリ to アプリ、アプリ to ブラウザ、ブラウザ to アプリのようにアプリ連携するようなサービスはそれらの全パスを網羅し確認出来ていること
- 環境差分
- 開発環境と本番環境の差による確認が出来ていること。
- パス等は全て本番に向いていること。
- 外部API接続アカウントは本番用になっていること。
- 国際化
- アプリ、サーバ、バッチ全てにおいて国際化が行われていること。
- ここで言う国際化は文言だけではなく、データフォーマット、文化による振る舞いの変化等も含む。
☆あるある:本番データを入れると動かない。本番環境の0件データ状態だと落ちる。
☆あるある:オンライン側の処理の入力チェック仕様とマスタメンテの入力チェック仕様が不整合。
対外接続確認
性能確認
- 単性能
- スケール前の最小構成にて、レイテンシーが許容値以内である確認が出来ていること。
- 最大性能
- スケール前の最小構成にて、性能の最大値を把握できていること。
- ボトルネックになる部分を把握していること。
- 過負荷
- 負荷をかけた状態にて、通常の処理の確認が出来ていること。
- 最大データ量
- 想定される最大データをDB等に設定し、機能の確認が行えていること。
☆あるある:データが多くなりバッチ処理時間が伸び、後続バッチ、またはそれを必要とするオンライン処理の開始時刻に間に合わなくなる。
☆あるある:大量データのCSVアップロード、ダウンロードでセッションタイムを超えたときに切断してしまう。
拡張性確認
- スケール
- スケーラブルな実装になっていること。
- ElastiCache、DynamoDB等オートスケールしないものに対するスケール性の担保の仕方が決定し、対策が講じられていること。
- EC2起動時に必要な全てのサービスが起動するようになっていること
- スケールインする際に必要なログ等のデータはストレージに退避するようになっていること
☆あるある:push用のバッチもAPIサーバで稼働しており、スケールさせるとバッチ処理がサーバ毎に重複起動し同じプッシュがユーザに複数配信される。
可用性確認
- 耐障害性
- 障害時に復旧するための手順があり、それの確認が出来ていること
- 障害時に自動復旧する仕組みが講じられていること。
- SPOF
- あるサーバの停止がシステム全体を止めてしまわないこと。
- あるモジュールの停止がサーバを止めてしまわないこと。
☆あるある:オンライン処理とバッチ処理を同一サーバ上にで実行している場合、バッチの過負荷によりオンライン側が停止。
運用確認
- バックアップ
- 必要なデータがバックアップ出来ていること、またそれをリカバルする方法が決まっており、確認が出来ていること
- 監視
- システムの状態を監視する仕組みがあること。
- 不具合の解析に必要な情報がログから取得出来るようになっていること。
- 日廻し試験
- スケジュール通り日次バッチ処理が動いていること。
- 月廻し試験
- 上記同様
- 特定日試験
- 祝日、祭日、閏年等により振る舞いを変える場合、その動作が確認出来ていること。
- 問い合わせ運用確認
- データ削除等のユーザからの問い合わせによる運用がある場合、運用担当者を交え、運用手順に従い運用を実施出来ることを確認できていること。
- ユーザ問い合わせへの調査に必要な情報がログから取得出来るようになっていること。
- ログ運用
- ローテーション、退避。
- 長時間運用時の資源
- サーバの再起動無しで長時間運用され続けている状況で、資源(メモリ、CPU、ディスク、..etc)の枯渇を起こさないことが確認できていること。
☆あるある:ログローテートが出来ておらず(またはファイルサイズ見積もりが甘く)、サーバ容量を圧迫し、サーバ停止。
セキュリティ確認
- 一般的なセキュリティ観点に対する対策
- バッファオーバーフロー対策、クロスサイトスクリプティング対策、パラメータ改ざん対策、バックドア・デバッグオプション対策、強制的ブラウズ対策、セッションハイジャック/リプレイ/Fixation対策、クロスサイトリクエストフォージェリー対策、ディレクトリトラバーサル対策、SQLインジェクション対策、OSコマンドインジェクション対策...等々
- クラウドを使う上でのチェックポイント
- 内部の運用管理ツールが公開されていないこと。
- DBへのアクセスがアプリケーションサーバからだけ等の制限をかけられていること。
- セキュリティグループの設定が0.0.0.0/0ではなく、接続元のSGやサーバIP等が絞られていること。
上記の確認ポイントを確認しつつ、バグが見つかった場合は、以下のような基本動作の徹底が必要です。
バグ発生時の基本動作
開発終盤になってくるとバグチケットの山になり、それに埋もれた終わりの見えない状態になると思いますが、そんなときこそ、基本動作を徹底することは大切です。バグでましたーなおしますー、こっちでも出ましたーなおしますーという場当たり的な修正ではなく、品質を作り込むことを意識しないといけません。今回のさいごの仕上げ部分に閉じず、どの試験でも同じスタンスが必要です。
- バグに対して、以下の切り分けを行うこと
- このバグを発見するための試験観点があり、それによって検出された場合
- その場合はハッピーですね。摘出すべくして、摘出した。試験項目がヒットしたということです。
- このバグは既に試験実施済みの観点なのにも関わらず、検出された場合
- これは、完全に試験すり抜けなので、何故すり抜けたかを原因を確認し、必要であれば、その観点で再試験が必要です。実施済み観点でバグが出てしまうと、今までやった試験自体の信頼性を疑わざるを得ない状況になってしまいます。
- このバグが試験をしていない観点のバグであった場合
- 観点漏れです。試験観点を追加し、追加試験の実施が必要です。
- このバグを発見するための試験観点があり、それによって検出された場合
API管理もこれで簡単!Apigeeが凄い
Web2.0で火がついたマッシュアップブームにより、WebAPIが脚光を浴びました。
当時は、GoogleMap上に何かを表示して、すげーーーってなっていただけですが、
最近は、スマホアプリのバックエンドとしてのより実用的な需要が高まっております。
スマホアプリを開発する場合は、サーバサイドはJSONをレスポンスするREST形式のAPIにすることが一般的だと思いますが、それらのAPI提供社側はJavaDocのような統一したAPIリファレンスがあるわけではなく、各社独自にリファンスドキュメントを公開しております。もっとフォーマットが統一されていると見やすいですよね。(個人的にはGoogleAppsのAPI Consoleが好きです。APIリファレンスであり、実行も出来るという。)
以下に、いくつかAPI公開されているもののリファレンスをあげてみます。
ATND API
http://api.atnd.org
Twitter API Refference(例:Timeline)
https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline
Twitterはもう一つこれも Twitter API Console
https://dev.twitter.com/console
最後のTwitterのAPI ConsoleはAPIをリファレンス上で実行できるのです。このTwitterが使っているコンソールはApigeeというAPI管理サービスを使っており、これは誰でも無料で利用できます。
そこで、上記のATND APIをapigeeでAPIコンソール化してみました。全画面で見たい場合は、右上の全画面ボタンを。
これ、リファレンス兼デバッカーになって凄く便利。
認証が必要なAPIの場合は、OAuthも出来たりします。
作り方は簡単
(1)Apigeeにサインアップ
http://apigee.com/
(2)ApigeeのAPI Consoleをクリック
(3)Create API Consoleをクリック
(4)作成するリファレンスの情報を入力
(5)WADLをアップロードする
これで完成。WADL手で作るのメンドーだけど、変数くらいはAPIのサーバサイドのI/O関係のデータクラスとかから自動生成できそうね。
ちなみにWADLはSOAPにおけるWSDLみたいなもの。wiki
WADLってしらなかったー。となると、java2wsdl.shやwsdl2java.shのようなものを期待してしまう。
意外に簡単!LINEのような電話番号認証の作り方(Twilio-SMS編)
LINEで使われているような電話番号による認証を試しに作ってみたので、ブログにまとめておきます。
電話番号認証とはこんなのです。
まず、電話番号を用いた認証ですが、方式が2通りありますので、それらの概要を説明します。
- SMS利用パターン
電話番号をアプリ側で入力すると、サーバから4桁の暗証番号入りのSMSが届き、その4桁の暗証番号をアプリ側で入力することで電話番号が正しいことを証明するやりかたです。
SMSを送信するには送信元の番号が必要になります。また、例えばアメリカ、カナダのようにショートコード(SMSで利用される事業者コード)を取得する必要のある国もあります。ショートコードは審査が入るため、数ヶ月取得に要することがあります。今回は送信者番号は通常の電話番号にします。
- IVR利用パターン
IVRとはInteractive Voice Responseの略で、自動音声応答による認証です。
事前にサービスに電話番号を登録しておき、ユーザにサービスの電話番号へ電話をかけさせることで、その番号を認証するInbound方式と、サービス側から登録した電話番号に電話をかけるOutbound方式があります。これはサービスを展開する国の電話料金、国際電話、国内電話の条件にあわせて使い分けることになります。
SMS認証だけでは、SMSが到達しなかったりとエラーをハンドリングした運用が必要になるため、補足的にIVRをリカバリ手段として提供するケースが多いです。
今回はSMS利用パターンで電話番号認証を作ってみました。
まずは、SMSを送信するためのサービスプロバイダーが何社かありますので、そのなかの一つを使ってみます。
SMS/IVR両方可能 無料枠が$20あり、開発用に自分に電話するだけであればある程度無料で利用可。
IVRのみ可能。日本のサービスなのでサポートが安心?
日本企業ですが、B2B型なので、個人サービサーがさくっとはじめるには敷居が高いかも。
ということで、Twilioの無料枠内で実験してみました。
SMS送信元番号で利用する電話番号はTwilio内で取得可能なので、それを使います。
Twilioで日本の番号を取得しようとするとSMSがno availableです。月$5で電話番号が取得できます。
なので、USでてきとーに取得します。USの番号はSMS利用可能です。USは月$1で電話番号が取得できます・・・。すごい。
次にSMS送信方法ですが、以下のAPIで簡単にSMSが送信できます。
curl -X POST 'https://api.twilio.com/2010-04-01/Accounts/{AccountSid}/SMS/Messages.json' \ -d 'From={+81のような国際電話形式の送信元電話番号}' \ -d 'To={+81のような国際電話形式の送信先電話番号}' \ -d 'Body={メッセージ本文}' \ -u {AccountSid}:{AuthToken}
更にTwilioは各言語のためのSDKを用意してくれているので、それを使えばさくっとAPI利用できます。
Javaでは以下の感じです。
// Install the Java helper library from twilio.com/docs/libraries import com.twilio.sdk.TwilioRestClient; import com.twilio.sdk.TwilioRestException; import com.twilio.sdk.resource.factory.SmsFactory; import com.twilio.sdk.resource.instance.Sms; import com.twilio.sdk.resource.list.SmsList; import java.util.HashMap; import java.util.Map; public class Example { // Find your Account Sid and Token at twilio.com/user/account public static final String ACCOUNT_SID = "SIDを設定してね"; public static final String AUTH_TOKEN = "{{ auth_token }}"; public static void main(String[] args) throws TwilioRestException { TwilioRestClient client = new TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN); // Build a filter for the SmsList Map<String, String> params = new HashMap<String, String>(); params.put("Body", "メッセージ本文"); params.put("To", "+81901234XXXX"); params.put("From", "+81909876XXXX"); SmsFactory messageFactory = client.getAccount().getSmsFactory(); Sms message = messageFactory.create(params); System.out.println(message.getSid()); } }
これを利用して、AWS環境でSMS認証サーバを構築しました。下図に処理フローをまとめてあります。認証用の一時データ保存領域としてMemcache(ElastiCache)を、QueueとしてSQSを使いました。
基本的には図に書いてある通りです。
補足としては... Twilioはショートコードを利用するとSMSの一括大量送信が可能になるのですが、今回はショートコードを取得していない関係上、Rate Limitで1送信元電話番号あたり1秒に1通しか送信できないという制限にひっかかってしまいます。そこで、どうせ月1ドルなので、スケールさせたい数だけ電話番号を取得して、取得した番号の数だけ、スレッドプールにSMS送信スレッドをプールする方式をとりました。それで購入した電話番号の数だけ並列送信が可能になります。そのためにQueueを利用しています。
- 実装環境
サーバ : AWS EC2
Memcache : AWS ElastiCache
Queue : AWS SQS
FW : Play2.0.4
言語 : Java
今更だけどEnumでSingleton
Java1.5からEnum型が追加され、定数の定義等に利用されていますが、EffectiveJava久々に読み直していたら、EnumでSingletonをやるのがおしゃれさんだよと書いてあったので、ブログにメモしときます。
周知の事実すぎるので、今更かよ感ありますが、知らなかった人は明日からやってみてください。
Java1.5より昔は以下の様にSingletonにしていたと思います。ここでは遅延初期化とか、スレッドセーフかどうかとか、シリアライズで複製されちゃうじゃんとかそういうことは分かり難くなるので、省きます。そうすると、こんな感じですね。
package singleton; public class TraditionalSingleton { private static final TraditionalSingleton INSTANCE = new TraditionalSingleton(); private TraditionalSingleton(){} public static TraditionalSingleton getInstance(){ return INSTANCE; } public void method(){ System.out.println("this is singleton"); } }
それをEnumでやると以下のようになります。Enumで定数化したときに、コンパイルすると実際は、public static final hogeになっているので、結局は同じことなんですが、可読性及び、シリアライズに対応していたり、リフレクションによるprivateコンストラクタ実行への防御とか、モロモロいい感じにしてくれます。
package singleton; public enum ModernSingleton { INSTANCE; public void method(){ System.out.println("this is singleton"); } }
実行は以下。
TraditionalSingleton.getInstance().method(); ModernSingleton.INSTANCE.method();
今まで知らなかったのが恥ずかしい。