안드로이드 개발 질문/답변
(글 수 45,052)
현재 하고자 하는 것은 BOOT_COMPLETED action이 브로드캐스팅 됨과 동시에 백그라운드에서 서비스를 하나 돌리고,
그 서비스를 여러 앱이 사용할 수 있는 환경으로 만들어주고자 합니다.
그래서 다음과 같이 코딩을 했는데, bindService는 true를 반환하는데 onServiceConnected()가 호출이 안되는지 ServiceConnection 인스턴스를 사용하려고 하면 NullPointerException이 뜨네요.ㅠㅠ
혹시 이유를 아시는 분 계실까요..
제 코드는 다음과 같습니다.
서비스 소스코드들과 aidl, manifest입니다.
1. TERMMAServiceManager.java : 부팅시 TERMMAMiddleware 서비스를 등록해주는 코드입니다.
package kr.ac.kaist.term;
import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.util.Log;
public class TERMMAServiceManager extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { Log.v("[TERMMAServiceManager]","onReceive!"); if(intent.getAction().equalsIgnoreCase("android.intent.action.BOOT_COMPLETED")){ Log.v("[TERMMAServiceManager]","BOOT Complete!!"); ComponentName cName = new ComponentName(context.getPackageName(), TERMMAMiddleware.class.getName()); ComponentName service = context.startService(new Intent().setComponent(cName)); if(service == null){ Log.e("[TERMMAServiceManager]","Could not start services TERMMAMiddleware"); } }else{ Log.e("[TERMMAServiceManager]","Received unexpected intent " + intent.toString()); } }
}
2. TERMMAMiddleware.java : 서비스파트가 구현된 코드입니다.
package kr.ac.kaist.term;
import java.io.DataOutputStream; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; import java.util.Iterator; import java.util.LinkedList; import java.util.Queue; import java.util.Timer; import java.util.TimerTask;
import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log;
public class TERMMAMiddleware extends Service{ private Queue<Message> mQueue; private Iterator it; private Timer timer; private TimerTask task; private long lastCheckOut; private long tCnt; public void onCreate() { super.onCreate(); Log.v("[TERMMAMiddleware]", "TERMMAMiddleware Service started!!!"); mQueue = new LinkedList<Message>(); Log.v("[TERMMAMiddleware]","queue assigned"); it = mQueue.iterator(); Log.v("[TERMMAMiddleware]","iterator assigned"); timer = new Timer(); Log.v("[TERMMAMiddleware]","timer created"); task = new TimerTask(){ public void run(){ Log.v("[TERMMAMiddleware]","in run"); dequeueAll(); } }; Log.v("[TERMMAMiddleware]","Task assigned!!"); } @Override public IBinder onBind(Intent intent) { Log.v("[TERMMAMiddleware]","TERMMAMiddleware service is bounded!!!"); return mBinder; }
private final TERMMAInterface.Stub mBinder = new TERMMAInterface.Stub() { public void send(String mString, String destIP, int destPort, boolean faultTolerant, int timeout) throws RemoteException { Log.v("[TERMMAMiddleware]","send!!!"); Message m = new Message(mString, destIP, destPort, faultTolerant, timeout); if(m.isFaultTolerant()==true){ enqueue(m); }else{ sendMessage(m); dequeueAll(); } } }; private void enqueue(Message m){ Log.v("[TERMMAMiddleware]","enqueue!!"); mQueue.add(m); //set-up timer if(mQueue.size()==0){ lastCheckOut = System.currentTimeMillis(); tCnt = m.getTimeout(); timer.schedule(task, m.getTimeout()); }else{ if(tCnt-(System.currentTimeMillis()-lastCheckOut) > m.getTimeout()){ timer.cancel(); timer.schedule(task, m.getTimeout()); } } } private void dequeueAll(){ Log.v("[TERMMAMiddleware]","dequeueAll!!"); while(it.hasNext()){ printMessage(mQueue.poll()); //sendMessage(mQueue.poll()); } } private void sendMessage(Message m){ Log.v("[TERMMAMiddleware]","sendMessage!!"); try { Socket clientSocket = new Socket(m.getDestIP(), m.getDestPort()); DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream()); out.writeBytes(m.getStr()+'\n'); clientSocket.close(); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void testString(String str){ Log.v("[TERMMAMiddleware]","The given string is, "+str); } public void printMessage(Message m){ Log.v("[TERMMAMiddleware]", "Message Info."); Log.v("[TERMMAMiddleware]", "Destination IP address = "+m.getDestIP()); Log.v("[TERMMAMiddleware]", "Destination port = "+m.getDestPort()); Log.v("[TERMMAMiddleware]", "Message string = "+m.getStr()); Log.v("[TERMMAMiddleware]", "Message faultTolerant = "+m.isFaultTolerant()); Log.v("[TERMMAMiddleware]", "Message timeout = "+m.getTimeout()); } }
3. AndroidManifest.xml : 서비스의 Manifest 파일입니다.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="kr.ac.kaist.term" android:versionCode="1" android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <application android:icon="@drawable/ic_launcher" android:label = "@string/app_name"> <service android:name="kr.ac.kaist.term.TERMMAMiddleware" android:process=":remote" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="kr.ac.kaist.term.TERMMAInterface" /> </intent-filter> </service> <receiver android:name=".TERMMAServiceManager" android:enabled="true" android:exported="true" android:label="TERMMAServiceManager"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver> </application> </manifest>
========= 이제부터는 Client side의 소스코드입니다.----------------
1. TestAActivity.java :서비스를 바인딩해서 사용하는 코드입니다.
package kr.ac.kaist.testa;
import kr.ac.kaist.term.TERMMAInterface; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.Toast;
public class TestAActivity extends Activity { TERMMAInterface TERMMAService = null; Button bindButton; Button unbindButton; TERMMAThread t1; boolean isBound; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { Log.v("[TESTAActivity]",""); super.onCreate(savedInstanceState); setContentView(R.layout.main); bindButton = (Button)findViewById(R.id.button1); bindButton.setOnClickListener(mBindListener); unbindButton = (Button)findViewById(R.id.button2); unbindButton.setOnClickListener(mUnbindListener); } private ServiceConnection mConnection = new ServiceConnection(){ public void onServiceConnected(ComponentName className, IBinder service) { Log.v("[TESTAActivity]","onServiceConnected"); TERMMAService = TERMMAInterface.Stub.asInterface(service); Toast.makeText(TestAActivity.this, "Connected", Toast.LENGTH_SHORT).show(); }
public void onServiceDisconnected(ComponentName arg0) { Log.v("[TESTAActivity]","onServiceDisconnected"); TERMMAService = null; Toast.makeText(TestAActivity.this, "DisConnected", Toast.LENGTH_SHORT).show(); } }; private OnClickListener mBindListener = new OnClickListener(){ public void onClick(View v){ Log.v("[TESTAActivity]","mBindListener onClick!"); /* * bindService() method below returns false, as it's using the higher level application context * isBound = bindService(new Intent(TERMMAInterface.class.getName()), mConnection, Context.BIND_AUTO_CREATE); * therefore, getApplicationContext().bindService() returns true */ isBound = getApplicationContext().bindService(new Intent(TERMMAInterface.class.getName()), mConnection, Context.BIND_AUTO_CREATE); Toast.makeText(TestAActivity.this, "Binding", Toast.LENGTH_SHORT).show(); Log.v("[TESTAActivity]","isBound = "+isBound); Log.v("[TESTAActivity]","TERMMAInterface.class.getName() = "+TERMMAInterface.class.getName()); Log.v("[TESTAActivity]","mConnection.toString = "+mConnection.toString()); t1 = new TERMMAThread(); t1.run(); } }; private OnClickListener mUnbindListener = new OnClickListener(){ public void onClick(View v){ Log.v("[TESTAActivity]","mUnbindListener onClick!"); t1.stop(); unbindService(mConnection); Toast.makeText(TestAActivity.this, "Unbinding", Toast.LENGTH_SHORT).show(); } }; public class TERMMAThread extends Thread{ public void run(){ Log.v("[TESTAActivity]","TERMMAThread run"); try { int cnt = 0; while(true){ sleep(5000); if(TERMMAService==null){ Log.v("[TestAActivity]","TERMMAService is null!!"); } TERMMAService.send("Hello", "dest_ip", ++cnt, true, 1000000); Toast.makeText(TestAActivity.this, "Message Sent", 2500).show(); } } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
2. AndroidManifest.xml : 클라이언트사이드의 Manifest 파일입니다.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="kr.ac.kaist.testa" android:versionCode="1" android:versionName="1.0" >
<uses-sdk android:minSdkVersion="10" />
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:label="@string/app_name" android:name=".TestAActivity" > <intent-filter > <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
</manifest>
아.. 구글링을 해도 저에게 맞는 답을 찾기가 힘들고 답답하네요.ㅠㅠ...
호출이 안되는게 아니고 bindService는 비동기 처리하기 때문에 그렇습니다.
bindService하신 다음에 바로 thread 시작하셨는데... 그렇게 하시면 bind가 완료되기 전에 TERMMAService를 이용하게 되므로
널포인터가 나는겁니다.
onServiceConnected 메소드에서 스레드를 실행해 보시면 될겁니다.