[Android]從一個簡單的AIDL實(shí)現(xiàn)看binder原理(三)bindService調(diào)用過程中Binder的傳遞

參考鏈接:

從一個簡單的AIDL實(shí)現(xiàn)看binder原理(一)簡單的AIDL實(shí)現(xiàn)
從一個簡單的AIDL實(shí)現(xiàn)看binder原理(二)bindService的調(diào)用過程

BindService時Binder的轉(zhuǎn)換

我們分析上面鏈接中的aidl實(shí)現(xiàn)案例美莫,在bindService時打印一下日志:

/*** MainActivity */
ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("ipcLog", "service = " + service.getClass().getName());
            proxy = IAIDLInterface.Stub.asInterface(service);
            Log.d("ipcLog", "proxy = " + proxy.getClass().getName());
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

/*** RemoteService */
@Nullable
    @Override
    public IBinder onBind(Intent intent) {
        StudentStub stub = new StudentStub();
        Log.d("ipcLog", "stub = " + stub.getClass());
        return stub;
    }
    public class StudentStub extends IAIDLInterface.Stub {
        @Override
        public List<Student> getStudent() throws RemoteException {
            return studentList;
        }

        @Override
        public void setStudent(Student student) throws RemoteException {
            studentList.add(student);
        }
    }

打印出的日志結(jié)果:
在org.hrcy.ipctest進(jìn)程中:


image.png

在org.hrcy.ipctest:remote進(jìn)程中:


image.png

在demo中我們可以看到洲赵,StudentStub 繼承于IAIDLInterface.Stub风宁, IAIDLInterface.Stub 繼承于android.os.Binder 也就是說 StudentStub是一個Binder對象,而在MainActivity打印出的log中我們看到磅崭,onServiceConnected方法接收到的是個BinderProxy譬涡,我們需要知道這個轉(zhuǎn)換是在哪里發(fā)生的

我們回顧一下上一篇博文:

bindService的調(diào)用過程

在這邊文章里合呐,我們找到了Service最終執(zhí)行的地方板乙,但是具體是怎么執(zhí)行的還沒有具體分析,下面我們分析一下onBind方法的具體執(zhí)行

從ActivityThread#handleBindService執(zhí)行開始:

private void handleBindService(BindServiceData data) {
        Service s = mServices.get(data.token);
        if (DEBUG_SERVICE)
            Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind);
        if (s != null) {
            try {
                data.intent.setExtrasClassLoader(s.getClassLoader());
                data.intent.prepareToEnterProcess();
                try {
                    if (!data.rebind) {
                        IBinder binder = s.onBind(data.intent);
                        ActivityManager.getService().publishService(
                                data.token, data.intent, binder);
                    } else {
                        s.onRebind(data.intent);
                        ActivityManager.getService().serviceDoneExecuting(
                                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
                    }
                    ensureJitEnabled();
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException(
                            "Unable to bind to service " + s
                            + " with " + data.intent + ": " + e.toString(), e);
                }
            }
        }
    }

我們看第15行具篇,從之前的分析我們可以知道纬霞,s.onBind方法執(zhí)行的就是Service的onBind方法,在這里驱显,onBind方法返回了一個Binder對象诗芜,接著執(zhí)行了

 ActivityManager.getService().publishService(data.token, data.intent, binder);

將獲得的binder對象發(fā)送了出去瞳抓,ActivityManager.getService()其實(shí)就是ActivityManagerService,這里是一個IPC的過程,具體細(xì)節(jié)不表伏恐,我們直接轉(zhuǎn)到ActivityManagerService中看publishService的實(shí)現(xiàn)

 public void publishService(IBinder token, Intent intent, IBinder service) {
        // Refuse possible leaked file descriptors
        if (intent != null && intent.hasFileDescriptors() == true) {
            throw new IllegalArgumentException("File descriptors passed in Intent");
        }

        synchronized(this) {
            if (!(token instanceof ServiceRecord)) {
                throw new IllegalArgumentException("Invalid service token");
            }
            mServices.publishServiceLocked((ServiceRecord)token, intent, service);
        }
    }

在代碼的第11行孩哑,這里轉(zhuǎn)到了ActiveServices#publishServiceLocked執(zhí)行,我們看publishServiceLocked的代碼:

