ラベル Android の投稿を表示しています。 すべての投稿を表示
ラベル Android の投稿を表示しています。 すべての投稿を表示

2015年7月23日木曜日

Androidに搭載のセンサーが、増えている

Xperia Z4(au)に搭載されているセンサを一覧で出力してみました。

こんなのついてるの?と思うセンサまでありました。
(下記のTYPE_*は、AndroidのSensor クラスに定義されている値です)

TYPE_ACCELEROMETER: 加速度センサ
TYPE_GAME_ROTATION_VECTOR: ゲーム用のローテーションセンサ
TYPE_GEOMAGNETIC_ROTATION_VECTOR:ローテーションセンサではあるがジャイロを使わない。低消費電力
TYPE_GRAVITY:重力センサ?
TYPE_GYROSCOPE:ジャイロセンサ。
TYPE_GYROSCOPE_UNCALIBRATED
TYPE_LIGHT:照度センサ
TYPE_LINEAR_ACCELERATION
TYPE_MAGNETIC_FIELD:方位角センサ
TYPE_MAGNETIC_FIELD_UNCALIBRATED
TYPE_ORIENTATION:端末の回転センサ
TYPE_PRESSURE:気圧センサ
TYPE_PROXIMITY:近接センサ
TYPE_ROTATION_VECTOR:傾きセンサ?
TYPE_SIGNIFICANT_MOTION:モーションセンサ?
TYPE_STEP_COUNTER:歩数カウンタ
TYPE_STEP_DETECTOR:一歩を検知するセンサ
UNKNOWN:22:ログを出してみると、チルトセンサとなっている/Sensor name="Tilt Detector", v
UNKNOWN:25:ログを出してみると、ピックアップジェスチャとなっている/Sensor name="Pick Up Gesture",

気圧センサや、歩数カウンタがあるというのが驚きです。
せっかくなので、テストアプリ作りつつ、動作を見てみました。
ソースはgithubにおいてあります。

起動すると、搭載されているセンサの一覧を取得し、表示します。


アイテムを選択すると、センサから返ってくる値を表示します。こちらは気圧センサ。


気象庁によると、計測した7/23日18:00の東京の気圧は1007.8hPaだったようなので、わりと正確な値が出ているようです。
IoTが話題に上がることが多い昨今ですが、端末センサでデータをとって、何か出来るかもしれませんね。

2012年5月21日月曜日

auスマートパス利用中

ATOKも、auスマートパス対象アプリになってました。
NAVITIMEもスマートパスの定額アプリなので、ナビウォークの月額会員切って、
スマートパスに買えてみました。

こういうサービスって、助かるな、と・・・

2012年3月12日月曜日

Xperia acroとMacを接続する!アップデートも、iTunes連携も!

こんなのあったとは、知りませんでした。

Bridge for Mac software

以下のことが出来るようです。
・ソフトウェアアップデート。
・iTunesから、Xperiaへの音楽のインポート。
・iPhotoから、Xperiaへの、写真のインポート。
・Xperiaから、iPhotoへの、写真のインポート。

説明には、以下のようにあるので、基本的にはXperia<->Mac感で、双方向に、音楽と写真、動画が送り合えるみたい。
---
With Bridge for Mac you can update your Android phone software, transfer photos, music, videos and podcasts between your phone and iTunes, iPhoto or a custom folder on your Mac. 
(拙約)
 Bridge for Macで、Android phoneのソフトウェアアップデートが出来ます。xperiaとiTunes、iPhoto、もしくはMacのフォルダの間で、写真、音楽、ビデオ、podcastが転送できます。
---

私のXperia acroでも使えるようになりました。現在iTunesと音楽同期中・・・

「使えるようになりました」と書いたのは、二日間トラブルと格闘していたからです。

手順通り、ダウンロードした.dmgをアプリケーションフォルダに放り込み、acroと接続・・・してみましたが、Sony Bridge for macから、Xperiaが認識されませんでした。
MTPモードにしたり、MSCモードにしたり、いろいろやってみたんですが、全然認識せず。

最終的に、なぜうまくいったのか、わからないんですが、MSCモードで、Sony Bridge for macがXperiaを探してるときに、FinderからXperiaのSDカードを見てたら、なぜか繋がるように・・・
デバッグしようとして、adb logcatしていたのが良かったのか・・・?

繋がらなかったときとの違いは、MSCモードにして、FinderでSDにアクセスしていたのと、adb logcatしていたぐらいです。
しかし、二回目は何もしなくても認識されました。

