2012年8月30日木曜日

Android 4.1 JellyBeanのロック画面表示中のActivityLifeCycleについて

Androidのバージョンによって、動作に差異があったのでメモ。
おそらく、Google側も省電力を気にしているのだと思います。




Android 2.3.3 GB(API Level 10) のActivityLifeCycle

電源キー押下によりScreen OFF
onPause()

電源キー押下によりScreen ON → ロック画面表示中
onResume()

ロック画面解除
※ActivityのActivityLifeCycleメソッドはコールされず

Android 4.0.X ICS(API Level 14,15) のActivityLifeCycle

電源キー押下によりScreen OFF
onPause()
 ↓
onStop()

電源キー押下によりScreen ON → ロック画面表示中
onRestart()
 ↓
onStart()
 ↓
onResume()

ロック画面解除
※ActivityのActivityLifeCycleメソッドはコールされず

Android 4.1 JellyBean(API Level 16) のActivityLifeCycle

電源キー押下によりScreen OFF
onPause()
 ↓
onStop()

電源キー押下によりScreen ON → ロック画面表示中
※ActivityのActivityLifeCycleメソッドはコールされず
ロック画面解除
onRestart()
 ↓
onStart()
 ↓
onResume()

2012年8月28日火曜日

自アプリケーションがTOPかどうか確認する方法(4)

ActivityのTaskListの順番を見るよりは、アプリケーションプロセスを見る方が正確だと思います。
ActivityManager#getRunningAppProcesses()で取得可能です。


処理:

  1. ActivityManagerから実行中プロセス情報一覧を取得
  2. 自アプリケーションが存在するかチェック
  3. プロセス情報(importance )の状態を確認する


ソースはこんな感じ。


    private void checkRunningAppProcess(){
        Log.e("","checkRunningAppProcess");

        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        List<runningappprocessinfo> processInfoList = am.getRunningAppProcesses();
        for( RunningAppProcessInfo info : processInfoList){
            if(info.processName.equals(getPackageName())){
                if( info.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND){
                    // app is FOREGROUND
                    Log.e("","app is FOREGROUND");
                }
            }
        }
    }

2012年8月27日月曜日

自アプリケーションがTOPかどうか確認する方法(3)

ActivityManager#getRecentTasks()の情報って?

getRunningTasks()と同様にgetRecentTasks()を見てみます。

