Android_Service 筆記(陸續更新)

 
Android_Service類onStartCommand的返回值問題 轉http://blog.sina.com.cn/s/blog_3fe961ae0100xhsl.html
Android開發的過程中,每次調用startService(Intent)的時候,都會調用該Service對象的onStartCommand(Intent,int,int)方法,然後在onStartCommand方法中做一些處理。然後我們注意到這個函數有一個int的返回值,這篇文章就是簡單地講講int返回值的作用。
從Android官方文檔中,我們知道onStartCommand有4種返回值:

START_STICKY:如果service進程被kill掉,保留service的狀態爲開始狀態,但不保留遞送的intent對象。隨後系統會嘗試重新創建service,由於服務狀態爲開始狀態,所以創建服務後一定會調用onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啓動命令被傳遞到service,那麼參數Intent將爲null。

START_NOT_STICKY:“非粘性的”。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統不會自動重啓該服務。

START_REDELIVER_INTENT:重傳Intent。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統會自動重啓該服務,並將Intent的值傳入。


START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保證服務被kill後一定能重啓。


Android Service解析 轉http://zy77612.iteye.com/blog/1292649


什麼是Service?

         解惑:

                1、  Service不是分離開的進程,除非其他特殊情況,它不會運行在自己的進程,而是作爲啓動運行它的進程的一部分。

                2、  Service不是線程,這意味着它將在主線程裏勞作。

        啓動service有兩種方法:

             1、  Context.startService()

                    調用者與服務之間沒有關聯,即使調用者退出,服務仍可運行

             2、  Context.bindService()

                    調用者與服務綁定在一起,調用者一旦退出,服務也就終止

Service的生命週期

         如果使用startService()啓動service,系統將通過傳入的Intent在底層搜索相關符合Intent裏面信息的service。如果服務沒有啓動則先運行onCreate,然後運行onStartCommand (可在裏面處理啓動時傳過來的Intent和其他參數),直到明顯調用stopService或者stopSelf纔將停止Service。無論運行startService多少次,只要調用一次stopService或者stopSelf,Service都會停止。使用stopSelf(int)方法可以保證在處理好intent後再停止。

         控制service運行的主要方式有兩種,主要是根據onStartCommand方法返回的數值。方法:

         1、START_STICKY

         2、START_NOT_STICKY or START_REDELIVER_INTENT

         這裏主要解釋這三個變量的意義:

          1、  START_STICKY

                 在運行onStartCommand後service進程被kill後,那將保留在開始狀態,但是不保留那些傳入的intent。不久後service就會再次嘗試重新創建,因爲保留在開始狀態,在創建     service後將保證調用onstartCommand。如果沒有傳遞任何開始命令給service,那將獲取到null的intent

          2、  START_NOT_STICKY

                 在運行onStartCommand後service進程被kill後,並且沒有新的intent傳遞給它。Service將移出開始狀態,並且直到新的明顯的方法(startService)調用才重新創建。因爲如果沒有傳遞任何未決定的intent那麼service是不會啓動,也就是期間onstartCommand不會接收到任何null的intent。

           3、  START_REDELIVER_INTENT

                在運行onStartCommand後service進程被kill後,系統將會再次啓動service,並傳入最後一個intent給onstartCommand。直到調用stopSelf(int)才停止傳遞intent。如果在被kill後還有未處理好的intent,那被kill後服務還是會自動啓動。因此onstartCommand不會接收到任何null的intent。

          客戶端也可以使用bindService來保持跟service持久關聯。謹記:如果使用這種方法,那麼將不會調用onstartCommand(跟startService不一樣,下面例子註釋也有解析,大家可試試)。客戶端將會在onBind回調中接收到IBinder接口返回的對象。通常IBinder作爲一個複雜的接口通常是返回aidl數據。

         Service也可以混合start和bind一起使用。

權限

         要運行service,首先必須在AndroidManifest.xml裏申明<service>標籤。

         Service能夠保護個人的IPC調用,所以在執行實現該調用時前先使用checkCallingPermission(String) 方法檢查是否有這個權限。

 