よくわかんないです。が、とりあえず使えているので、様子見・・・です。



2012年3月3日土曜日

Xperiaをひかり電話の子機にする

公式サイト:スマホdeひかり電話

全く知りませんでした。サイトの手順通りに設定しただけですが、簡単にひかり電話の子機になりました。
手順はAGEphoneダウンロード→「ひかり電話」をタップするだけ。

VPNサーバを立てれば、外出先でもひかり電話として発着呼が可能みたい。

これはすごい・・・けど、べんりなのかな・・・?
スマフォをSIMなし・WiFiオンリーで運用するとかしてる人は、便利か。

あ、ちなみにiPod touchでも使えます。





2012年2月11日土曜日

Androidスマフォをモバイルバッテリーとして使う

そうです、Androidで、たとえばモバイルルータの充電が出来ちゃいます。

論より証拠、充電中の写真。


モバイルルータの方が充電中表示になってます。

原理は簡単です。XperiaはUSB On-The-Goに対応してますので、XperiaはUSBに給電してる→それを外部機器につなげば・・・。
Xperiaではなくても、Android 3.0からは標準でUSB On-The-Goに対応してますし、2.3.4でもメーカによって対応してるようなので、Android機であれば、同じようなことが出来るはずです。
(試す場合は、事前にお持ちのスマフォのUSB On-The-Go対応状況を調べておきましょう)

用意したものは、USB On-The-Go対応のケーブル。MicroUSB(Type-A)のケーブルを用意すれば良いです。私が使ったのはELECOM MPA-EMA015BK(写真で使ってるものです)。

私はモバイルバッテリーも持ち歩いてるので、使うかな?とか思ってましたが、早速今日、お世話になりました。

私はパケ代節約のためにXperiaの3G使わないようにしてます。ですので、ルータの方の電池がなくなって、Xperiaだけ生きてても困るので、そんなときに、最終手段的にこれ使えると、結構助かります。


2012年1月18日水曜日

DATA08Wで位置ゲー

スマフォ版のmobage/GREEのしろつくとか、位置登録使おうとしたとき、WiFi使ってると「3G回線のみONにしてください」と言われます。

ですが、DATA08Wだと、Wimax/3Gどちらをつかんでてもちゃんと位置登録できます。
同じWImaxのモバイルルータ、Aterm WM3500Rだとできなかったんですけどね。

・・・KDDIのIPを判断してるからかなぁ。
(DATA08WはKDDIのMVMO)


2012年1月16日月曜日

internalメソッドの呼び出し

前のエントリに関連して、端末をrebootできないか調べてるときに、internal Class/methodの呼び出しも調べたので、ついでにエントリしておきます。
AndroidフレームワークではIActivityManager.shutdown()をCallして電源OFFを開始しているので、このinternal methodをアプリから直接呼び出すコードを書きます。

もちろんpermissionがない(というかrootが必要)ということで全く動作しません。
あくまでinternalなmethodの呼び出し方、ということで、書いておきます。
(internalの呼び出しはデメリットが多いので使うことないかもしれないけど)

なお、39行目で、メソッドを探すためにループしてますが、getMethod()をつかって一発で取り出すことも出来ます。

package test.alarm;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import android.app.Activity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class AlarmTestActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        Button button = (Button)findViewById(R.id.shutdown);
        
        button.setOnClickListener(new OnClickListener()
        {

   @Override
   public void onClick(View v) {
    ClassLoader cl = getClassLoader(); 
    try 
    {
     Class ActivityManagerNative = Class.forName("android.app.ActivityManagerNative");
     Class IActivityManager = Class.forName("android.app.IActivityManager");

     Method getDefault = ActivityManagerNative.getMethod("getDefault", null);
     Object am = IActivityManager.cast(getDefault.invoke(ActivityManagerNative, null));

     Method[] shutdown =  am.getClass().getMethods();//, Integer.class);
     Method sd = null;
     for (int i = 0; i < shutdown.length; i++)
     {
      if (shutdown[i].getName().equals("shutdown"))
      {
       Log.d("method", shutdown[i].getName());
       Log.d("method", shutdown[i].toString());
       sd = shutdown[i];
       
      }
    }
           if (am != null && sd != null) {  
            sd.invoke(am, 10);
     //am.shutdown(MAX_BROADCAST_TIME);    
           }  

    } catch (ClassNotFoundException e)
    {
     e.printStackTrace();
    } catch (NoSuchMethodException e)
    {
     e.printStackTrace();
    } catch (IllegalArgumentException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    } catch (IllegalAccessException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    } catch (InvocationTargetException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
    
   }
         
        });

   }
   
}

