Service銷燬流程

更木小八發表於2018-11-08

文中的原始碼版本為api23

Service銷燬流程

stopService流程

流程簡圖如下

Service銷燬流程
關閉Service我們通常使用Context.stopService,該方法會經歷以下方法呼叫 Context.stopService-> ContextImpl.stopService-> ContextImpl.stopServiceCommon-> ActivityManagerService.stopServcie-> ActiveServcie.stopServiceLocked

ActiveServcie.stopServiceLocked

int stopServiceLocked(IApplicationThread caller, Intent service,
            String resolvedType, int userId) {
    //...

    // If this service is active, make sure it is stopped.
    ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, null,
            Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false);
    if (r != null) {
        if (r.record != null) {
            final long origId = Binder.clearCallingIdentity();
            try {
                stopServiceLocked(r.record);
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
            return 1;
        }
        return -1;
    }

    return 0;
}

private void stopServiceLocked(ServiceRecord service) {
    //...
    //設定startRequested為false,在後面的流程中有用
    service.startRequested = false;
    //...
    service.callStart = false;
    bringDownServiceIfNeededLocked(service, false, false);
}

複製程式碼

該方法邏輯比較簡單,通過retrieveServiceLocked方法找到服務記錄,然後呼叫過載方法stopServiceLocked繼續下面的流程,該方法內部直接呼叫了bringDownServiceIfNeededLocked

ActiveServices.bringDownServiceIfNeededLocked

private final void bringDownServiceIfNeededLocked(ServiceRecord r, 
            boolean knowConn/*false*/,
            boolean hasConn/*false*/) {

    //...

    //判斷是否有bind
    if (isServiceNeeded(r, knowConn, hasConn)) {
        return;
    }

    // Are we in the process of launching?
    if (mPendingServices.contains(r)) {
        return;
    }

    bringDownServiceLocked(r);
}
複製程式碼

該方法做了三件事情

  1. 呼叫isServiceNeeded方法判斷服務是否還有必要存在,如果有則直接返回
  2. 檢視服務是否存在於待啟動服務列表中,如果是則直接返回
  3. 上面的條件都不滿足的話則會呼叫bringDownServiceLocked繼續停止服務的流程
ActiveServices.isServiceNeeded
private final boolean isServiceNeeded(ServiceRecord r, boolean knowConn, boolean hasConn) {
    // 該欄位在stopServiceLocked方法中已經被置為false
    if (r.startRequested) {
        return true;
    }

    // knowConn此時為false,所以會走這個if流程
    if (!knowConn) {
        hasConn = r.hasAutoCreateConnections();
    }
    if (hasConn) {
        return true;
    }

    return false;
}

//ServiceRecord.java
public boolean hasAutoCreateConnections() {
    // XXX should probably keep a count of the number of auto-create
    // connections directly in the service.
    for (int conni=connections.size()-1; conni>=0; conni--) {
        ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
        for (int i=0; i<cr.size(); i++) {
            if ((cr.get(i).flags&Context.BIND_AUTO_CREATE) != 0) {
                return true;
            }
        }
    }
    return false;
}
複製程式碼

isServiceNeeded內部又呼叫了hasAutoCreateConnections hasAutoCreateConnections會檢測當前服務的繫結記錄(bindService記錄),在這些記錄中只要有使用了帶有BIND_AUTO_CREATE標誌的Intent則返回true,表示不允許關閉服務。從這點可以看出,呼叫stopService之後並不一定會真正的關閉服務。 假設服務未被繫結,我們繼續下面的流程

ActiveServices.bringDownServiceLocked

private final void bringDownServiceLocked(ServiceRecord r) {

    for (int conni=r.connections.size()-1; conni>=0; conni--) {
        ArrayList<ConnectionRecord> c = r.connections.valueAt(conni);
        for (int i=0; i<c.size(); i++) {
            ConnectionRecord cr = c.get(i);
            cr.serviceDead = true;
            try {
                //斷開客戶端連線
                //通過IPC觸發ServiceConnectino.onServiceDisconnected
                cr.conn.connected(r.name, null);
            } catch (Exception e) {
                //...
            }
        }
    }


    if (r.app != null && r.app.thread != null) {
        for (int i=r.bindings.size()-1; i>=0; i--) {
            IntentBindRecord ibr = r.bindings.valueAt(i);
            //...
            if (ibr.hasBound) {
                try {
                    //...
                    ibr.hasBound = false;
                    //通過IPC觸發Service.onUnbind方法
                    r.app.thread.scheduleUnbindService(r,
                            ibr.intent.getIntent());
                } catch (Exception e) {
                    //...
                }
            }
        }
    }

    //...

    //清理服務記錄
    final ServiceMap smap = getServiceMap(r.userId);
    smap.mServicesByName.remove(r.name);
    smap.mServicesByIntent.remove(r.intent);
    r.totalRestartCount = 0;
    unscheduleServiceRestartLocked(r, 0, true);

    //從待啟動流程中移除
    for (int i=mPendingServices.size()-1; i>=0; i--) {
        if (mPendingServices.get(i) == r) {
            mPendingServices.remove(i);
            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Removed pending: " + r);
        }
    }

    //...

    //一些清理工作
    r.clearDeliveredStartsLocked();
    r.pendingStarts.clear();

    if (r.app != null) {
        //...
        r.app.services.remove(r);
        if (r.app.thread != null) {
            //...
            try {
                //...
                mDestroyingServices.add(r);
                r.destroying = true;
                mAm.updateOomAdjLocked(r.app);
                //通過IPC觸發Service.onDestory
                r.app.thread.scheduleStopService(r);
            } catch (Exception e) {
                //...
            }
        } else {
            //...
        }
    } else {
        //...
    }

    //...
}

