弊社BPSで開発しておりますAndroid端末向け音楽試聴アプリMusicFlyを先ほど1週間ぶりにアップデートしました。
今回のアップデートにより
XperiaMiniに代表される低解像度端末や、反対の高解像度端末などへの対応を果たし
全スクリーンサイズ対応のアプリケーションとなりました。
技術者向けの参考情報
また、以前より原因が解明できずにいたバグのいくつか解決することができました。
そのうちの一つがAndroid1.5でのみタブを利用した画面で強制終了してしまうクリティカルなバグであり、
今回無事解決しました。技術者向けの詳しい話はこちらで。
該当端末を利用している方にはご不便おかけしました。
Androidで便利な、タブ表示。

タブ表示
これは、TabActivity, TabHost, TabSpecを使って簡単に実現できます。
src/MainActivity.java
package jp.bpsinc.android.tabtest;
import android.app.TabActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
public class MainActivity extends TabActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TabHost tabHost = getTabHost();
LayoutInflater.from(this).inflate(R.layout.main, tabHost.getTabContentView(), true);
//タブを作る
TabSpec tab1 = tabHost.newTabSpec("tab1");
tab1.setIndicator("tab1");
tab1.setContent(R.id.tab1);
TabSpec tab2 = tabHost.newTabSpec("tab2");
tab2.setIndicator("tab2");
tab2.setContent(R.id.tab2);
tabHost.addTab(tab1);
tabHost.addTab(tab2);
}
}
res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="TAB1" android:id="@+id/tab1" />
<TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="TAB2" android:id="@+id/tab2" />
</FrameLayout>
しかし、サンプルでなく複雑なアプリを作ると、Android 1.5 (SDK Version 3) の時だけ、StackOverflowErrorが発生するという現象に見舞われることがあります。
body : java.lang.StackOverflowError
at android.text.TextUtils#getChars:69
at android.graphics.Canvas#drawText:1278
at android.text.Layout#draw:337
at android.widget.TextView#onDraw:3921
at android.view.View#draw:5838
at android.view.ViewGroup#drawChild:1486
at android.view.ViewGroup#dispatchDraw:1228
at android.view.ViewGroup#drawChild:1484
at android.view.ViewGroup#dispatchDraw:1228
at android.view.ViewGroup#drawChild:1484
at android.view.ViewGroup#dispatchDraw:1228
at android.view.ViewGroup#drawChild:1484
at android.view.ViewGroup#dispatchDraw:1228
at android.widget.AbsListView#dispatchDraw:1319
at android.widget.ListView#dispatchDraw:2820
at android.view.View#draw:5944
….
こちらのGoogleGroupディスカッションでも触れられているようですが、Android 1.5では、タブの中に多重にネストしたコンテンツがあると、StackOverflowErrorが発生します。
手元のエミュレータで試したところ、12回ネストしたところで、エラーになりました。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent" android:layout_height="fill_parent" android:text="TAB1" android:id="@+id/tab1" />
<FrameLayout android:id="@+id/tab2" android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent">
<TextView android:text="TAB2" android:layout_width="wrap_content" android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</FrameLayout>
XMLをパーツに分けて書いていると、うっかり12回を超えてしまうことがあるので、注意が必要です。
解決策としては、ネスト回数を減らすか、Android 1.5を対象から外すという後ろ向きなものが最適ですね。
音楽試聴AndroidアプリケーションMusicFlyが先日公開されたバージョン2.2.0より、他のアプリから呼び出すことが可能になりました。
アーティスト名もしくはアルバム名を受け取ってその検索結果一覧画面を表示させることができます。

他アプリケーション →