AlarmManager.RTC_WAKEUPのwakeup?

AlarmManager.RTC_WAKEUPの説明を見ると、こう書いてあります。

Alarm time in System.currentTimeMillis() (wall clock time in UTC), which will wake up the device when it goes off.

"wake up the device when it goes off."ということは、Alarmをセットすることで、指定時刻に端末の電源をONできるのか?と思い、ググってもよくわからないので調査。

package test.alarm;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class AlarmTestActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        

        Button button = (Button)findViewById(R.id.alarm);
        button.setOnClickListener(new OnClickListener()
        {

   @Override
   public void onClick(View v) {
    AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(ACTION_WAKEUP);
    intent.setAction(ACTION_WAKEUP);
    PendingIntent wakeupPendingIntent = PendingIntent.getBroadcast(AlarmTestActivity.this, 0, intent, PendingIntent.FLAG_ONE_SHOT);

    am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (61 * 1000), wakeupPendingIntent);
    registerReceiver(alarmReceiver, alarmFilter);
    Log.d("alarm", "set alarm.");
   }
         
        });
    }
 private final BroadcastReceiver alarmReceiver = new BroadcastReceiver()
 {

  @Override
  public void onReceive(Context context, Intent intent) {
   if (intent.getAction().equals(ACTION_WAKEUP))
   {
    Log.d("ALARM", "ACTION_WAKEUP");
   } else {
    assert true;
   }
   
  }
  
 };
    public static final String ACTION_WAKEUP = "wake_up";
 private static final IntentFilter alarmFilter = new IntentFilter();
    static {
     alarmFilter.addAction(ACTION_WAKEUP);
    }
    
}


このコードで、Alarmをセット。端末を起動した状態であれば、ちゃんとアラーム通知が来るのが確認できたので、Alarmのセット→Alarm発火前に電源OFFで試す。

結果、端末起動してくれませんでした。


"wake up the device when it goes off."の"goes off"は、端末のスリープのことみたいですね。残念。

2011年12月30日金曜日

DATA08Wの電池持ち(2)

この記事で、スペックより電池持ちがよいっぽいと書いたんですが。

取説を見ると、やっぱりWimax通信はSleepするんですね。






Androidの場合、Googleアカウントを定期的に同期しているし、また私の場合キャリアメールを結構な頻度で受信してますが(迷惑メールが多すぎて)、それでもちゃんとSleepに入っているようなので、ほんとにずっとWimax通信を使ってない限り、この記事で書いたぐらいの時間(7時間近く?)は、普通に持つような気がします。

まぁとはいえ、一日中外出の時とか、電池切れされるとかなり困るので、予備バッテリーが外部バッテリーをもっといた方が幸せかなと。

ちなみにわたしはこれを使ってます。



でも、いま非接触充電の外部バッテリーもあるみたい。結構気になる・・・




2011年12月26日月曜日

DATA08Wの電池持ち

ちゃんと調査してないんですが・・・

本日10:30ぐらいから、DATA08Wの電源入れて、XPERIAとiPod touchがWiFiで常時接続、という状態で放置していたのですが、(すっかり忘れてて)19:00すぎに見たところ・・・

あれ?まだ電源切れてない。

電池残量はピクト表示でラスト1。

スペックでみると、こんなにもつはずがない。

連続通信時間WiMAX: 約270分
3G: 約310分

不思議に思って、DATA08WでWimax接続時間を見ると、45秒になってました。
無通信の時は、Wimaxを切断してるってことですかね?

それはそれでありがたい。スペック見て「充電 気をつけないと」とか思ってたので、思わぬ発見。

でももう少し様子見よう。

DATA08Wの通信速度

せっかくなので、簡単に速度測定してみました。

使ったサイトは価格.comのスピードテスト。
クライアント側はCore 2 Duo 2.26GHzのMacBookPro(Mid 2009)。
MacとDATA08Wの接続はUSBにしています。

まずWimax。電波の強さは、最大5本表示のところ、2本。







まずまず。続いて3G。こちらの電波強度は最大5本表示のところ、2本。







こっちは、思ったより出ている印象。

ググってみると、Wimaxは6〜7Mbpsはでるみたい。3Gは(こちらの測定のほうが)早い結果が出たもよう。

価格.comの記事だとWimaxで13.1Mbpsとか・・・(これはPR記事だから、アレですが)

