2012年10月31日水曜日

Nexus 7でのAppWidgetのサイズ

Nexus 7 
(Resolution 800*1025,DPI 195dpi*201dpi ( tv dpi ) )

でのAppWidgetを設置する時、
xmlで記述する
<appwidget-provider>の属性 minWidthやminHeightで設定するDPIとその設置後のセルの大きさが以下のガイドラインに書かれてるものは違ったのでメモします。
App Widget Design Guidelines
Determining a size for your widget



結果だけ書くと、上記のページの表の表記に従うと

Portrait (縦長使用時)
n Columns  =  100*n- 120 dp(minWidth
n Rows      =    70*n- 30 dp(minHeight ) 表のまま


Landscape(横長使用時)
n Columns  =    70*n- 30 dp  (minWidth ) 表のまま
n Rows      =    87.9*n- 104 dp(minHeight ) 



という謎な感じになってました。

dpi とAppWidgetの関係について理解しきれてないところがあるかも知れませんが、
とりあえずメモ代わりに。


また、いろいろと面倒になりそうですね。。

2012年8月4日土曜日

ただの願望。。

Androidアプリって、開発者側から古いバージョンを使ってるユーザーへ
の強制アップデートのオプションできないかなぁ。。

特にWebのAPIを使ってデータ取得するようなアプリ作ってると、過去への互換性や安定性
が正直しんどい。


毎回起動時に自分でサーバに最新バージョンを取得して、更新を促すっていうのが普通なんだろうけど、実装が面倒だなっていうだけ。
(本来はしないといけないんだろうけど)


確かに、そういうオプションがあると、
最初は普通のアプリに見せかけて、悪質なアプリ化されてしまうよなこともできてしまうけど。。。

うーむ。


2012年7月26日木曜日

AsyncTaskを継承してるクラスでのstaticメソッド呼び出しの罠


(ソースコード見難いと思います・・すいません)

(API レベル4以上で再現する現象・・のはず)


staticメソッドを持った AsyncTaskを継承したクラス(ExtendsAsyncTask)を作成。 


public class ExtendsAsyncTask extends AsyncTask<Void, Void, Void> {
public static void method() {
}
@Override
protected Void doInBackground(Void... params) {
return null;
}
}
上のクラスの staticメソッドに別スレッドでアクセスすると
 public class TestActivity extends Activity{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() { @Override
public void run() {
ExtendsAsyncTask.method();
}
};
new Thread(r).start();
}
}
なんと ExtendsAsyncTask.method(); の呼び出しで
 java.lang.ExceptionInInitializerError が発生してアプリが落ちてしまいます。


ちょっと原因をさぐってみました。

公開されてるAndroidのソースコード(今回は4.0.4)のAsyncTaskをみて


607行目
private static class InternalHandler extends Handler {
・・・
}
さらに

190行目
 private static final InternalHandler sHandler = new InternalHandler();
が怪しいと判断 (判断するまでに至った理由は省略)

ちなみに static フィールドはClassがロードされた時に実行される (参考:static イニシャライザ)

ここでさきほどのExtendsAsyncTaskを以下のように変更
public class ExtendsAsyncTask {
public static void method() {
} 
private static InternalHandler handler = new InternalHandler();
public static class InternalHandler extends Handler {
@Override
public void handleMessage(Message msg) {
}
}
}
これでもおなじく



 java.lang.ExceptionInInitializerError が発生しました。

さらにコンパクトに下記の様にしても同様です。

public class ExtendsAsyncTask {
public static void method() {
}
private static Handler handler = new Handler();
}

つまりHandler 周りが怪しい?
( Handler さんには昔、かなりお世話になっておりました。。)



そして次にActivity自体を以下のようにしてみます。
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() {
@Override
public void run() {
new Handler();
}
};
new Thread(r).start();
}
}
 java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

という例外が発生します。


ほほう。。

ちょっとググったところ、以下のページがとても参考になったので掲載しておきます。

つまり内部動作を理解すると
各スレッドで Looper.prepare() を呼び出す前にnew Hander()を呼び出すな ということです。


(この記事の発展にもあるように UIスレッド場合は、フレームワーク側が呼び出している)

つまり
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() {
@Override
public void run() {
Looper.prepare();
new Handler();
}
};
new Thread(r).start();
}
}
こうすると落ちなくなります。

つまり、
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() {
@Override
public void run() {
 Looper.prepare();
ExtendsAsyncTask.method();
}
};
new Thread(r).start();
}
} 
元のコードはこうしても落ちなくなります。


そして、注目したいのは
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
new ExtendsAsyncTask();
Runnable r = new Runnable() {
@Override
public void run() {
ExtendsAsyncTask.method();
}
};
new Thread(r).start();
}
}
というように UIスレッド上でExtendsAsyncTaskのインスタンスを生成すると、
アプリが落ちることはありません。

これらが(Looper.prepare()をされたあとの)UIスレッド上で実行されるためです。
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
・・・
}

筆者の場合

ExtendsAsyncTask.method();を呼び出す前に

ほとんどUIスレッド上でnew ExtendsAsyncTask();
でインスタンスを生成していたのですが、

ある処理の時、ExtendsAsyncTaskのインスタンスを一度も生成せずに
ExtendsAsyncTaskをクラスをロードせずに)
直接 ExtendsAsyncTask.method();を別スレッドで実行していたことが判明し、
問題が発覚し、原因を追求しました。


他の状態に依存しない public static メソッドは
どこで定義しても大丈夫だろうという思い込みがあったのは
確かです。


まぁAndroidのバグではないと思いますが、つまづきやすい仕様というか 
なんというかという感じですが。

何かの参考になっていただければ幸いです。


まとめ

AsyncTaskを継承してるクラスのpublic staticメソッド呼び出しはUIスレッド
で行いましょう。

(というまとめでいいのかわかりませんが?)

私の設計が悪いのでは・・ということは、言ってはいけません