進程生命週期

         當service運行在低內存的環境時,將會kill掉一下存在的進程。因此進程的優先級將會很重要:

         1、  如果service當前正在執行onCreate、onStartCommand、onDestroy方法,主進程將會成爲前臺進程來保證代碼可以執行完成避免被kill

         2、  如果service已經啓動了,那麼主進程將會比其他可見的進程的重要性低,但比其他看不見的進程高。因爲只有少部分進程始終是用戶可見的,因此除非在極度低內存的時候,不然 service是不會被kill的。

         3、  如果有客戶端關聯到service,那麼service永遠比客戶端重要。也就是說客戶端可見,那麼service也可見(我理解這裏的可見並不是可以看到,而是重要性,因爲可見往往就表示重要性高)。

         4、  Service可以使用startForeground API將service放到前臺狀態。這樣在低內存時被kill的機率更低,但是文檔後面又寫了,如果在極度極度低內存的壓力下,該service理論上還是會被kill掉。但這個情況基本不用考慮。

         當然如果service怎麼保持還是被kill了,那你可以通過重寫onStartCommand返回變量來設置它的啓動方式。比如:START_STICKY、START_REDELIVER_INTENT等等,前面已經討論了它們的作用,這裏就不再累贅了

        另外:

        service 的onCreate和onStartCommand 是運行在主線程的,所以如果裏面有處理耗時間的任務。兩種處理:

        1、  請將它們都挪到新的線程裏。

        2、  用系統提供的IntentService,它繼承了Service,它處理數據是用自身新開的線程。

      好了說了這麼多下面就是例子的時刻了。總共有兩個例子,第一個是本地調用,第二個是遠程調用.

      口水快沒了,所以下面就直接進入代碼環節吧。代碼裏面已經有詳細的註解了,如果真的真的還是不明白,那就是我這篇二手鞋的失敗了。:(

public class LocalService extends Service {

private NotificationManager mNM;

// 通知唯一標示,在通知開始和結束使用
    private int NOTIFICATION = R.string.local_service_started;

// 與界面交互的類,由於service跟界面總是運行在同一程序裏,所以不用處理IPC
    public class LocalBinder extends Binder {
        LocalService getService() {
return LocalService.this;
        }
    }

    @Override
public void onCreate() {
        mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

// 在service開始時,將icon圖標放到通知任務欄
        showNotification();
    }

//
    private void showNotification() {
        CharSequence text = getText(R.string.local_service_started);

        Notification notification = new Notification(R.drawable.icon, text,
                System.currentTimeMillis());

// 當點擊通知時,啓動該contentIntent關聯的activity
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, showActivity.class), 0);

// 在通知欄上顯示標題和內容
        notification.setLatestEventInfo(this,
                getText(R.string.local_service_label), text, contentIntent);

        mNM.notify(NOTIFICATION, notification);
    }

// 兼容2.0以前版本
    @Override
public void onStart(Intent intent, int startId) {
    }

// 在2.0以後的版本如果重寫了onStartCommand,那onStart將不會被調用,注:在2.0以前是沒有onStartCommand方法
    @Override
public int onStartCommand(Intent intent, int flags, int startId) {

        Log.i("Service", "Received start id " + startId + ": " + intent);
// 如果服務進程在它啓動後(從onStartCommand()返回後)被kill掉, 那麼讓他呆在啓動狀態但不取傳給它的intent.
// 隨後系統會重寫創建service,因爲在啓動時,會在創建新的service時保證運行onStartCommand
// 如果沒有任何開始指令發送給service,那將得到null的intent,因此必須檢查它.
// 該方式可用在開始和在運行中任意時刻停止的情況,例如一個service執行音樂後臺的重放
        
return START_STICKY;
    }

    @Override
public void onDestroy() {
        mNM.cancel(NOTIFICATION);
        Toast
                .makeText(this, R.string.local_service_stopped,
                        Toast.LENGTH_SHORT).show();

    }

    @Override
public IBinder onBind(Intent intent) {
return mBinder;
    }

