It’s now or never

IT系の技術ブログです。気になったこと、勉強したことを備忘録的にまとめて行きます。

【Android】他のアプリケーションの上にViewを表示する

Androidのアプリの中には、 何かのアプリを起動中もずっと画面上に残り続けるアプリがあると思います。

今回は、上記のように他のアプリケーション上で、
自分のアプリケーションのViewを表示する方法について試して見ました。


Androidのviewは、複数のレイヤーによって構成されており、
通常のアプリケーションのレイヤーは、他のアプリケーションの画面上に乗せることができません。

常に画面上に自分のアプリケーションのViewを表示し、
他のアプリケーションが起動中も自分のアプリのViewを画面上に表示するためには、WindowManagerを使ってレイヤーを指定する必要があります。


通常のアプリケーションの上に載せられるレイヤーには、
幾つか種類がありますが、今回は、画面上のViewに対してタッチイベントを取得したかったので、WindowManager.LayoutParams.TYPE_SYSTEM_ALERTを使用しました。


Androidのレイヤーについては、下記の参考リンクにて詳しく説明されているので、ご参照ください。

使い方は、
以下のようにまずWindowManagerインスタンスを取得し、
画面上に出したいViewをWindowManagerに対してaddView()します。

その際に、LayoutParamsにWindowManager.LayoutParams.TYPE_SYSTEM_ALERTを指定することで、
通常のアプリケーションよりも上にViewを表示することが可能となります。

WindowManager.LayoutParams  Params = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,       // アプリケーションのTOPに配置
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |  // フォーカスを当てない(下の画面の操作がd系なくなるため)
            WindowManager.LayoutParams.FLAG_FULLSCREEN |        // OverlapするViewを全画面表示
            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,  // モーダル以外のタッチを背後のウィンドウへ送信
            PixelFormat.TRANSLUCENT);  // viewを透明にする

WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
windowManager.addView([OverlapView], mOverlapViewParams);


通常のアプリケーションよりも上にViewを表示するため、
使用後は、removeView()でViewを削除しないと、
画面上にずっと出続けてしまう危険がありますので注意が必要です。

[サンプル]

以下のサンプルでは、
ActivityからServiceをBindし、ServiceからWindowmanagerに対して、
ViewをaddView()しています。

アプリケーションが終了したタイミングで、
画面上に表示しているViewも一緒に削除されるようにServiceのunbindのタイミングで、
removeView()を呼び画面からViewを削除しています。

■ OverlapService.java

public class OverlapService extends Service {
    
    private WindowManager mWindowManager;
    private FrameLayout mOverlapView;
    private WindowManager.LayoutParams mOverlapViewParams;
    
    // Serviceに接続するためのBinderクラスを実装する
    public class LocalBinder extends Binder {
        //Serviceの取得
        OverlapService getService() {
            return OverlapService.this;
        }
    }
    // Binderの生成
    private final IBinder mBinder = new LocalBinder();
    
    @Override
    public IBinder onBind(Intent intent) {
        // Service接続時に呼び出される
        // 戻り値として、Serviceクラスとのbinderを返す。
        Log.i("", "onBind" + ": " + intent);
        return mBinder;
    }
 
    @Override
    public void onRebind(Intent intent){
        // Unbind後に再接続する場合に呼ばれる
        Log.i("", "onRebind" + ": " + intent);
    }
 
    @Override
    public boolean onUnbind(Intent intent){
        // Service切断時に呼び出される
        // onUnbindをreturn trueでoverrideすると次回バインド時にonRebildが呼ばれる
        return true;
    }
    
    @Override
    public void onCreate() {
        super.onCreate();
        
        mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        LayoutInflater layoutInflater = LayoutInflater.from(this);
        mOverlapView = new FrameLayout(getApplicationContext());
        ((FrameLayout)mOverlapView).addView(layoutInflater.inflate(R.layout.Overlap_content_view, null));
        mOverlapViewParams = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,       // アプリケーションのTOPに配置
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |  // フォーカスを当てない(下の画面の操作がd系なくなるため)
            WindowManager.LayoutParams.FLAG_FULLSCREEN |        // OverlapするViewを全画面表示
            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, // モーダル以外のタッチを背後のウィンドウへ送信
            PixelFormat.TRANSLUCENT);  // viewを透明にする

        mWindowManager.addView(mOverlapView, mOverlapViewParams);
    }
    
    @Override
    public void onDestroy() {
        // ServiceがunbindされるタイミングでViewも削除して上げる
        mWindowManager.removeView(mOverlapView);
        super.onDestroy();
    }
}

■ MainActivity(呼び出し元)

public class MainActivity extends Activity {
    
    // Serviceとのインターフェースクラス
    private ServiceConnection mConnection = new ServiceConnection() {
        OverlapService mBindService;
        public void onServiceConnected(ComponentName className, IBinder service) {
            // Serviceとの接続確立時に呼び出される。
            // service引数には、Onbind()で返却したBinderが渡される
            mBindService = ((OverlapService.LocalBinder)service).getService();
            //必要であればmBoundServiceを使ってバインドしたServiceへの制御を行う
        }
         
        public void onServiceDisconnected(ComponentName className) {
            // Serviceとの切断時に呼び出される。
            mBindService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button bindBtn = (Button) findViewById(R.id.bind_service_button);
        bindBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent(MainActivity.this,OverlapService.class);
                bindService(i, mConnection, Context.BIND_AUTO_CREATE);
            }
        });
        
        Button unbindBtn = (Button) findViewById(R.id.unbind_service_button);
        unbindBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Serviceをunbindする
                unbindService(mConnection);
            }
        });
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Serviceをunbindする
        unbindService(mConnection);
    }
}

(参考リンク)
http://techbooster.org/android/ui/13182/
http://matsuhilog.blogspot.jp/2011/06/typesystemalert.html