Android必知必會的四大元件 -- Service篇

ClericYi發表於2020-02-25

Android必知必會的四大元件 -- Service篇

前言

寫著一篇文章的原因,主要是因為在面試中,服務這個關鍵詞的出現頻率非常高。很多時候,面試官會問你,Service中能否進行耗時操作? 我們稍後就會揭曉那麼這個答案。

思維導圖

Android必知必會的四大元件 -- Service篇

生命週期

生命週期圖示

由圖中可以直觀的看出幾點。

啟動方式 存在方式
startService() 獨立於Activity執行,自行結束或被叫停
bindService() 繫結於Activity執行,Activity結束時,會被叫停
涉及方法 用途
onCreate()
onDestroy()
onStartCommand() 用於計數,服務被呼叫的次數
onBind() 與Activity元件繫結
onUnbind() 與Activity元件解綁

使用方法

Service方法需要在AndroidManifest.xml中進行註冊

// 第一步:在AndroidManifest.xml中進行註冊
<service android:name=".LocalService"/>

// 第二步:啟動
① startService(Intent);
② bindService(Intent, ServiceConnection, Int);

// 第三步:解綁(使用方法② 啟動時操作)
unBindService(ServiceConnection);

// 第四步:暫停
stopService(Intent);
複製程式碼

Activity和Service的通訊

ActivityService的通訊其實就是基於IBinder來進行實現的。但是IBinder其實是一個介面,對我們而言一般使用他的實現類Binder並通過強制轉換來完成操作。

/**
 * Service方法繼承
 * onBind()是一個抽象方法。
 */
public class LocalService extends Service {
    private final IBinder binder = new ServiceBinder();

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

    public class ServiceBinder extends Binder {
        LocalService getLocalService(){
            return LocalService.this;
        }
    }
}
複製程式碼

以上程式碼,是一個用於通訊的基礎版本。

既然需要通訊,那我們總需要知道對方是誰,如果使用的是startService(),上文已經提到他是獨立於Activity的,所以勢必使用的是bindService()

在上文的使用方法中已經提到了bindService()使用到的引數,IntentServiceConnectionInt

ServiceConnection

/**
 * bindService()方法中的引數之一。
 * 用於對service進行操作
 */
ServiceConnection connection = new ServiceConnection() {
           // Activity和Service繫結時呼叫
            @Override
            public void onServiceConnected(ComponentName name, IBinder binder) {
                // 基於Binder拿到我們要的Service
                service = ((LocalService.ServiceBinder)binder).getLocalService();
                // 幹你需要乾的事情
            }
            // Activity和Service解綁時呼叫
            @Override
            public void onServiceDisconnected(ComponentName name) {
                service = null;
            }
        };
複製程式碼

Int

  • BIND_AUTO_CREATE:收到繫結需求,如果Service尚未建立,則立即建立。
  • BIND_DEBUG_UNBIND:用於測試使用,對unbind呼叫不匹配的除錯幫助。
  • BIND_NOT_FOREGROUND:不允許此繫結將目標服務的程式提升到前臺排程優先順序

這是一個已經存在於Service類中的值,這裡並不全部例舉,一般來說都是使用BIND_AUTO_CREATE

必須要呼叫的unbindService(ServiceConnection)

為什麼我們一定要呼叫這個方法,如果我們不解綁會出現什麼樣的問題?

經過測試,Logcat中爆出了這樣的錯誤Activity has leaked ServiceConnection that was originally bound here。也就是說ServiceConnection記憶體洩漏了。這也是為什麼我們一直說需要解綁的原因。

IntentService

public class LocalIntentService extends IntentService {
    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public LocalIntentService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        
    }
}
複製程式碼

先看一段我們的繼承程式碼,和Service不同的地方就是,必須重寫的方法是onHandleIntent(Intent intent)。 那我們也和之前一樣做一個原始碼導讀好了。

IntentService原始碼導讀

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    @UnsupportedAppUsage
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;
    
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
}
複製程式碼