private final IBinder mBinder = new LocalBinder();

}


public class LocalActivity extends Activity {
/** Called when the activity is first created. */
private LocalService mBoundService;
private boolean mIsBound;

private ServiceConnection mConnection = new ServiceConnection() {

        @Override
public void onServiceDisconnected(ComponentName name) {
// 當進程崩潰時將被調用,因爲運行在同一程序,如果是崩潰將所以永遠不會發生
// 當解除綁定時也被調用
            mBoundService = null;
            Toast.makeText(LocalActivity.this,
                    R.string.local_service_disconnected, Toast.LENGTH_SHORT)
                    .show();

        }

        @Override
public void onServiceConnected(ComponentName name, IBinder service) {
// service連接建立時將調用該方法
            mBoundService = ((LocalService.LocalBinder) service).getService();
            Toast.makeText(LocalActivity.this,
                    R.string.local_service_connected, Toast.LENGTH_SHORT)
                    .show();
        }
    };

void doBindService() {
// 建立service連接。因爲我們知道程序會運行在本地裏,因此使用顯示的類名來實現service
// (但是不支持跟其他程序交互)
// 兩種傳遞,一種是在manifest裏寫好intent-filter的action,一種是顯示傳遞
// bindService(new Intent("com.LocalService.LocalService"), mConnection,
// Context.BIND_AUTO_CREATE);
//         bindService(new Intent(LocalActivity.this, LocalService.class),
//         mConnection, Context.BIND_AUTO_CREATE);

//如果用這種方法將會調用onStartCommand方法
        startService(new Intent(LocalActivity.this, LocalService.class));
        mIsBound = true;
    }

void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
            stopService(new Intent(LocalActivity.this, LocalService.class));
//            unbindService(mConnection);
            mIsBound = false;
        }
    }

    @Override
protected void onDestroy() {
        doUnbindService();

super.onDestroy();
    }

    @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

         doBindService();
    }
}
大家可以試試startService 和bindService這兩種區別。
輕鬆一下,Toast.makeText(this, "溫馨提示:\n代碼已經寫好了,如果想測試一下可以去掉註釋的喔", 2000).show(); ^-^
下面是遠程調用的例子,主要是用系統提供的Messenger,省去自己去寫複雜的aidl文件

<service android:name="MessengerService" android:process=":remote">
<intent-filter>
<action android:name="com.LocalService.MessengerService" />
</intent-filter>
</service>

如果加了android:process=":remote" ,那在調試時在service斷點是不會觸發的。

public class MessengerService extends Service {

private NotificationManager mNM;

// 保存所有跟服務連接的客戶端
    ArrayList<Messenger> mClients = new ArrayList<Messenger>();
// 保存最後一次跟服務連接的客戶端的標誌
    int mValue = 0;
// 註冊指令,Message's replyTo 字段值必須是client 的Messenger
    static final int MSG_REGISTER_CLIENT = 1;
// 取消指令,Message's replyTo 字段值必須是先前給MSG_REGISTER_CLIENT的Messenger
    static final int MSG_UNREGISTER_CLIENT = 2;
// 服務發送指令,可以在客戶端和服務直接交流
    static final int MSG_SET_VALUE = 3;

// 處理客戶端傳送過來的消息
    class IncomingHandler extends Handler {

        @Override
public void handleMessage(Message msg) {

switch (msg.what) {
case MSG_REGISTER_CLIENT:
// Optional Messenger where replies to this message can be sent.
// The semantics of exactly how this is used are up to the
// sender and receiver.
                mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
                mClients.remove(msg.replyTo);
break;
case MSG_SET_VALUE:
                mValue = msg.arg1;
for (int i = mClients.size() - 1; i >= 0; i--) {
try {
                        mClients.get(i).send(
                                Message.obtain(null, MSG_SET_VALUE, mValue, 0));

                    } catch (RemoteException e) {
// 遠程客戶端出錯,從list中移除
// 遍歷列表以保證內部循環安全運行
                        mClients.remove(i);
                    }
                }
break;
default:
super.handleMessage(msg);

            }
            Log.i("Service", "有" + mClients.size() + "客戶端");
        }
    }

// 創建一個新的Messenger跟已存在的Handler關聯
// 如果有任何消息發送到Messenger,將交給Handler處理
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    @Override
public void onCreate() {
        mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

// 在service開始時,將icon圖標放到通知任務欄
        showNotification();
    }

//
    private void showNotification() {
        CharSequence text = getText(R.string.local_service_started);

        Notification notification = new Notification(R.drawable.icon, text,
                System.currentTimeMillis());

// 當點擊通知時,啓動該contentIntent關聯的activity
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, showActivity.class), 0);