この結果であれば、全く問題ないですね。へたなADSLより速いかも。

2011年12月25日日曜日

昨日の記事の顛末

au DATA08Wがつながらなかった件。
au shopに行ってみてもらったところ、


「電番情報が書かれてません」


・・・をぃ。


販売した店じゃないと書き込むことが出来ないとのことなので、再度販売店へ。
15分ほど待たされたけど、無事、接続を確認することが出来ました。

たのみますよ、○マ○さん。


2011年12月24日土曜日

au DATA08W契約・・・が。つながんね〜。故障?障害?

au DATA08Wを契約しました。

いままでWimaxを契約してました。Wimaxは、通信速度が3Gと比較にならないぐらい速くて、かなり快適に利用してました。
外出してるのに、WiFiでつないでるような速度。へたなWiFi Spotなんかより、安定してて充分な速度がでていて、満足できました。

Wimax唯一の不満が、やっぱりエリアで、都心部を出ると、ほとんどつながらない。
結局、そういうときはスマフォの3Gを使わざるを得なくて、なんか結局、毎月両方のパケ代を払っていたことが、ちょっと微妙だなーと。
#14M Byte程度の通信料で上限額行くのもどうかと思うけど・・・>au/UQ Wimax

で、そこに3G+WiMaxが使えて、月額 約5000円で、3G/WiMaxのいいとこ取りできるDATA08W・・・こりゃ買いだ!!と。PCでも、iPod touchも、一台でつながっちゃうし。

と思って、価格調べまくって契約。新規0円で、二年縛りですが、だいたい縛りなしのところは端末台1万円なので、まぁ解除料とほぼ同じなので、新規0円を選択。しかも話聞いたら商品券¥10000キャッシュバック!

・・・が。

家に帰ってきて、いざ接続〜、とおもったら、あれ?ナンカ、全然つながらない・・・????
ず〜っと、接続中のアイコン表示のまま、接続状態にならない・・・あれ〜??????
いままでずっと使ってたから、ウチは間違いなくWimax圏内なんですが。

au.net(¥525)使うのがイヤで、自分のプロバイダのPacketWinの設定とか先にしちゃったのが悪かったのかもしれないけど・・・
オールリセット(設定画面から出来るヤツ)しても、状況変わらん。

うーん。なんかインフラ側の契約関係のバッチ処理が停滞してるのか?とか思うが、障害情報は特になし。
3Gのみ・WiMaxのみ、どっちもダメ。

つーことで、明日、朝もう一度試してみて、ダメならショップに行ってきます。

新品交換ぐらいですめばいいんですが。

(12/25)結果、こんなでした

2011年11月23日水曜日

Android ICS for x86

ICS(Android 4.0)から、デフォルトでx86向けビルドがサポートされてるの・・・?
とりあえずやってみるか。

やりかたは。とりあえずググっときましょう

2011年11月22日火曜日

強制終了を考慮したServiceの設計

#更新予定とか書いといて、結局一ヶ月が経ちました

以前書いたとおり、Serviceは強制終了させられます。(要因などはこちらのエントリーで)
ではどうするか、ということですが、まずはAndroidのDevelopers Guide。
Services | Android Developers  (こちらで日本語に訳されてます)