複製程式碼

停止服務的邏輯還是挺清晰的

  1. 關閉所有的客戶端連線,這個階段就是通過IPC觸發客戶端的ServiceConnection.onServiceDisconnected
  2. 通過IPC觸發服務的onUnbind生命週期方法
  3. 清理一些資源
  4. 通過IPC觸發服務的onDestory生命週期方法

此時服務就真正走向了生命的終點了。

unbindService流程

流程簡圖如下

Service銷燬流程

Context.unbindService方法會經歷以下呼叫鏈 Context.unbindService-> ContextImpl.unbindService-> ActivityManagerService.unbindService-> ActiveService.unbindServiceLocked

ActiveService.unbindServiceLocked

boolean unbindServiceLocked(IServiceConnection connection) {
    //對應於客戶端的ServiceConnection
    IBinder binder = connection.asBinder();

    //可以使用同一個ServiceConnection連線多個Service
    //因此這裡拿出來是一個List
    ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);
    //...

    final long origId = Binder.clearCallingIdentity();
    try {
        while (clist.size() > 0) {
            ConnectionRecord r = clist.get(0);
            removeConnectionLocked(r, null, null);
            //...
        }
    } finally {
        Binder.restoreCallingIdentity(origId);
    }

    return true;
}
複製程式碼

客戶端的一個ServiceConnection例項可以bind至多個Service,對應於AMS這邊就會儲存多個ConnectionRecordunbindServiceLocked內使用了一個while迴圈,依次對每個ConnectionRecord呼叫removeConnectionLocked

ActiveServices.removeConnectionLocked

void removeConnectionLocked(
        ConnectionRecord c, ProcessRecord skipApp, ActivityRecord skipAct) {
    IBinder binder = c.conn.asBinder();
    AppBindRecord b = c.binding;
    ServiceRecord s = b.service;
    //移除ServiceRecord以及客戶端ProcessRecord等內部維護的
    //ConnectionRecord
    ArrayList<ConnectionRecord> clist = s.connections.get(binder);
    if (clist != null) {
        clist.remove(c);
        if (clist.size() == 0) {
            s.connections.remove(binder);
        }
    }
    b.connections.remove(c);
    if (c.activity != null && c.activity != skipAct) {
        if (c.activity.connections != null) {
            c.activity.connections.remove(c);
        }
    }
    if (b.client != skipApp) {
        b.client.connections.remove(c);
        //...
    }
    clist = mServiceConnections.get(binder);
    if (clist != null) {
        clist.remove(c);
        if (clist.size() == 0) {
            mServiceConnections.remove(binder);
        }
    }

    //...

    if (b.connections.size() == 0) {
        b.intent.apps.remove(b.client);
    }
    //此處serviceDead為false
    if (!c.serviceDead) {
        //...
        if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0
                && b.intent.hasBound) {
            try {
                //...
                b.intent.hasBound = false;
                // Assume the client doesn't want to know about a rebind;
                // we will deal with that later if it asks for one.
                b.intent.doRebind = false;
                s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent());
            } catch (Exception e) {
                Slog.w(TAG, "Exception when unbinding service " + s.shortName, e);
                serviceProcessGoneLocked(s);
            }
        }

        //這個flags就是呼叫bindService時用的flags
        if ((c.flags&Context.BIND_AUTO_CREATE) != 0) {
            boolean hasAutoCreate = s.hasAutoCreateConnections();
            //...
            bringDownServiceIfNeededLocked(s, true, hasAutoCreate);
        }
    }
}
複製程式碼

removeConnectionLocked首先會做一些清理工作,之後會呼叫ApplicationThread.scheduleUnbindService方法觸發Service.onUnbind 其次,如果發起bind請求時所用的flags中包含BIND_AUTO_CREATE標誌,還會觸發bringDownServiceIfNeededLocked hasAutoCreateConnectionsbringDownServiceIfNeededLocked方法我們在分析stopService流程的時候已經分析過了,就不展開講了,這裡只討論bringDownServiceIfNeededLocked入參變化而引起的一些變化。 假設當前沒有其他客戶端繫結至該服務,那麼此時hasAutoCreate應該為false,那麼bringDownServiceIfNeededLocked的形參,knowConntruehasConnfalse 這兩個引數,只會在bringDownServiceIfNeededLocked內呼叫isServiceNeeded方法使用到,再貼一下這個方法的程式碼

private final boolean isServiceNeeded(ServiceRecord r, boolean knowConn/*true*/, boolean hasConn/*false*/) {
    // startRequested只有在呼叫過startService才會被置為true
    // 這裡為false
    if (r.startRequested) {
        return true;
    }

    // Is someone still bound to us keepign us running?
    if (!knowConn) {
        hasConn = r.hasAutoCreateConnections();
    }
    if (hasConn) {
        return true;
    }

    return false;
}
複製程式碼

可以看到isServiceNeeded此時返回false,因此銷燬的流程還會繼續進行下去。 後續的流程就跟stopService的一樣了。

相關文章