AndroidのServiceについて調べてみた

Serviceについて調べてみたのでまとめ。

Serviceを起動前にBindしても起動できる

bindService()でServiceにBindすると一緒にServiceがCreateされる。ただし、bindService()で起動されたServiceはunbindService()された時に殺される。unBindした後も起動しておいて欲しい場合はstartService()後にbindService()してあげる事でunbindService()したとしても殺されない。bindしたActivityがunbindせずに死ぬと例外吐くからちゃんとonDestoryとかでunbindしてあげましょう。

Serviceはメモリがなくなったら殺される

Backgroundで起動して動いている間は死なないものだと思っていたけど、Serviceはメモリがなくなったら死ぬ。メモリがなくなった場合はonDestroyも通らずに強制的にKillされるらしい。なので、常駐させる場合はstartForeground()で今起動中だということを教えてあげると表示中のActivityと同じぐらい優先度が上がってそうそう殺されなくなる。ただメモリがどうしてもなくなったときは殺されるらしいが、それは気にしなくていい程度の頻度だそうで。startForeground()で起動中にした場合は、stopForeground()を呼んであげる事で死んでも大丈夫だよと教えることができる。

startForeground()でNotificationを出したくない場合

ユーザに起動中というのを教えるためにService起動中はNotificationを出したほうがいいとは思うけど、どうしても出したくない場合は引数に渡すnotificationのiconを0で渡せばNotificationになにも表示されなくなる。最も手軽な方法は以下だと思う。

startForeground(NOTIFICATION_ID, new Notification()) 

aidlでActivity→Serviceの連携

普通にaidlファイルを作ってonBind()の戻り値として返してあげるとbindService()の引数で渡すServiceConnection#onServiceConnectedで取得することができる。取得した後はaidlで定義されたInterfaceを使って自由に色々やりましょうって感じ。後、AndroidManifest.xmlにactionとしてaidlを設定してあげないと通信できないらしい。

Binderを継承してActivity→Serviceの連携

aidlを使用した連携とほぼ同じ。ただし、ActivityとServiceが同一プロセスで動いている場合じゃないとだめ。こっちのほうがお手軽にできるからActivityとServiceが同一プロセス上で動く保証があるならこっち使ったほうがいいんじゃないかと。ってか、プロセスが違う場合ってServiceが起動している状態でActivityが再起動した時とかかだと思うからそういうことが起こりそうならaidl使って連携したほうがいいはず。

BroadcastReceiverでService→Activityの連携

Service側からBroadCastでメッセージ投げてActivityで受け取るだけ。bindしてんだから関数で呼び出せないものかと思ったけど、Messageで通信するのがいいっぽい。receiverはandroid:exported=”false”を設定してあげて同一アプリ無いじゃないと受け取らないようにしておいたほうがセキュリティ上いいと思う。

サンプル

ServiceSample.java

package com.choilabo.activityservicebindsample;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.widget.Toast;

public class ServiceSample extends Service {

	private IServiceSample.Stub serviceSampleInf = new IServiceSample.Stub() {
		@Override
		public void makeToast(String value) throws RemoteException {
			Intent intent = new Intent("param");
			sendBroadcast(intent);
		}
	};
	
	@Override
	public void onRebind(Intent intent) {
		super.onRebind(intent);
		Toast.makeText(this, "onRebind ServiceSample", Toast.LENGTH_SHORT).show();
	}

	@Override
	public boolean onUnbind(Intent intent) {
		super.onUnbind(intent);
		Toast.makeText(this, "onUnbind ServiceSample", Toast.LENGTH_SHORT).show();
		return true;
	}

	@Override
	public void onCreate() {
		super.onCreate();
		Toast.makeText(this, "onCreate ServiceSample", Toast.LENGTH_SHORT).show();
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		Toast.makeText(this, "onDestroy ServiceSample", Toast.LENGTH_SHORT).show();
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Toast.makeText(this, "onStartCommand ServiceSample", Toast.LENGTH_SHORT).show();
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
		Toast.makeText(this, "onStart ServiceSample", Toast.LENGTH_SHORT).show();
	}

	@Override
	public IBinder onBind(Intent intent) {
		Toast.makeText(this, "onBind ServiceSample", Toast.LENGTH_SHORT).show();
		return serviceSampleInf;
	}
	
	public void makeToast() {
		Toast.makeText(this, "This is service message", Toast.LENGTH_SHORT).show();
	}
}

MainActivity.java

package com.choilabo.activityservicebindsample;

import com.choilabo.activityservicebindsample.R;

import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {

	private Button makeToast;
	private Button btnBind;
	private Button btnUnBind;
	private Button btnStart;
	private Button btnStop;
	
	private ServiceConnection mConnection;
	private IServiceSample connectionService;
	private BroadcastReceiverSample receiver = new BroadcastReceiverSample();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        makeToast = (Button)findViewById(R.id.btnMakeToast);
        makeToast.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				if(connectionService != null) {
					try {
						connectionService.makeToast(null);
					} catch (RemoteException e) {
						e.printStackTrace();
					}
				}
			}
		});
        
        btnBind = (Button)findViewById(R.id.btnBind);
        btnBind.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
		        mConnection = new ServiceConnection() {
					@Override
					public void onServiceConnected(ComponentName name, IBinder service) {
						Toast.makeText(MainActivity.this, "onServiceConnected MainActivity", Toast.LENGTH_SHORT).show();
						connectionService = IServiceSample.Stub.asInterface(service);
					}

					@Override
					public void onServiceDisconnected(ComponentName name) {
						Toast.makeText(MainActivity.this, "onServiceDisconnected MainActivity", Toast.LENGTH_SHORT).show();
					}
		        };

				Intent intent = new Intent(MainActivity.this, ServiceSample.class);
				bindService(intent, mConnection, Service.BIND_AUTO_CREATE);
			}
		});
        
        btnUnBind = (Button)findViewById(R.id.btnUnBind);
        btnUnBind.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				if(mConnection != null) {
					unbindService(mConnection);
					mConnection = null;
				}
			}
		});
        
        btnStart = (Button)findViewById(R.id.btnStart);
        btnStart.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				Intent intent = new Intent(MainActivity.this, ServiceSample.class);
				startService(intent);
			}
		});
        
        btnStop = (Button)findViewById(R.id.btnStop);
		btnStop.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				Intent intent = new Intent(MainActivity.this, ServiceSample.class);
				stopService(intent);
			}
		});
    }

	@Override
	protected void onStart() {
		super.onStart();
		IntentFilter filter = new IntentFilter("param");
		registerReceiver(receiver, filter);
	}

	@Override
	protected void onStop() {
		super.onStop();
		unregisterReceiver(receiver);
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		if(mConnection != null) {
			unbindService(mConnection);
			mConnection = null;
		}
	}
}