The Basicsの最後の方を見ると、以下のように書いてます。(日本語サイトですとAndroid システムはメモリが少なくなり・・・のくだり

The Android system will force-stop a service only when memory is low and it must recover system resources for the activity that has user focus. If the service is bound to an activity that has user focus, then it's less likely to be killed, and if the service is declared to run in the foreground (discussed later), then it will almost never be killed. Otherwise, if the service was started and is long-running, then the system will lower its position in the list of background tasks over time and the service will become highly susceptible to killing—if your service is started, then you must design it to gracefully handle restarts by the system. If the system kills your service, it restarts it as soon as resources become available again (though this also depends on the value you return from onStartCommand(), as discussed later). For more information about when the system might destroy a service, see the Processes and Threading document.

 ポイントは以下:

  1. 長時間Serviceを起動してると、システムに強制終了させられやすくなる
  2. 自分のServiceが強制終了させられても、SystemがServiceを再起動してくれる。
  3. onStartCommand()実行後、戻り値を何にするかで再起動の挙動が変わる。

3がポイントですが、onStartCommand()で特定の戻り値を設定することで、再起動時に、最後にServiceに飛んで来たIntentを再配信することが出来ます。(戻り値に何を設定するかはAPI仕様書や、上記のServiceのDeveloper guideを参照)

つまり、強制終了されてもいいように作るには、
  1. ServiceはIntentによるイベントドリブンな形で作る
  2. ServiceがIntentを受けたら、そこからの一連の処理は受けたIntentで一意に決まるようにする

こうしておけば強制終了されてもIntentが再配信されるので、そのIntentを受けて処理を再開できます。
処理もIntentの種別やデータで一意に決まるので、復帰も問題ないです。

もちろん、Serviceは連続的に動かすものなので、状態を持たせたりして、一意に決められない場合もあります。というか、こっちの方が当たり前のような。

今回試した方法では、Serviceの状態変数(State patternとか使ったらClass)や、保持しているデータをIntentを受けるたびにファイルシステムに書き出すようにしました。
Intentを受けるたびに、読み出しをすることで、再起動後も状態とデータを保持できます。

この方法ですと、副次的なメリットもあって、Low batteryでの強制終了や電池パックがはずれて・・・みたいなときでも、復帰が出来るようになります。もちろん、状態保存中に電池が外れて、みたいなパターンのフォローは必要ですが。

デメリットは、毎回ファイルシステムにアクセスするので、電池を食うのと、処理が重くなること。このあたりは、読み出し・書き出しをたとえば3回に一回にするとか、読み出しも再起動時のみ、にするなどの対策をすれば良いかと。

とりあえず、この方法で、しばらく評価してますが、問題はなさそうです。

2011年9月22日木曜日

XPERIA acro(au IS11S) アップデート

XPERIA acroのアップデートが出ましたので、早速アップデートしました。
発表されてすぐアップデートしましたが、みんな殺到したのか、メールアプリとLISMOなどのダウンロードがなかなか出来なかったこと以外、特に問題なく終了しました。
auOneIDの設定あたりが、ちょっとわかりづらかったですが。その程度です。

ちょっと感想をば。

【メール】
他のauの機種で、auのメールアプリが使いづらいという噂を聞いていたので、ちょっと心配してましたが、とりあえず、まぁいいんじゃないか、という感じでした。結構軽いし。むしろ、ガラケーよりデコメが使いやすそうで良いかと。・・・デコメってほとんど使わないですけど。

基本的に、受信メールはAndroidのEメールアプリ(auoneメール)で見てますが、ezwebメールでも、「アドレス帳登録外」を独立したフォルダに保存できるので、迷惑メールをそっちに放り込めるのも助かります。

ただフォルダに分けただけだと、受信通知がたくさん来てうっとーしいので、自動受信もOFFにしてます。「Eメール設定」>「受信・表示設定」>「メール自動受信」をタップです。

残念ながら、auoneメールから、返信だけezwebメールアプリで作成、は出来ず。まぁ、当たり前か・・・

【LISMO】
あまり使い込んでないですが、起動時にしょっちゅう、「SDの楽曲データを読みに行ってます」、みたいなメッセージで待たされるので、これは常用無理だなと・・・ROMが少ないのもあるので、削除候補。ダウンロードアプリにしてくれてありがとう、auさん、ソニエリさん。

ちなみに楽曲データメッセージの件、LISMOアプリ終了を、BackキーではなくHOMEキーで行うと、次回起動時にメッセージが出ません。
つまり、見事にここで書いたような動作をそのまましていると。・・・これ、わざとですよね?
すっごく、ユーザ的にわかりづらい動作ですが、わざとですよね?

まぁ、HOMEキーで終了(ほんとは中断)でも、LowRAMやなんやの条件で、またメッセージ出ると思いますが。
RAMが少ないのもあるので、削(以下自粛)。


【ROM/RAM不足?】
ROMは・・・元々少ないのが、さらに少なくなったようです。
現時点で、空き容量46M・・・これでも、ほとんどのアプリをSDに出してるんですが。

だんだんアプリ入れられなくなってくる・・・

RAMは・・・ちゃんと調べてないですが、ちょいちょいLowRAMになっているように見えます。
ブラウザで重いページ開くと、前のアプリの復帰が遅くなったり、BGアプリが止まってったり・・・
そのせいかわかりませんが、全体的に重くなったような印象。もたつくことが多々あり。

次の機種は、ROMもRAMもケチらないでほしいなぁ。ROM/RAM増やすとコストが上がるのわかりますが、そのためにユーザ体験を犠牲にするのでは、何がしたいのかわからなくなりますよ。

・・・ちと文句が多いような気がしますがw iOSとかでもそうですが、定期的にアップデートあるのは、やっぱりうれしいし、楽しいですね。良いところも悪いところもありますが、やはり楽しみにしちゃいます。

2011年9月20日火曜日

やっぱりヘンだ、Service

少し前のエントリーで、Serviceの強制終了について、書いてみました。

しかし、それでも、やっぱりよく終了させられてる・・・

Serviceをバックグラウンドで動かしてても、フォアグラウンドのブラウザで、重いページとか開くと、黙って止まります。
(startForeground()で設定した)Notification領域の表示も、無言で消えます。
前にソース調べたとき、LowMemory状態でもServiceをKillするようだったので、少し調べたら、こちらのページが。

落ちないServiceの作り方

想定通りというか、やはりServiceは、Killされる前提で設計・コーディングした方がよい、ということになりますorz
勝手にKillされるって、どーいうことよ・・・

まだ試してはいないんですが、一つの解決策として、状態を持たないようなServiceであれば、完全にイベントドリブンにして、何らかのイベント・呼び出しにおける処理が、それぞれ独立して、完結する形で作り込むこと。

もう一つは、状態を持つようなServiceであれば、状態と関連するデータを、Javaのシリアライズ機能で、都度ファイルシステムに保存する。そして次のイベント呼び出しごとにシリアライズしたデータを読み出して、処理を再開するとか。

そんな方法も採れるのかな、、、と思案中。あまりスマートじゃないけど。

Androidって、Javaで取っつきやすくて、サンプルコードもいっぱいあるので、作り出すのは簡単ですが、こういった特有の、怪しい動作がたくさんあって、品質あげたり検証していくのって、本当に、大変・・・。

良くも悪くも、クラウドが主体(というか、前提?)なPlatformのような気がしています。
クラウドなら、基本的にクラウドに対する要求と、その応答で、クライアント側はだいたい終わりですからね。

2011年9月17日土曜日

Android アプリの終了処理:Low BatteryとShutdown(2)

前回の続きで、こんどはACTION_SHUTDOWNについてです。

前回のエントリのサンプルコードで、LowBatteryの場合と、電源キー押しでのShutdownの場合に、ACTION_SHUTDOWNのBroadcast Intentが投げられることがわかりました。
これを拾って、終了処理をすればよいわけですが、ふと疑問に思います。

「終了処理で、無限ループに陥ったら、どうなるの?」

前回のサンプルコードで、onShutdown()に無限ループのコードを入れたところ・・・端末は正常に?終了しました。
onStop()/onDestroy()にも入れてみましたが、同じ動作です。

なんだか怪しいので、こちらを参考に、Shutdown処理を追ってみました。

ShutdownThread.javaの213行目(Gingerbread 12/23版の場合)で、Broadcast Intentを発行しています。
以下、抜粋。
Log.i(TAG, "Sending shutdown broadcast...");
        
        // First send the high-level shut down broadcast.
        mActionDone = false;
        mContext.sendOrderedBroadcast(new Intent(Intent.ACTION_SHUTDOWN), null,
                br, mHandler, 0, null, null);
        
        final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
        synchronized (mActionDoneSync) {
            while (!mActionDone) {
                long delay = endTime - SystemClock.elapsedRealtime();
                if (delay <= 0) {
                    Log.w(TAG, "Shutdown broadcast timed out");
                    break;
                }
                try {
                    mActionDoneSync.wait(delay);
                } catch (InterruptedException e) {
                }
            }
        }
        
        Log.i(TAG, "Shutting down activity manager...");
        
        final IActivityManager am =
            ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
        if (am != null) {
            try {
                am.shutdown(MAX_BROADCAST_TIME);
            } catch (RemoteException e) {
            }
        }
mActionDonwSync.wait()は、ACTION_SHUTDOWNのBroadcast Intent発行が終わると戻ってきますので、発行しただけでam.shutdown()に入ります。

IActivityManagerはリモートインターフェースですが、リモートの先はframeworks/base/services/java/com/android/server/am/ActivityManagerService.javaです。

ActivityManagerService::shutdown()はどのようになっているかというと。
public boolean shutdown(int timeout) {
        if (checkCallingPermission(android.Manifest.permission.SHUTDOWN)
                != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Requires permission "
                    + android.Manifest.permission.SHUTDOWN);
        }
        
        boolean timedout = false;
        
        synchronized(this) {
            mShuttingDown = true;
            mWindowManager.setEventDispatching(false);

            if (mMainStack.mResumedActivity != null) {
                mMainStack.pauseIfSleepingLocked();
                final long endTime = System.currentTimeMillis() + timeout;
                while (mMainStack.mResumedActivity != null
                        || mMainStack.mPausingActivity != null) {
                    long delay = endTime - System.currentTimeMillis();
                    if (delay <= 0) {
                        Slog.w(TAG, "Activity manager shutdown timed out");
                        timedout = true;
                        break;
                    }
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
        }
        
        mUsageStatsService.shutdown();
        mBatteryStatsService.shutdown();
        
        return timedout;
    }
これ以上追ってないので、推測が入りますが、Activityの終了を待ってはいるけど、timeout引数で指定されてきた時間が過ぎると、Activityの終了をまたなくなる、あきらめちゃう、ということになると思います。

あきらめた後は、、、System系のServiceの停止や、ファイルシステムのアンマウントが始まるようなので、終了できなかったアプリは、動作が不安定になって最終的にKillされちゃうだけかなと。

なお、このTimeoutは、ShutdownThread.javaのMAX_BROADCAST_TIMEで指定されている時間で、Android Originalソースだと10秒になっていました。

MAX_BROADCAST_TIMEの10秒、というのは、単一のアプリからするとかなり長いですが、ほかにShutdown、終了処理をするアプリもいる訳なので、あまり悠長に処理をしてられる余裕もないのかも。

Shutdownで処理をする場合は、間に合わなくなる可能性も考えて、重要なデータから退避するとか、そもそもKillされる前提で設計した方がいいのかな、と思います。

・・・少し考えすぎているような気もしますがそーいうこともあるのかな、と、頭の片隅に入れておきます。

Android アプリの終了処理:Low BatteryとShutdown(1)



アプリを作る際、Low Batteryによる端末終了と、Shutdownによる終了の処理が必要になりますので、調べてみました。

Low Batteryの処理
バッテリーステータスは、Broadcast Intentで、アプリに飛んできます。簡単に図にまとめます。(FULL/NORMAL/LOWは、便宜的につけたステータスです)



今回のようなシチュエーションでは、Low Batteryによる処理をしますので、ACTION_BATTERY_LOWのBroadcast Intentを捕まえれば・・・ではないです

このACTION_BATTERY_LOWは、機種にもよりますが、バッテリー残量が15%になると発行されるようです。

そうです、ぜんぜんLow Batteryとは言えません。このIntentが発行されるタイミングは、まさに、端末が「バッテリー残量が少ないです」って割り込みのポップアップを出してくるタイミングです。

これはただのユーザへの警告ですので、アプリとしてここでLow Batteryの終了を始めるのは、気が早い。
さらに、アプリ起動時に、このBroad cast Intentが発行済みの場合、(つまり残量15%以下)、アプリには通知されません。

アプリが知りたいのは、バッテリー残量が0%になった、もう端末終了するよ〜の状態です。
Emulatorと、Xperiaで、Low Batteryにしてみたところ、ACTION_BATTERY_CHANGEDで、EXTRA_LEVELが0で通知され、その直後にACTION_SHUTDOWNが飛んでくる動作になっていました。
ということで、EXTRA_LEVEL=0を検出するか、ACTION_SHUTDOWNを受ける動作をすればよい、ということになります。

ただ、そのACTION_SHUTDOWNも、何も考えずに使えるわけでもなさそうなので、別エントリで調査結果を書きます。

Android アプリの終了処理:Low BatteryとShutdown(2)に続きます

Battery周りの動作を確認したコードを晒しておきます。
ちなみにBroadcastReceiverの作り方など、こちらを参考にさせてもらっています。

package test.sample.battery;

import android.app.Activity;
import android.os.Bundle;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.Message;
import android.util.Log;


public class BatteryTestActivity extends Activity {
 private static final String TAG = "BatteryTest";
 
 private final static int msgBatteryLow = 1;
 private final static int msgShutdown = 2;
 private final static int msgBatteryChanged = 3;
 private final static int msgBatteryOK = 4;
 
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        startSystemMonitor();
    }

 @Override
 protected void onDestroy() {
  Log.d(TAG, "onDestroy()");
  super.onDestroy();
  stopSystemMonitor();
 }

 @Override
 protected void onPause() {
  Log.d(TAG, "onPause()");
  super.onPause();
 }

 @Override
 protected void onRestart() {
  Log.d(TAG, "onRestart()");
  super.onRestart();
 }

 @Override
 protected void onResume() {
  Log.d(TAG, "onResume()");
  super.onResume();
 }

 @Override
 protected void onStart() {
  Log.d(TAG, "onStart()");
  super.onStart();
 }



 @Override
 protected void onStop() {
  Log.d(TAG, "onStop()");
  super.onStop();
 }


 public void onBatteryOk()
 {
  Log.d(TAG, "onBatteryOk");
 }
 public void onBatteryLow()
 {
  Log.d(TAG, "onBatteryLow()");
 }
 
 public void onBatteryChanged()
 {
  Log.d(TAG, "onBatteryChanged");
 }

 public void onShutdown()
 {
  Log.d(TAG, "onShutdown");
 }

 private Handler mHandler = new Handler()
 {

  @Override
  public void handleMessage(Message msg) {
   switch (msg.what)
   {
   case msgBatteryLow:
    onBatteryLow();
    break;
   case msgBatteryChanged:
    onBatteryChanged();
    break;
   case msgShutdown:
    onShutdown();
    break;
   case msgBatteryOK:
    onBatteryOk();
    break;
    
   default:
    super.handleMessage(msg);
    break;
   }
   super.handleMessage(msg);
  }
  
 };
 
 
 public void startSystemMonitor()
 {
  registerReceiver(lowBatteryReceiver, lowBatteryFilter);
  registerReceiver(batteryChangedReceiver, batteryChangedFilter);
  registerReceiver(shutdownReceiver, shutdownFilter);
 }
 
 public void stopSystemMonitor()
 {
  unregisterReceiver(lowBatteryReceiver);
  unregisterReceiver(batteryChangedReceiver);
  unregisterReceiver(shutdownReceiver);
 }
 
 private final BroadcastReceiver lowBatteryReceiver = new BroadcastReceiver()
 {

  @Override
  public void onReceive(Context context, Intent intent) {
   Log.d(TAG, "lowBatteryReceiver:onReceive()" + ", action:" + intent.getAction());

   if (intent.getAction().equals(Intent.ACTION_BATTERY_LOW))
   {
    mHandler.sendEmptyMessage(msgBatteryLow);
   } else if (intent.getAction().equals(Intent.ACTION_BATTERY_OKAY)){
    mHandler.sendEmptyMessage(msgBatteryOK);
   } else {
    assert true;
   }
   
  }
  
 };

 private static final IntentFilter lowBatteryFilter = new IntentFilter();
    static {
     lowBatteryFilter.addAction(Intent.ACTION_BATTERY_LOW);
     lowBatteryFilter.addAction(Intent.ACTION_BATTERY_OKAY);
    }
 
 private final BroadcastReceiver batteryChangedReceiver = new BroadcastReceiver()
 {

  @Override
  public void onReceive(Context context, Intent intent) {
   if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED))
   {
    int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);

    Log.d(TAG, "ACTION_BATTERY_CHANGED: " + level + ", Extra:" + BatteryManager.EXTRA_SCALE);

    Message msg = new Message();
    msg.what = msgBatteryChanged;
    msg.arg1 = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
    msg.arg2 = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
    
    mHandler.sendMessage(msg);

   } else {
    assert true;
   }
   
  }
  
 };

 private static final IntentFilter batteryChangedFilter = new IntentFilter();
    static {
     batteryChangedFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
    }

 private final BroadcastReceiver shutdownReceiver = new BroadcastReceiver()
 {

  @Override
  public void onReceive(Context context, Intent intent) {
   if (intent.getAction().equals(Intent.ACTION_SHUTDOWN))
   {
    Log.d(TAG, "ACTION_SHUTDOWN");
    mHandler.sendEmptyMessage(msgShutdown);
   } else {
    assert true;
   }
   
  }
  
 };
 private static final IntentFilter shutdownFilter = new IntentFilter();
    static {
     shutdownFilter.addAction(Intent.ACTION_SHUTDOWN);
    }

}

2011年9月2日金曜日

ListViewとScrollView

できないと思っていた、ScrollViewの中にListViewを入れるための方法。
こちらのサイトの方法でできるようです、というよりできました。ここにメモしておきます。

Android: put ListView in a ScrollView

なんで普通の方法で、できなかったか、というと、ListViewもScroll機能があるため、表示のサイズが画面(レイアウト)に収まるように、ListViewがそれ自体のサイズを決めてしまうから。そのため、ListViewを格納しているScrollViewは、収まっているListViewのサイズをスクロール不要、と認識してしまいます。
それだけなら、ListViewのスクロール機能は動きそうですが、Scroll機能(フリック)はScrollViewで処理されてしまうため、ListViewもScrollしない、となると考えられます。