其實從整個程式碼的變數我們已經可以做一個猜測了。Looper+Handler+Service的組成成分。那它的處理過程勢必依賴於一個Handler的通訊機制。另外看到了ServiceHandler中的stopSelf()方法,我們也就清楚了一個問題為什麼我們不需要去控制IntentService的暫停。

接下來從生命週期的角度來看看這個IntentService,因為Binder機制上是一致的,所以分析主線就是onCreate() --> onStartCommand() --> onDestroy()

onCreate()

public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
複製程式碼

建立了一個HandlerThread,去初始化了LooperHandler,也就說明服務在內部處理。

onStartCommand()

public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId); // 1 -->
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; // 2 -->
    }
複製程式碼

onStart()

    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
複製程式碼

其他都是和Handler一致的,整體流程也就是Message封裝,再通過Handler進行一個傳送。

mRedelivery

這個變數是幹什麼的?

/**
     * Constant to return from {@link #onStartCommand}: if this service's
     * process is killed while it is started (after returning from
     * {@link #onStartCommand}), and there are no new start intents to
     * deliver to it, then take the service out of the started state and
     * don't recreate until a future explicit call to
     * {@link Context#startService Context.startService(Intent)}.  The
     * service will not receive a {@link #onStartCommand(Intent, int, int)}
     * call with a null Intent because it will not be restarted if there
     * are no pending Intents to deliver.
     * 
     * <p>This mode makes sense for things that want to do some work as a
     * result of being started, but can be stopped when under memory pressure
     * and will explicit start themselves again later to do more work.  An
     * example of such a service would be one that polls for data from
     * a server: it could schedule an alarm to poll every N minutes by having
     * the alarm start its service.  When its {@link #onStartCommand} is
     * called from the alarm, it schedules a new alarm for N minutes later,
     * and spawns a thread to do its networking.  If its process is killed
     * while doing that check, the service will not be restarted until the
     * alarm goes off.
     */
    public static final int START_NOT_STICKY = 2;

    /**
     * Constant to return from {@link #onStartCommand}: if this service's
     * process is killed while it is started (after returning from
     * {@link #onStartCommand}), then it will be scheduled for a restart
     * and the last delivered Intent re-delivered to it again via
     * {@link #onStartCommand}.  This Intent will remain scheduled for
     * redelivery until the service calls {@link #stopSelf(int)} with the
     * start ID provided to {@link #onStartCommand}.  The
     * service will not receive a {@link #onStartCommand(Intent, int, int)}
     * call with a null Intent because it will only be restarted if
     * it is not finished processing all Intents sent to it (and any such
     * pending events will be delivered at the point of restart).
     */
    public static final int START_REDELIVER_INTENT = 3;
複製程式碼

一大段冗長的英文很煩,更何況我也就低分飄過6級的水平呢,哈哈哈哈!!

就不折磨你們了,直接做出一個解釋吧。

  • START_NOT_STICKY:預設模式,這是一個容許被殺的模式,隨時允許被叫停
  • START_REDELIVER_INTENT:告訴系統在崩潰後重新啟動服務,並重新傳遞在崩潰時存在的意圖

好了,以上基本就是整個IntentService的介紹了。

總結

  1. 在ANR機制中,Service的響應時長不能超過20s,其實也可以比較直觀的看出,Service其實並不能進行所謂耗時操作。但是如果加上了Thread進行非同步處理,那麼其實他還是可以進行耗時操作的。(具體看你怎麼進行回答,主要還是一個知識點,Service執行在主執行緒)
  2. Service存在的原因是Activity是一個經常會被銷燬的元件,雖然我們同樣可以通過Thread進行非同步操作,但是當Activity例項被銷燬時,相應的捆綁在Activity生命週期內的Thread例項我們也沒有能力再去尋找了。

以上就是我的學習成果,如果有什麼我沒有思考到的地方或是文章記憶體在錯誤,歡迎與我分享。


相關文章推薦:

手撕AsyncTask

幾張圖讓你明白Android四大啟動模式

Glide都在用的LruCache,你會幾分?

相關文章