IServiceSample.aidl

package com.choilabo.activityservicebindsample;

interface IServiceSample {
	void makeToast(String value);
}

BroadcastReceiverSample.java

package com.choilabo.activityservicebindsample;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class BroadcastReceiverSample extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		Toast.makeText(context, "BroadcastReceiverSample get broadcast", Toast.LENGTH_SHORT).show();
	}
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.choilabo.activityservicebindsample"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="5"
        android:targetSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <receiver
			android:name=".BroadcastReceiver"
            android:exported="false"
            />
        
        <service android:name=".ServiceSample">
            <intent-filter>
                <action android:name="IServiceSample"></action>
            </intent-filter>
        </service>
        
    </application>

</manifest>

まとめ

Servieなのに殺されるってどういうことだっていう。スマホはメモリが少ないからしょうがないっちゃしょうがないのかなぁ。startForeground()はNotificationを出すからそうそう殺されないっていう理屈のはずなのにicon消せるってのもどうかと思う。まぁ、やっと少しService周りを理解できた気がする。

サイドバーが途中でついてくる奴作った

最近よく見かける下にスクロールしてるとサイドバーが途中から上部で固定される奴がどういう実装になってるのか気になったからちょろりとjquerypluginとして実装してみた。基本的には高さとってposition:fixedを変えてるだけ。

(function($) {

    $.fn.sidebarFixed = function(action, option){

        var sidebarFixed = function(self) {
            var $self = $(self);
            var top = $self.offset().top;
            self.isFixed = false;

            $(window).on('scroll', function() {
                if(!self.isFixed) {
                    if(top <= $(window).scrollTop()) {
                        $self.css({
                            'position': 'fixed',
                            'top': '0px'
                        });
                        self.isFixed = true;
                    }
                } else {
                    if(top >= $(window).scrollTop()) {
                        $self.css({
                            'position': 'static',
                        });
                        self.isFixed = false;
                    }
                }
            });
        };

        return this.each(function(i) {
            sidebarFixed(this);
        });
    };
})(jQuery)

↓がデモ。

http://www.choilabo.com/sample/sidebar-fixed/

やってることはpluginを読み込んで、途中で止めたいdivを↓みたいにpluginに渡してやるだけ。

    $(document).ready(function() {
      $('.fixed').sidebarFixed();
    });

サクッと書いたけどFirefoxとChromeでは動くみたい。IEは環境がないからよくわからん。jsはさくっとかけてさくっと試せるからやっぱり便利。

sakuraでApacheとnode.jsを同居させる

httpのportってdefaultが80だからApacheとnode.jsを同時に起動させるとどっちかしかアクセス出来ないもんだと思ってた。んで、しょうがないから、http://hogehoge.jp:8080みたいにportを指定してnodeの場合はアクセスしてたんだけど、カッコ悪いし不便だしなんか方法ないかなぁと調べたら案外あっさりできた。

ReverseProxyの設定でapacheにアクセスしてきたやつを吹き飛ばせばいいみたい。

<VirtualHost *:80>
    ServerName hogehoge.your.domain
    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/
</VirtualHost>

こんなふうにhttpd.conf書いてやれば、hogehoge.your.domainでアクセスしてきた奴がport:3000にアクセスしたことになるから、後はnode.jsを3000で上げてあげればポート指定が入ってなくてもアクセスしてくれるというメモ。

PCで悪いことして捕まっても大丈夫な方法を考えた

最近巷でこのニュースが騒がれてる

Expired

これ見て掲示板に書き込みとかではなくて著作権違法のものをダウンロードするウイルスとか出せたらボタンひとつで気に食わない奴を刑務所送りにできる時代がきそうで怖いなーとか思ったんだけど、よく考えればすべてをウイルスのせいにしてしまえばなんとでもなるんじゃね?って思ったりした。

手順は簡単

  1. 自分のPCに定期的にどこかサーバにアクセスするようなものを仕込んでおく
  2. どこかの掲示板に手製のウイルスをダウンロードするリンクを貼る
  3. わざとそのリンクを踏んでウイルス落として感染しておく

こんだけ。

後は適当にそのPCで悪いことしまくって捕まったとしても、定期的にアクセスされているサーバで一定期間アクセスがなかったら手製のウイルス経由で「警察のバーカ。愉快犯だよ~ん」みたいな犯行声明を出させるようにしておけば、捕まって取り調べを受けてる間ずっと「僕はやっていない」って一点張りしてるうちに犯行声明が出てきて無事に開放される。みたいなね。

これでPC使ってわるいことしまくれるな。