void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
        final long origId = Binder.clearCallingIdentity();
        try {
            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "PUBLISHING " + r
                    + " " + intent + ": " + service);
            if (r != null) {
                Intent.FilterComparison filter
                        = new Intent.FilterComparison(intent);
                IntentBindRecord b = r.bindings.get(filter);
                if (b != null && !b.received) {
                    b.binder = service;
                    b.requested = true;
                    b.received = true;
                    for (int conni=r.connections.size()-1; conni>=0; conni--) {
                        ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni);
                        for (int i=0; i<clist.size(); i++) {
                            // 通過記錄的ConnectionRecord找到要進(jìn)行通信的進(jìn)程信息
                            ConnectionRecord c = clist.get(i);
                            if (!filter.equals(c.binding.intent.intent)) {
                                if (DEBUG_SERVICE) Slog.v(
                                        TAG_SERVICE, "Not publishing to: " + c);
                                if (DEBUG_SERVICE) Slog.v(
                                        TAG_SERVICE, "Bound intent: " + c.binding.intent.intent);
                                if (DEBUG_SERVICE) Slog.v(
                                        TAG_SERVICE, "Published intent: " + intent);
                                continue;
                            }
                            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Publishing to: " + c);
                            try {
                                // 通過connectionRecord中的conn與目標(biāo)進(jìn)程建立連接
                                c.conn.connected(r.name, service, false);
                            } catch (Exception e) {
                                Slog.w(TAG, "Failure sending service " + r.name +
                                      " to connection " + c.conn.asBinder() +
                                      " (in " + c.binding.client.processName + ")", e);
                            }
                        }
                    }
                }

                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false);
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

這個方法主要是通過需要ConnectionRecord找到與Service進(jìn)行綁定的目標(biāo)進(jìn)程翠桦,那么就會有同學(xué)發(fā)問了横蜒,這個ConnectionRecord是在哪里進(jìn)行添加記錄的呢?答案就是在我們在發(fā)起bindService的過程中销凑,在上篇博文中我們講到丛晌,在我們發(fā)戚bindService的過程中,會執(zhí)行ActivityServices#bindServiceLocked方法闻鉴,我們回顧一下這個方法:

int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
            String resolvedType, final IServiceConnection connection, int flags,
            String callingPackage, final int userId) throws TransactionTooLargeException {
        
           // 省略1萬字

            AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
            ConnectionRecord c = new ConnectionRecord(b, activity,
                    connection, flags, clientLabel, clientIntent);

            IBinder binder = connection.asBinder();
            ArrayList<ConnectionRecord> clist = s.connections.get(binder);
            if (clist == null) {
                clist = new ArrayList<ConnectionRecord>();
                s.connections.put(binder, clist);
            }
            clist.add(c);
            b.connections.add(c);
            if (activity != null) {
                if (activity.connections == null) {
                    activity.connections = new HashSet<ConnectionRecord>();
                }
                activity.connections.add(c);
            }
            b.client.connections.add(c);
            if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
                b.client.hasAboveClient = true;
            }
            if ((c.flags&Context.BIND_ALLOW_WHITELIST_MANAGEMENT) != 0) {
                s.whitelistManager = true;
            }
            if (s.app != null) {
                updateServiceClientActivitiesLocked(s.app, c, true);
            }
            clist = mServiceConnections.get(binder);
            if (clist == null) {
                clist = new ArrayList<ConnectionRecord>();
                mServiceConnections.put(binder, clist);
            }
            clist.add(c);

           // 省略1萬字

        return 1;
    }

去掉其他代碼后,這部分就是在啟動Service時記錄的啟動Service的進(jìn)程和該進(jìn)程的其他信息茂洒,看到這里我們就能理解bindService的執(zhí)行時序了:


bindService執(zhí)行的大致過程

回到之前的分析孟岛,在通過ConnectionRecord找到目標(biāo)進(jìn)城后,就要進(jìn)行IPC調(diào)用對目標(biāo)進(jìn)程進(jìn)行關(guān)聯(lián)了督勺,繼續(xù)循著ActiveServices#publishServiceLocked方法進(jìn)行分析渠羞,通過ConnectionRecord找到目標(biāo)進(jìn)程的IServiceConnection,IServiceConnection類長這樣:


image.png

看到這種格式就知道智哀,IServiceConnection類是AIDL生成的次询,它Service端的實(shí)現(xiàn)是InnerConnection,
InnerConnection是ServiceDispatcher的內(nèi)部類瓷叫,ServiceDispatcher是LoadedApk的內(nèi)部類屯吊,LoadedApk是目標(biāo)進(jìn)程APK在內(nèi)存中的具體實(shí)現(xiàn),回到ActiveServices#publishServiceLocked方法的解析摹菠,在這個方法中調(diào)用
c.conn.connected(r.name, service, false)盒卸,這是一次IPC過程,從AMS進(jìn)程跨進(jìn)程調(diào)用到了啟動要綁定Service的進(jìn)程次氨,那么connected方法的實(shí)現(xiàn)就一定是在InnerConnection類中:

private static class InnerConnection extends IServiceConnection.Stub {
            final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;

            InnerConnection(LoadedApk.ServiceDispatcher sd) {
                mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd);
            }

            public void connected(ComponentName name, IBinder service, boolean dead)
                    throws RemoteException {
                LoadedApk.ServiceDispatcher sd = mDispatcher.get();
                if (sd != null) {
                    sd.connected(name, service, dead);
                }
            }
        }

在connected這里調(diào)用了ServiceDispatcher#connected方法,看一下這個方法:

public void connected(ComponentName name, IBinder service, boolean dead) {
            if (mActivityThread != null) {
                mActivityThread.post(new RunConnection(name, service, 0, dead));
            } else {
                doConnected(name, service, dead);
            }
        }

在這里調(diào)用了doConnected方法:

public void doConnected(ComponentName name, IBinder service, boolean dead) {
            ServiceDispatcher.ConnectionInfo old;
            ServiceDispatcher.ConnectionInfo info;

            synchronized (this) {
                if (mForgotten) {
                    // We unbound before receiving the connection; ignore
                    // any connection received.
                    return;
                }
                old = mActiveConnections.get(name);
                if (old != null && old.binder == service) {
                    // Huh, already have this one.  Oh well!
                    return;
                }

                if (service != null) {
                    // A new service is being connected... set it all up.
                    info = new ConnectionInfo();
                    info.binder = service;
                    info.deathMonitor = new DeathMonitor(name, service);
                    try {
                        service.linkToDeath(info.deathMonitor, 0);
                        mActiveConnections.put(name, info);
                    } catch (RemoteException e) {
                        // This service was dead before we got it...  just
                        // don't do anything with it.
                        mActiveConnections.remove(name);
                        return;
                    }

                } else {
                    // The named service is being disconnected... clean up.
                    mActiveConnections.remove(name);
                }

                if (old != null) {
                    old.binder.unlinkToDeath(old.deathMonitor, 0);
                }
            }

            // If there was an old service, it is now disconnected.
            if (old != null) {
                mConnection.onServiceDisconnected(name);
            }
            if (dead) {
                mConnection.onBindingDied(name);
            }
            // If there is a new service, it is now connected.
            if (service != null) {
                mConnection.onServiceConnected(name, service);
            }
        }

看這個方法的最后一句蔽介,我們終于找到了onServiceConnected的執(zhí)行了

我們看一下bindService具體執(zhí)行的時序圖:


image.png

可以看到,bindService的整個執(zhí)行進(jìn)行了四次進(jìn)程跨越煮寡,可以說是非常復(fù)雜了虹蓄,但是我們在這個過程中依然沒有分析出來Binder是在什么時機(jī)轉(zhuǎn)換為BinderProxy的,篇幅有限幸撕,下一節(jié)再講吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末薇组,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子坐儿,更是在濱河造成了極大的恐慌体箕,老刑警劉巖专钉,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異累铅,居然都是意外死亡跃须,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門娃兽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菇民,“玉大人,你說我怎么就攤上這事投储〉诹罚” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵玛荞,是天一觀的道長娇掏。 經(jīng)常有香客問我,道長勋眯,這世上最難降的妖魔是什么婴梧? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮客蹋,結(jié)果婚禮上塞蹭,老公的妹妹穿的比我還像新娘。我一直安慰自己讶坯,他們只是感情好番电,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辆琅,像睡著了一般漱办。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上婉烟,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天洼冻,我揣著相機(jī)與錄音,去河邊找鬼隅很。 笑死撞牢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的叔营。 我是一名探鬼主播屋彪,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绒尊!你這毒婦竟也來了畜挥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤婴谱,失蹤者是張志新(化名)和其女友劉穎蟹但,沒想到半個月后躯泰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡华糖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年麦向,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片客叉。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡诵竭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出兼搏,到底是詐尸還是另有隱情卵慰,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布佛呻,位于F島的核電站裳朋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吓著。R本人自食惡果不足惜鲤嫡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夜矗。 院中可真熱鬧泛范,春花似錦让虐、人聲如沸紊撕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽对扶。三九已至,卻和暖如春惭缰,著一層夾襖步出監(jiān)牢的瞬間浪南,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工漱受, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留络凿,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓昂羡,卻偏偏與公主長得像絮记,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子虐先,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容