ActivityManagerService#getRecentTasks()の処理をチェック。

    public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
            int flags) {
                     :
                     :
            final int N = mRecentTasks.size();
            ArrayList<ActivityManager.RecentTaskInfo> res
                    = new ArrayList<ActivityManager.RecentTaskInfo>(
                            maxNum < N ? maxNum : N);
            for (int i=0; i<N && maxNum > 0; i++) {
                     :
                     :
       

mRecentTasksのTaskRecord情報からListを生成します。

TaskRecordをmRecentTasksに追加するタイミング

ActivityManagerService#addRecentTaskLocked(TaskRecord task)でaddを行っており、
このモジュールをコールするのはActivityStackのこちら。

final boolean realStartActivityLocked()
final boolean resumeTopActivityLocked()

Activity起動、またはresumeを行ってActivityがTopになったタイミングでtaskをaddします。


TaskRecordをmRecentTasksから削除するタイミング

削除するのは以下のタイミング
addRecentTaskLocked()のadd処理でMAX(=20)を超えた場合、古いものを削除 RecentsPanelView(=履歴表示)からSwipで削除した場合、ActivityManagerService#removeTask()をコール


こちらはRunningTasksとくらべ、ユーザー操作に近いトリガーとなります。


getRecentTasks()は『起動アプリ履歴に残っているTask一覧』であって、
現在、起動中と意味でTask情報を取得したい場合はgetRunningTasks()を使用する方が好ましいと思います。

自アプリケーションがTOPかどうか確認する方法(2)

ActivityManager#getRunningTasks()の情報って?

ActivityManager#getRunningTasks()の情報が何かソースを見てみます。

モジュールを辿っていくと、ActivityManagerService#getTasks()をコールします。

ActivityManager#getRunningTasks()
- ActivityManagerService#getTasks()


ActivityManagerService#getTasks()は以下のような処理。


    public List getTasks(int maxNum, int flags,
                         IThumbnailReceiver receiver) {
        ArrayList list = new ArrayList();
                     :
                     :
            int pos = mMainStack.mHistory.size()-1;
            ActivityRecord next =
                pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null;
            ActivityRecord top = null;
            TaskRecord curTask = null;
            int numActivities = 0;
            int numRunning = 0;
            while (pos >= 0 && maxNum > 0) {
                     :
                     :
            }
    }
mMainStack.mHistoryのActivityRecord情報からListを生成します。



ActivityRecordをmMainStack.mHistoryに追加するタイミング

mMainStack.mHistoryのadd()をコールするソースはActivityStack.java。
もともとActivityManagerServiceに記述されていたソースのうち、Stack情報に関わる操作などがActivityStack.javaに分離されました。

で、mMainStack.mHistoryのadd()をコールするのは以下のモジュール。


final boolean switchUser()
private final void startActivityLocked()
private final ActivityRecord resetTaskIfNeededLocked()
private final ActivityRecord moveActivityToFrontLocked()
final void moveTaskToFrontLocked()
final boolean moveTaskToBackLocked()
Activityをstartしたり、起動済みのActivityをFrontに移行する際に呼ばれていました。



ActivityRecordをmMainStack.mHistoryから削除するタイミング

リストから明示的に削除を行っているのは以下のモジュールです。

removeActivityFromHistoryLocked
このモジュールはActivityがDestroyされるときにコールされます。

final boolean destroyActivityLocked
つまり、アプリ内のすべてのActivityがDestroyされるとmMainStack.mHistory内から抹殺され、 (onStop状態でも)Activityが一つでも残っていればmMainStack.mHistoryに存在することとなります。

自アプリケーションがTOPかどうか確認する方法

以前の記事でApplication単位でresume/pauseのハンドリングが不可能とわかりました。
 http://baroqueworksdevjp.blogspot.jp/2012/08/applicationonterminate.html


では、他に何かいい方法がないか調べてみました。


ActivityManagerの公開APIを使う

以下のような、Task情報を取得できるAPIをActivityのonStop()でコールしてみました。
    private void checkRecentTasks(){
        Log.e("","checkRecentTasks");

        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        List<RecentTaskInfo> taskInfo = am.getRecentTasks(10, ActivityManager.RECENT_WITH_EXCLUDED);
        for( RecentTaskInfo info : taskInfo){
            Log.e("", "" + info.baseIntent.getComponent().getPackageName());
        }
    }
    
    private void checkRunningTasks(){
        Log.e("","checkRunningTasks");
  
        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        List<RunningTaskInfo> taskInfo = am.getRunningTasks(10);
        for( RunningTaskInfo info : taskInfo){
            Log.e("","" + info.topActivity.getPackageName());
        }
    }


Homeキー押下によりSampleアプリをBackGroudにさせたlogです。

checkRecentTasks
08-27 21:12:13.314: E/(7511): com.android.launcher
08-27 21:12:13.314: E/(7511): jp.baroqueworksdev.sampleapp
08-27 21:12:13.314: E/(7511): com.android.packageinstaller
08-27 21:12:13.314: E/(7511): jp.r246.twicca
08-27 21:12:13.314: E/(7511): com.alphonso.pulse
08-27 21:12:13.314: E/(7511): com.chnavi.android2ch
08-27 21:12:13.314: E/(7511): jp.mixi
08-27 21:12:13.314: E/(7511): com.facebook.katana
08-27 21:12:13.314: E/(7511): com.android.chrome

checkRunningTasks
08-27 21:12:13.321: E/(7511): com.android.launcher
08-27 21:12:13.321: E/(7511): jp.baroqueworksdev.sampleapp
08-27 21:12:13.321: E/(7511): jp.r246.twicca
08-27 21:12:13.321: E/(7511): com.alphonso.pulse
08-27 21:12:13.321: E/(7511): com.chnavi.android2ch
08-27 21:12:13.321: E/(7511): jp.mixi
08-27 21:12:13.321: E/(7511): com.facebook.katana
08-27 21:12:13.321: E/(7511): com.android.chrome


直近に起動したアプリ順に並んでいました。
Android Developer Siteにも以下のように書いています。
 with the most recent being first and older ones after in order.


上記のようなTaskリストを取得して、自アプリがTOPかどうかチェックするのも一つの手段かと思います。.
ただし、ロック画面が表示されている場合、TOPにいても「画面上表示されておらずユーザーには見えない」ことを忘れずに。

2012年8月25日土曜日

DTI ServersMan SIM 3G 100 が届きました

500円SIMのニュースで話題になった、DTIの「ServersMan SIM 3G 100」を購入しました。




申し込みから到着まで


到着までの時系列
 8/20(月) DTI公式ページから申し込み http://dream.jp/
 8/22) 発送 (※ヤマト運輸 東京都台東区からでした)
 8/25(土) 到着


Galaxy Nexus(※海外版)に挿してみた

Galaxy NexusにSIMを挿入してみました。 DTIはdocomoさんのMVNOなので、APN設定にmopera UI / SPモードが標準で表示されていました。


DTIのAPN設定を完了し通信状態となっても、アンテナピクトが更新されませんでした。
これは端末側のソフトウェアの問題なので気にしない、気にしない。



テザリング

Galaxy Nexus(※海外版)でテザリングができるかどうか試してみました。




問題なくテザリング動作可能でした。
Docomo版端末は制限事項があるみたいなのでご自身で調べてみてください。


使ってみた感想

Twitterやメールなどテキストベースなら問題ない通信速度です。
ただ、Facebookやmixiなど画像を多く含むアプリケーションでは、ストレスを感じてしまいます。
100kのベストエフォートなんで、こんな感じです。

2012年8月24日金曜日

Application#onTerminate()がコールされない


(Activityではなく)Application単位でresume/pauseのハンドリングが可能か調査を行いました。
結論からいうと、frameworksからコールされる明示的なモジュールはありませんでした。


で、気になったのがApplication#onTerminate()というモジュール。
Android Developerには以下のように記載されています。

http://developer.android.com/reference/android/app/Application.html#onTerminate()

This method is for use in emulated process environments. It will never be called on a production Android device, where processes are removed by simply killing them; no user code (including this callback) is executed when doing so.

そう、Androidデバイスでは呼ばれることがないと書かれています。
じゃあ、何のために存在するのでしょう?

Application#onTerminate()をコールする箇所をgrepすると、ActivityThread.javaにありました。

                case EXIT_APPLICATION:
                    if (mInitialApplication != null) {
                        mInitialApplication.onTerminate();
                    }
                    Looper.myLooper().quit();
                    break;


この処理が行われるのは、ActivityThread#scheduleExit()の内部からhandleメッセージが発行されたとき。

ActivityThread#scheduleExit()をコールするのは、以下のActivityManagerService.javaの2モジュール。


private final boolean attachApplicationLocked(IApplicationThread thread, int pid)
final void trimApplications()

モジュール内の処理を見てみると、以下のような処理でした。
                    if (app.pid > 0 && app.pid != MY_PID) {
                        EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
                                app.processName, app.setAdj, "empty");
                        Process.killProcessQuiet(app.pid);
                    } else {
                        try {
                            app.thread.scheduleExit();
                        } catch (Exception e) {
                            // Ignore exceptions.
                        }
                    }
ActivityThread#scheduleExit()をコールする条件は次の場合。
  • PIDが0未満
  • PIDがMY_PID(= System Process)


System Processと同じApplicationの場合、コールするみたいなんです。
そんなApplicationってありえるのでしょうか。。。


このソースを見る限り、商品として発売されているAndroidデバイスでは呼ばれないことは確実ですね。