Android + Service 建國科技大學 資管系 饒瑞佶
Android Service Android中的Service與Activity不同,Service不具有UI 介面,不能自己啟動
Android Service life cycle Android Service的生命週期不同於Activity, Service的生命 週期只有onCreate(),onStart(),onDestroy()三個方法 第一次啟動Service時,首先執行onCreate(),再執行 onStart()方法 停止Service則是執行onDestroy()方法 如果Service已經啟動,再次啟動Service時,將不再執行 onCreate()方法,而是直接執行onStart()方法
Service life cycle 不牽涉到Activity與Service兩邊互動與資料傳輸
Service lifecycle Activity與Service需要互相傳輸使用 Activity單純呼叫Service
Activity呼叫Service內的方法 Service做一個計時器方法,Activity呼叫後取得結果顯示在畫面上 Android bound service Activity呼叫Service內的方法 Service做一個計時器方法,Activity呼叫後取得結果顯示在畫面上
建立一個Service-BoundService
Service名稱
會自動註冊到AndroidManidest.xml中
預設有BoundService與onBind事件
Service lifecycle Activity與Service需要互相傳輸使用 Activity單純呼叫Service
加入onCreate事件
onCreate事件
public class BoundService extends Service { private static String LOG_TAG = "ServiceTest"; // debug用 private Chronometer mChronometer; // 計時器 public BoundService() { } @Override public void onCreate() { super.onCreate(); Log.v(LOG_TAG, "onCreate"); mChronometer = new Chronometer(this); mChronometer.setBase(SystemClock.elapsedRealtime()); mChronometer.start(); // 開始計時
onBind事件 @Override public IBinder onBind(Intent intent) { Log.v(LOG_TAG, "onBind2"); return mBinder; }
建立Activity與Service間聯繫(bind) private IBinder mBinder = new MyBinder(); public class MyBinder extends Binder { BoundService getService() { return BoundService.this; }
加入onRebind與onUnbind與onDestroy事件
加入對應的事件程式 @Override public void onRebind(Intent intent) { Log.v(LOG_TAG, "onRebind"); super.onRebind(intent); } public boolean onUnbind(Intent intent) { Log.v(LOG_TAG, "onUnbind"); return true; public void onDestroy() { super.onDestroy(); Log.v(LOG_TAG, "onDestroy"); mChronometer.stop();
Service中加入計時程式 // 等待Activity呼叫使用 public String getTimestamp() { long elapsedMillis = SystemClock.elapsedRealtime() - mChronometer.getBase(); int hours = (int) (elapsedMillis / 3600000); int minutes = (int) (elapsedMillis - hours * 3600000) / 60000; int seconds = (int) (elapsedMillis - hours * 3600000 - minutes * 60000) / 1000; int millis = (int) (elapsedMillis - hours * 3600000 - minutes * 60000 - seconds * 1000); return hours + ":" + minutes + ":" + seconds + ":" + millis; }
layout <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="edu.ctu.rcjao.androidservice.MainActivity"> <Button android:id="@+id/print_timestamp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:text="從Service取出計時時間" /> <TextView android:id="@+id/timestamp_text" android:text="" /> android:id="@+id/stop_service" android:text="停止Service" /> </LinearLayout> layout
設定Activity bind Service
public class MainActivity extends AppCompatActivity { BoundService mBoundService; // Service boolean mServiceBound = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 對應畫面物件 final TextView timestampText = (TextView) findViewById(R.id.timestamp_text); Button printTimestampButton = (Button) findViewById(R.id.print_timestamp); Button stopServiceButon = (Button) findViewById(R.id.stop_service); // 從Service取出計時時間 printTimestampButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { if (mServiceBound) { // 如果已經bind,呼叫Service內的getTimestamp方法 timestampText.setText(mBoundService.getTimestamp()); } }); // 停止Service stopServiceButon.setOnClickListener(new View.OnClickListener() { if (mServiceBound) { // 如果已經bind,切斷Service unbindService(mServiceConnection); mServiceBound = false; Intent intent = new Intent(MainActivity.this, BoundService.class); stopService(intent); 呼叫Service 停止Service
建立onStart與onStop @Override protected void onStart() { super.onStart(); Intent intent = new Intent(this, BoundService.class); startService(intent); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } protected void onStop() { super.onStop(); if (mServiceBound) { unbindService(mServiceConnection); mServiceBound = false;
建立mServiceConnection private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { mServiceBound = false; } public void onServiceConnected(ComponentName name, IBinder service) { BoundService.MyBinder myBinder = (BoundService.MyBinder) service; mBoundService = myBinder.getService(); mServiceBound = true; };
result 每按一次取出經過的時間
執行log
從Service更新Activity Service呼叫Activity方法
Layout加入一個TextView 用來顯示Service更新的資料 android:id="@+id/timestamp_text_from_service" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:text="" />
修改Service,加入callback Callbacks activity; //宣告 // 加入callbacks interface(需要到Activity實作updateClient) public interface Callbacks{ public void updateClient(long data); } // 註冊Activity 成為Callbacks client public void registerClient(Activity activity){ this.activity = (Callbacks)activity;
修改Service,加入計時與更新Activity // 開始計時 private long startTime = 0; private long millis = 0; public void startCounter(){ // 由Activity發動 startTime = System.currentTimeMillis(); handler.postDelayed(serviceRunnable, 0); Toast.makeText(getApplicationContext(), "開始計時", Toast.LENGTH_SHORT).show(); } // 停止計時 public void stopCounter(){ handler.removeCallbacks(serviceRunnable); Handler handler = new Handler(); Runnable serviceRunnable = new Runnable() { @Override public void run() { millis = System.currentTimeMillis() - startTime; activity.updateClient(millis); // 透過callback更新Activity handler.postDelayed(this, 1000); };
修改Activity 繼承Callback 會被要求實作updateClient
加入畫面TextView物件對應
開始與停止計時
註冊Activity到Service
@Override public void updateClient(long data) { int seconds = (int) (data / 1000) % 60 ; int minutes = (int) ((data / (1000*60)) % 60); int hours = (int) ((data / (1000*60*60)) % 24); timestampTextfromservice.setText((hours>0 ? String.format("%d:", hours) : "") + ((minutes<10 && hours > 0)? "0" + String.format("%d:", minutes) : String.format("%d:", minutes)) + (seconds<10 ? "0" + seconds: seconds)); }
Service 範例 播放音樂
單純呼叫
Service流程
新增一組Activity與layout
MediaController.java
<LinearLayout xmlns:android="http://schemas. android xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_media_controller" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="edu.ctu.rcjao.androidservice.MediaController" android:orientation="vertical"> <TextView android:text="" android:layout_height="wrap_content" android:id="@+id/textState " android:textSize="30sp" /> <Button android:text="播放" android:id="@+id/Play" android:text="繼續播放" android:id="@+id/ContinuePlay" android:text="暫停" android:id="@+id/Pause" android:text="停止" android:id="@+id/Stop" </LinearLayout> layout 顯示播放狀態
加入OnClickListener
設定對應畫面物件
public class MediaController extends AppCompatActivity implements View public class MediaController extends AppCompatActivity implements View.OnClickListener { Button buttonPlay, buttonContinuePlay, buttonStop, buttonPause; TextView textState; Intent intent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_media_controller); // 對應layout物件 buttonPlay = (Button) findViewById(R.id.Play); buttonContinuePlay = (Button) findViewById(R.id.ContinuePlay); buttonPause = (Button) findViewById(R.id.Pause); buttonStop = (Button) findViewById(R.id.Stop); textState = (TextView)findViewById(R.id.textState); // 設定onclick事件 buttonPlay.setOnClickListener(this); buttonStop.setOnClickListener(this); buttonPause.setOnClickListener(this); buttonContinuePlay.setOnClickListener(this); intent = new Intent(this, MusicService.class); //透過intent啟動Service }
onClick @Override public void onClick(View v) { switch (v.getId()) { case R.id.Play: playAction(1); textState.setText("- Playing -"); buttonPlay.setEnabled(false); buttonContinuePlay.setEnabled(false); buttonPause.setEnabled(true); buttonStop.setEnabled(true); break; case R.id.ContinuePlay: playAction(2); case R.id.Pause: playAction(3); textState.setText("- Pause -"); buttonPlay.setEnabled(true); buttonContinuePlay.setEnabled(true); buttonPause.setEnabled(false); case R.id.Stop: playAction(4); textState.setText("- Stop -"); buttonStop.setEnabled(false); } onClick
新增playAction方法 // 執行的播放動作 private void playAction(int op) { Bundle bundle = new Bundle(); bundle.putInt("op", op); //設定intent啟動的動作 intent.putExtras(bundle); MediaController.this.startService(intent); //啟動service,透過intent傳送資料 }
建立Service-MusicService 會自動註冊到AndroidManifest.xml中
建立播放音樂物件
加入onStart 音樂檔 @Override public void onStart(Intent intent, int startId) { //完成onCreate()後啟動 if (intent != null) { Bundle bundle = intent.getExtras(); if (bundle != null) { int op = bundle.getInt("op"); switch (op) { case 1: //play player=MediaPlayer.create(this, R.raw.test) ; player.start(); break; case 2: //replay case 3: //pause player.pause(); case 4: //stop if (player != null) { player.stop(); } 加入onStart 音樂檔
加入onStop @Override public void onDestroy() { super.onDestroy(); if (player != null) { player.stop(); player.release(); }
執行 當App在背景時,音樂仍然會繼續播放