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

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月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デバイスでは呼ばれないことは確実ですね。

2012年8月15日水曜日

NavigationBarの有無判定


アプリケーション作成時に「NavigationBarが表示されている端末かどうか?」の判定が必要となった場合、どうすればいいか確認します。

以下のモジュールがFrameworks内にあります
 PhoneWindowManager#hasNavigationBar()
 WindoManagerService#hasNavigationBar()

このモジュールを使用しているソースがあるかどうかチェック。
アプリケーションで使えそうなモジュールはViewConfigurationというクラス。

public class ViewConfiguration {
    private ViewConfiguration(Context context) {
             :
        if (!sHasPermanentMenuKeySet) {
            IWindowManager wm = Display.getWindowManager();
            try {
                sHasPermanentMenuKey = wm.canStatusBarHide() && !wm.hasNavigationBar();
                sHasPermanentMenuKeySet = true;
            } catch (RemoteException ex) {
                sHasPermanentMenuKey = false;
            }
        }
             :
    }

    public boolean hasPermanentMenuKey() {
        return sHasPermanentMenuKey;
    }
}


hasPermanentMenuKey()というmethodは
Permanentな(常に押下できる)MenuKeyは存在する/しないか判定するmethod。

sHasPermanentMenuKeyは以下の条件で値が決定する。
wm.canStatusBarHide()は「Phone/Tabletの判定」、wm.hasNavigationBar()は「NavigationBarの有無判定」なので、
Tabletも場合、そのそもNavigationBarは非対応なので、falseとなる。
Phone ModeでかつNavigationBarがない場合、trueとなる。
Phone ModeでかつNavigationBarがある場合、falseとなる。

となる。

と、いうことでアプリ側で以下のように値を取得すればOK。

//hasPermanentMenuKey == true はNavigationBarが非表示
//hasPermanentMenuKey == false はNavigationBarが表示
boolean isNavigationBar = ! ViewConfiguration.get(this).hasPermanentMenuKey();


関連リンク:

 何気なく使っているFrameworks 第10回 NavigationBar / ナビゲーションバー

 EmulatorでNavigationBarを表示する