// 在通知欄上顯示標題和內容
        notification.setLatestEventInfo(this,
                getText(R.string.remote_service_label), text, contentIntent);

        mNM.notify(R.string.remote_service_started, notification);
    }
//
    @Override
public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("Service", "Received start id " + startId + ": " + intent);
return START_STICKY;
    }

    @Override
public void onDestroy() {
        mNM.cancel(R.string.remote_service_started);
        Toast.makeText(this, R.string.remote_service_stopped,
                Toast.LENGTH_SHORT).show();

    }

    @Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
    }

}

public class MessengerActivity extends Activity {
/** Called when the activity is first created. */
private Messenger mService = null;

private boolean mIsBound;

private TextView mCallbackText;

class IncomingHandler extends Handler {

        @Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MessengerService.MSG_SET_VALUE:
                mCallbackText.setText("Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
            }
        }
    }

final Messenger mMessenger = new Messenger(new IncomingHandler());

private ServiceConnection mConnection = new ServiceConnection() {

        @Override
public void onServiceDisconnected(ComponentName name) {
// 當進程崩潰時將被調用,因爲運行在同一程序,如果是崩潰將所以永遠不會發生
// 當解除綁定時也被調用
            mService = null;
            mCallbackText.setText("Disconnected.");

            Toast.makeText(MessengerActivity.this,
                    R.string.remote_service_disconnected, Toast.LENGTH_SHORT)
                    .show();

        }

        @Override
public void onServiceConnected(ComponentName name, IBinder service) {
// service連接建立時將調用該方法
// 返回IBinder接口以便我們可以跟service關聯。
// 我們可通過IDL接口來交流
            mService = new Messenger(service);
            mCallbackText.setText("Attached.");

// 只有我們連接着都監聽着服務
            try {
// 註冊
                Message msg = Message.obtain(null,
                        MessengerService.MSG_REGISTER_CLIENT);
                msg.replyTo = mMessenger;
                mService.send(msg);

// 例子
                msg = Message.obtain(null, MessengerService.MSG_SET_VALUE,
11111111, 0);
                mService.send(msg);
            } catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
            }
            Toast.makeText(MessengerActivity.this,
                    R.string.remote_service_connected, Toast.LENGTH_SHORT)
                    .show();
        }
    };

void doBindService() {
        bindService(new Intent(MessengerActivity.this, MessengerService.class),
                mConnection, Context.BIND_AUTO_CREATE);
        mIsBound = true;
        mCallbackText.setText("Binding.");

    }

void doUnbindService() {
if (mIsBound) {
if (mService != null) {
try {
// 取消註冊
                    Message msg = Message.obtain(null,
                            MessengerService.MSG_UNREGISTER_CLIENT);
                    msg.replyTo = mMessenger;
                    mService.send(msg);
                } catch (RemoteException e) {
// There is nothing special we need to do if the service
// has crashed.
                }
            }

// Detach our existing connection.
            unbindService(mConnection);
            mIsBound = false;
            mCallbackText.setText("Unbinding.");

        }
    }

    @Override
protected void onDestroy() {
        doUnbindService();
super.onDestroy();
    }

    @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
        setContentView(R.layout.messenger);
        mCallbackText = (TextView) findViewById(R.id.text);
        doBindService();
    }
}

測試遠程調用,我弄多一份項目來測試,主要是查看是否連接成功和有多少個客戶端連接上.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章