MusicFly
右の画像はサンプルアプリケーションから【hoge】という単語でMusicFlyにアーティスト検索を起動させています。
サンプルアプリケーションのコードを載せておきますので、参考にしていただいて、ぜひとも素晴らしいアプリを開発してください。
// 入力フィールド
final EditText editText = (EditText) findViewById(R.id.text);
// アーティスト検索ボタン
final Button artistBtn = (Button) findViewById(R.id.btn_artist);
artistBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 入力フィールドのテキストを取得
String input = editText.getText().toString();
// アーティスト検索を実行
Uri uri = Uri.parse(String.format("musicfly://search?type=artist&name=%s", input));
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
});
// アルバム検索ボタン
final Button albumBtn = (Button) findViewById(R.id.btn_album);
albumBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String input = editText.getText().toString();
Uri uri = Uri.parse(String.format("musicfly://search?type=album&name=%s", input));
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
}
});
インプレッション表示の収入がないとか、クリック単価が低いとか、なかなか広告が出ないとか、アプリ・Web開発者側からするとお金にしづらいAdMobではありますが、すごく使いやすいので、使っている方も多いと思います。
Androidに完全対応した(少ない手間で組み込める)数少ない広告システムということもあり、色々お世話になります。
今回は、AdMobの登録~Androidで広告を表示してみるまでの簡単な手順と、注意点です。
登録
http://jp.admob.com/から登録します。
アプリケーションを追加
ログインしたら上部タブから「マーケットプレイス」を選択します。
「キャンペーン」は広告主用のものなので、開発者は「サイト及びアプリケーション」を開きます。
「サイト・アプリケーションを追加」ボタンをクリックし、「Android」ボタンをクリックすると、アプリ情報入力画面になります。
ここに入力した内容に間違いがあっても、広告が表示されない、ということはありませんが、なるべく正直に入力しましょう。
SDKのダウンロード
アプリの登録を完了すると、SDKとマニュアルPDFがダウンロードできるようになります。
SDKはダウンロード・解凍して、適当な場所に置いておきます。同フォルダにAdMobSDKのリファレンス(JavaDoc)もあるので、是非参考にしてください。
これ以降の手順は、PDFに従うのが一番です!!
広告が表示されないとき
ということで、解説を途中で放棄して、「PDF通りにやったのに広告が表示されない」という場合のチェックポイントです。
基本的に、ManifestにパブリッシャーIDを入れて、attrs.xml、表示したいViewのXMLを書けばOKなのですが、以下の場合、広告が表示されません。
・ネットワーク接続できない
この場合、広告は取得できず、何も表示されません。
・表示領域が狭い
AdViewのサイズは、横幅320px以上である必要があります。それより小さいと、何も出てきません。
・ログに「no ads are available」と出る場合
広告がありません。AdMobは広告主に比べて開発者が多いのか、広告が見つからないことが良くあります。
この場合、Webの管理画面から「自社広告」を入れておけば、何も見つからないときに自社広告を表示することができます。
・単に遅い
Viewが表示されてから広告が表示されるまで、数秒かかるのが普通です。
上部に入れる場合は、48pxくらいの高さを確保しておいた方が、レイアウトが変わらなくて良いかもしれません。
動的にキーワードフィルタする
Web管理画面から、表示する広告のジャンル、キーワードフィルタ等を設定することができます。
しかし、アプリのコードから指定すれば、現在表示しているコンテンツにマッチした広告を表示することも可能です。
以下のようなコードで可能です。
AdView ad = (AdView)findViewById(R.id.ad);
ad.setKeywords("music");
とても多機能なAdMob、うまく使いこなしたいですね。
Androdではインテントという仕組みによって、アプリケーション間でデータを渡すことがきるのはご存じのとおり。
インテントに画像ファイルを詰めて、Gmailに渡せば新規メールの添付ファイルとなるし、
Twitterクライアントに渡せば画像をつぶやいてくれたりできます。
では具体的にどう書けばいいのかというと、
File file = new File(ファイルパス); // 他アプリに渡すファイル
Intent intent = new Intent(Intent.ACTION_SEND); // データーを送信するインテント
intent.setType("image/png"); // データタイプの指定
intent.putExtra(Intent.EXTRA_SUBJECT, "件名");
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
startActivity(intent);
でいけます。
開発中は問題ないのに、いざリリースしてデータをたっぷりと詰め込んだら、RejectedExecutionExceptionが発生して強制終了、ということがあります。
これは、AsyncTaskにたくさん詰め込みすぎの場合などに発生します。
AsyncTaskでは内部的にキューを持っていますが、このキューサイズを超えるタスクをexecuteすると、ブロックされずに例外が発生します。
ということで、
・catchして後でやりなおす(すぐにやり直したら意味が無い)
・catchして無視する(リスト表示程度なら、無視してユーザにリロードさせた方が良いかも)
などの対策をすれば良さそうです。
久々の技術ネタです。
Androidでデバッグ用のログを出力する際には
Log.d(String tag, String msg);
としますが、これで出力しているログは、このまま何もしなければリリース時にユーザーさんからも見えてしまします。
やはりデバッグ用のログが見えてしまうのは都合が悪いと思うので、
リリース時に少しのコストでこれを解消するTIPSをご紹介します。
方針としましては、
マニフェストファイルのis_debuggableがtrueの際にデバッグログの出力をオフにすることとします。
public class DeployUtil {
/**
* マニフェストファイルを読んでデバッグモードかどうかを取得
*/
public static boolean isDebuggable(Context context) {
PackageManager manager = context.getPackageManager();
ApplicationInfo appInfo = null;
try {
appInfo = manager.getApplicationInfo(context.getPackageName(), 0);
} catch (NameNotFoundException e) {
return false;
}
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == ApplicationInfo.FLAG_DEBUGGABLE) {
return true;
}
return false;
}
}
public class Katazou extends Application {
private static boolean isDebuggable;
@Override
public void onCreate() {
// デバッグモードか調べる
isDebuggable = DeployUtil.isDebuggable(getApplicationContext());
}
}
public class LogUtil {
/**
* デバッグログを出力する マニュフェストファイルでデバッグモードになっていなければ出力しない
*/
public static final void d(String tag, String msg) {
if (Katazou.isDebuggable()) {
Log.d(tag, msg);
}
}
}
あとは、ソースコード中のLog.dを一括置換でLogUtil.dとすれば、
リリース時にis_debuggableをtrueにするだけで簡単にデバッグログをオフにすることができます。
参考
Android で自動オフできるログ出力
Xperiaの解像度は480*854です。
しかし、以下のコードで解像度を取得すると、320*569と返ってくることがあります。
Display disp = getWindowManager().getDefaultDisplay();
int dispWidth = disp.getWidth();
int dispHeight = disp.getHeight();
こんな時の検索ワードは、ずばり「Xperia 569 320」ですね。
発売直後に既に回答が議論されていました。
http://groups.google.co.jp/group/android-group-japan/browse_thread/thread/5fad044c9b0429dd
要するに、minSdkVersion=”4″にしろということですね。
まあ、Android 1.5の機種はほぼ絶滅なので、特に問題はないんですが、なんだこれ・・・・
Javaのパッケージは、階層構造に見せかけた単なるドット区切りの名前ですが、少なくとも見た目は階層にしたいですね。
Eclipseの設定がたまに変わってしまうことがあるのですが、階層表示・フラット表示の切り替えは↓です。

当然ながら、言語ごとに真偽判定(true/false)の基準が違います。
特にスクリプト系の言語では、明示的に型を指定しないことが多いので、たまに問題になります。
たまたま手元にあった環境で、どんな値が真に判定されるのか、というのをまとめてみました。
新しい言語に突撃するときのメモ代わりにでもして頂ければ幸いです。
検証コードイメージ
a = 0
if a
print 'true'
else
print 'false'
end

凡例:
× は、コンパイルエラー
ー は、言語仕様にその値が存在しない
備考:
*”" というのは、ナル文字の値を指す(\0なので、実体はゼロという整数値)
array[] というのは、各言語での空の配列を指す
※1 C言語にfalseは存在しない
※2 配列のアドレスを指すため、真と評価される
※3 noticeが発生するが、設定や@で抑制可能
こうしてみると、やはりPHPは変態です。’0′ をfalseと判定する言語はあまりないですね。Webに特化しているのがよくわかります。$_GETなどは文字列で渡ってくるので、確かに手抜きに便利仕様です。
空配列をfalseと判定するのが、PythonとPHPというなんだかおもしろい組み合わせになりました。
良く忘れるけど、rubyはゼロという数字をtrueに判定するので注意が必要ですね。
結局、ミスのしようが無い Java / C# は安全で好きです。
また、ゼロは false、ほかは true と割り切ったC言語も、シンプルで好きです。