android投屏技術(shù)????:發(fā)現(xiàn)設(shè)備源碼分析

cover

前言

上篇文章是關(guān)于發(fā)現(xiàn)設(shè)備代碼實(shí)現(xiàn)過(guò)程,本來(lái)這兩篇文章是一起的,寫著寫著發(fā)現(xiàn)實(shí)在是太長(zhǎng)了衍慎,我擔(dān)心會(huì)看著會(huì)消化不良,所以分開(kāi)了皮钠。

關(guān)于 android 投屏技術(shù)系列:
一稳捆、知識(shí)概念

這章主要講一些基本概念, 那些 DLNA 類庫(kù)都是基于這些概念來(lái)做的麦轰,了解這些概念能幫助你理清思路乔夯,同時(shí)可以提升開(kāi)發(fā)效率,遇到問(wèn)題也能有個(gè)解決問(wèn)題的清晰思路款侵。

二末荐、手機(jī)與tv對(duì)接

這部分是通過(guò)Cling DLNA類庫(kù)來(lái)實(shí)現(xiàn)發(fā)現(xiàn)設(shè)備的。
內(nèi)容包括:

  1. 抽出發(fā)現(xiàn)設(shè)備所需接口
  2. 發(fā)現(xiàn)設(shè)備步驟的實(shí)現(xiàn)
  3. 原理的分析

三喳坠、手機(jī)與tv通信

這部分也是通過(guò)Cling DLNA類庫(kù)來(lái)實(shí)現(xiàn)手機(jī)對(duì)tv的控制鞠评。
內(nèi)容包括:

  1. 控制設(shè)備步驟
  2. 控制設(shè)備代碼實(shí)現(xiàn)
  3. 手機(jī)如何控制tv
  4. tv將自己的信息如何通知手機(jī)
  5. 原理的分析

源碼分析階段

什么源碼?
打開(kāi)ide看源碼

我們先從入口開(kāi)始:

upnpService.getControlPoint().search();

可見(jiàn)是 控制點(diǎn)執(zhí)行的 search 方法壕鹉。而 ControlPoint 的實(shí)現(xiàn)類是 ControlPointImpl:
ControlPointImpl.search() 如下:

public void search() {
    search(new STAllHeader(), MXHeader.DEFAULT_VALUE);
}

STAllHeader 是什么玩意剃幌?進(jìn)去看看

public class STAllHeader extends UpnpHeader<NotificationSubtype> {

    public STAllHeader() {
        setValue(NotificationSubtype.ALL);
    }

    public void setString(String s) throws InvalidHeaderException {
        if (!s.equals(NotificationSubtype.ALL.getHeaderString())) {
            throw new InvalidHeaderException("Invalid ST header value (not "+NotificationSubtype.ALL+"): " + s);
        }
    }

    public String getString() {
        return getValue().getHeaderString();
    }
}

setValue(NotificationSubtype.ALL) ???

public enum NotificationSubtype {

    ALIVE("ssdp:alive"),
    UPDATE("ssdp:update"),
    BYEBYE("ssdp:byebye"),
    ALL("ssdp:all"),
    DISCOVER("ssdp:discover"),
    PROPCHANGE("upnp:propchange");

    private String headerString;

    NotificationSubtype(String headerString) {
        this.headerString = headerString;
    }

    public String getHeaderString() {
        return headerString;
    }
}

NotificationSubtype.ALL = "ssdp:all"
是否記得 ssdp 聋涨? 這個(gè)就是發(fā)現(xiàn)設(shè)備的協(xié)議, ":" 后面就是一個(gè)篩選负乡。
好了牍白,我們繼續(xù)返回到 search 中。
ControlPointImpl.search() 實(shí)際調(diào)用的是

public void search(UpnpHeader searchType, int mxSeconds) {
        log.fine("Sending asynchronous search for: " + searchType.getString());
        getConfiguration().getAsyncProtocolExecutor().execute(
                getProtocolFactory().createSendingSearch(searchType, mxSeconds)
        );
    }

下面解釋一下:

getConfiguration() 返回的對(duì)象是 UpnpServiceConfiguration
getAsyncProtocolExecutor() 返回的對(duì)象是一個(gè)執(zhí)行者 Executor
getProtocolFactory() 返回的對(duì)象是 ProtocolFactory
看起來(lái)最后執(zhí)行的是 ProtocolFactory.createSendingSearch() 方法進(jìn)行的設(shè)備發(fā)現(xiàn)抖棘。

這段代碼仍然有很多疑惑的地方:

  1. UpnpServiceConfiguration 是什么茂腥? 有什么作用?
  2. UpnpServiceConfiguration 包含了一個(gè) Executor 它如何工作的切省?
  3. ProtocolFactory 協(xié)議工廠最岗? 它跟協(xié)議有什么關(guān)系嗎?
  4. 這些亂七八糟的怎么連接起來(lái)的朝捆?

我們帶著這些問(wèn)題來(lái)解析源碼般渡。

先補(bǔ)充能量~

首先 UpnpServiceConfiguration 是不是有點(diǎn)面熟?其實(shí)在前面AndroidUpnpServiceImpl 中就有一個(gè)方法:

public UpnpServiceConfiguration getConfiguration();

這個(gè) UpnpServiceConfiguration 在 AndroidUpnpServiceImpl 中實(shí)際上是 AndroidUpnpServiceConfiguration芙盘。在 AndroidUpnpServiceImpl onCreate 時(shí)構(gòu)造的驯用。
其實(shí)這個(gè)東西,它是用于配置環(huán)境的儒老,比如 AndroidUpnpServiceConfiguration 它就用于配置 android 環(huán)境蝴乔,比如一些網(wǎng)絡(luò)、xml解析驮樊、全局使用的一些方法之類的薇正。所以想想 從這里面獲取執(zhí)行者(這個(gè)執(zhí)行者是:ClingExecutor)也比較正常了。那么如果我們有什么特殊的需要巩剖,也可以自己定義一個(gè)配置铝穷,然后增加一些自己需要的方法等钠怯。

上面提到了 ClingExecutor 它有什么用處佳魔?

我們看一下 ClingExecutor 源碼:

public static class ClingExecutor extends ThreadPoolExecutor {

        public ClingExecutor() {
            this(new ClingThreadFactory(),
                 new ThreadPoolExecutor.DiscardPolicy() {
                     // The pool is unbounded but rejections will happen during shutdown
                     @Override
                     public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
                         // Log and discard
                         log.info("Thread pool rejected execution of " + runnable.getClass());
                         super.rejectedExecution(runnable, threadPoolExecutor);
                     }
                 }
            );
        }

        public ClingExecutor(ThreadFactory threadFactory, RejectedExecutionHandler rejectedHandler) {
            // This is the same as Executors.newCachedThreadPool
            super(0,
                  Integer.MAX_VALUE,
                  60L,
                  TimeUnit.SECONDS,
                  new SynchronousQueue<Runnable>(),
                  threadFactory,
                  rejectedHandler
            );
        }

        @Override
        protected void afterExecute(Runnable runnable, Throwable throwable) {
            super.afterExecute(runnable, throwable);
            if (throwable != null) {
                Throwable cause = Exceptions.unwrap(throwable);
                if (cause instanceof InterruptedException) {
                    // Ignore this, might happen when we shutdownNow() the executor. We can't
                    // log at this point as the logging system might be stopped already (e.g.
                    // if it's a CDI component).
                    return;
                }
                // Log only
                log.warning("Thread terminated " + runnable + " abruptly with exception: " + throwable);
                log.warning("Root cause: " + cause);
            }
        }
    }

通過(guò)源碼我們可以知道兩點(diǎn):

  1. ClingExecutor 繼承 ThreadPoolExecutor(線程池) 說(shuō)明它就是執(zhí)行線程池相關(guān)配置等工作的。
  2. 里面提到了 ClingThreadFactory晦炊,說(shuō)明線程是在這里創(chuàng)建的鞠鲜。

ClingExecutor 在 AndroidUpnpServiceConfiguration 父類 DefaultUpnpServiceConfiguration 構(gòu)造中 構(gòu)造的:

   protected ExecutorService getDefaultExecutorService() {
        return defaultExecutorService;
    }

    protected ExecutorService createDefaultExecutorService() {
        return new ClingExecutor();
    }

在 發(fā)現(xiàn)設(shè)備 中,是通過(guò)獲取 AndroidUpnpServiceConfiguration 的執(zhí)行者断国,返回的就是這個(gè) ClingExecutor

    public Executor getAsyncProtocolExecutor() {
        return getDefaultExecutorService();
    }

在 控制設(shè)備 中贤姆,也是通過(guò)獲取 AndroidUpnpServiceConfiguration 中的執(zhí)行者,它返回的還是這個(gè) ClingExecutor

public ExecutorService getSyncProtocolExecutorService() {
        return getDefaultExecutorService();
    }

所以在 AndroidUpnpServiceConfiguration 中所有的返回執(zhí)行者都是返回的它稳衬。
而且控制點(diǎn)的命令都是通過(guò)這個(gè)執(zhí)行者來(lái)完成的霞捡,說(shuō)明它擔(dān)任了 Cling 中很重要的角色。

執(zhí)行過(guò)程的話薄疚,簡(jiǎn)單來(lái)說(shuō)就是發(fā)一個(gè)指令(這個(gè)指令要么是 發(fā)現(xiàn)設(shè)備協(xié)議 要么是 控制設(shè)備協(xié)議 的指令)碧信,Executor 執(zhí)行的 就是一個(gè) Runnable 了赊琳。在 發(fā)現(xiàn)設(shè)備中 這個(gè) Runnable 的實(shí)現(xiàn)類是 SendingSearch;控制設(shè)備 中實(shí)現(xiàn)類是 ActionCallback砰碴。(這篇文章 主要分析一下 發(fā)現(xiàn)設(shè)備躏筏,控制設(shè)備 下篇文章來(lái)看)

下面我們看一下 SendingSearch 的精簡(jiǎn)版源碼:

public abstract class SendingAsync implements Runnable {
...
}

public class SendingSearch extends SendingAsync {
...

    // 發(fā)現(xiàn)設(shè)備 最后執(zhí)行方法, 是它 是它 就是它呈枉。趁尼。
    protected void execute() throws RouterException {

        log.fine("Executing search for target: " + searchTarget.getString() + " with MX seconds: " + getMxSeconds());

        OutgoingSearchRequest msg = new OutgoingSearchRequest(searchTarget, getMxSeconds());
        prepareOutgoingSearchRequest(msg);

        for (int i = 0; i < getBulkRepeat(); i++) {
            try {

                getUpnpService().getRouter().send(msg);

                // UDA 1.0 is silent about this but UDA 1.1 recommends "a few hundred milliseconds"
                log.finer("Sleeping " + getBulkIntervalMilliseconds() + " milliseconds");
                Thread.sleep(getBulkIntervalMilliseconds());

            } catch (InterruptedException ex) {
                // Interruption means we stop sending search messages, e.g. on shutdown of thread pool
                break;
            }
        }
    }

...
}

我們一起分析一下,你要聽(tīng) 簡(jiǎn)單版 還是 復(fù)雜版猖辫?
簡(jiǎn)單版:
重要代碼:getUpnpService().getRouter().send(msg);
翻譯出來(lái)就是 向路由 發(fā)消息酥泞;
這個(gè)消息 msg 是 OutgoingSearchRequest 的實(shí)例,OutgoingSearchRequest 里面就封裝了 發(fā)現(xiàn)設(shè)備 的請(qǐng)求內(nèi)容啃憎。

復(fù)雜版:
%……¥……%%&%@#@@%#%%$$%&&((
(太復(fù)雜了婶博,系統(tǒng)無(wú)法翻譯)

不調(diào)你口味了。荧飞。 還是詳細(xì)看一下
這里面的疑點(diǎn):

  1. 這個(gè)路由是什么鬼凡人?
  2. SendingSearch 在哪定義的?

首先 getRouter 是不是在哪看過(guò)叹阔? 是的 在上篇文章挠轴,
AndroidUpnpServiceImpl 里面看過(guò)。在 AndroidUpnpServiceImpl 里耳幢,getRouter() 是 AndroidRouter 對(duì)象岸晦。

public class AndroidRouter extends RouterImpl {
...
}

getUpnpService().getRouter().send(msg); send 方法實(shí)際在 RouterImpl 中。
看一下:

    /**
     * Sends the UDP datagram on all bound {@link org.fourthline.cling.transport.spi.DatagramIO}s.
     *
     * @param msg The UDP datagram message to send.
     */
    public void send(OutgoingDatagramMessage msg) throws RouterException {
        lock(readLock);
        try {
            if (enabled) {
                for (DatagramIO datagramIO : datagramIOs.values()) {
                    datagramIO.send(msg);
                }
            } else {
                log.fine("Router disabled, not sending datagram: " + msg);
            }
        } finally {
            unlock(readLock);
        }
    }

DatagramIO 它的實(shí)現(xiàn)類是 DatagramIOImpl睛藻,send 方法其實(shí)就是發(fā)io流給路由了启上。
這個(gè)路由 就是封裝了一些網(wǎng)絡(luò)相關(guān)的內(nèi)容,包括網(wǎng)絡(luò)地址店印、發(fā)送io流的內(nèi)容等等冈在。

回想一下發(fā)現(xiàn)設(shè)備流程,我們首先確保 android手機(jī) 跟 tv盒子在同一個(gè)網(wǎng)絡(luò)下(這樣 tv盒子其實(shí)向路由發(fā)送了自己的信息)按摘,然后 我們的手機(jī)設(shè)備告訴路由 我們需要什么樣的設(shè)備(支持投屏)包券,路由通過(guò)我們的需求在設(shè)備列表中篩選完之后 我們就得到了這些設(shè)備。

好了炫贤,發(fā)現(xiàn)設(shè)備流程還剩最后一步就結(jié)束了:
向路由發(fā)完消息之后 我怎么得到設(shè)備列表的溅固?

  1. 這些設(shè)備保存在哪里?
  2. 我們?nèi)绾伪煌ㄖ皆O(shè)備的改變兰珍?

這些設(shè)備保存在哪里侍郭?
這些設(shè)備是保存在 RegistryImpl 中:
保存的設(shè)備有兩種:一個(gè)是 RemoteItems(遠(yuǎn)程設(shè)備,不是當(dāng)前設(shè)備);另一個(gè)是 LocalItems(本地設(shè)備,就是當(dāng)前設(shè)備)亮元。他們就相當(dāng)于列表汰寓。

我們?nèi)绾伪煌ㄖ皆O(shè)備的改變?
是否記得上篇文章提到的苹粟,監(jiān)聽(tīng)有滑。發(fā)現(xiàn)設(shè)備之后 會(huì)回調(diào)到我們定義的監(jiān)聽(tīng)。
其實(shí)在 lan 層會(huì)截獲到路由發(fā)的消息嵌削,然后會(huì)通知到我們毛好。

總結(jié)一下:

  1. AndroidUpnpServiceConfiguration 它就用于配置 android 環(huán)境,比如一些網(wǎng)絡(luò)苛秕、xml解析肌访、全局使用的一些方法之類的。那么如果我們有什么特殊的需要艇劫,也可以自己定義一個(gè)配置吼驶,然后增加一些自己需要的方法等。
  2. ClingExecutor 是一個(gè)執(zhí)行者店煞,發(fā)現(xiàn)設(shè)備蟹演、控制設(shè)備命令都是由它來(lái)執(zhí)行
  3. 發(fā)現(xiàn)設(shè)備實(shí)際是通過(guò) SendingSearch 來(lái)實(shí)現(xiàn)的,控制設(shè)備則是 ActionCallback(下篇文章會(huì)提到)
  4. SendingSearch 其實(shí)就是向路由發(fā)消息顷蟀,告訴路由 我需要什么樣的設(shè)備酒请,路由會(huì)篩選,通過(guò)我們定義的監(jiān)聽(tīng)返回給我們鸣个。

點(diǎn)擊查看詳細(xì)代碼

大功告成羞反,我終于可以開(kāi)心的玩耍了
收工

下集預(yù)告:
我們現(xiàn)在已經(jīng)成功發(fā)現(xiàn)了 tv盒子,我們要投屏必須要控制它的 播放囤萤、暫停昼窗、停止、拖拽等操作涛舍。下集我們會(huì)一步步 來(lái)實(shí)現(xiàn)這些操作澄惊。

下集看點(diǎn):

  1. 設(shè)備控制 的代碼實(shí)現(xiàn)
  2. 設(shè)備控制 原理分析
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市做盅,隨后出現(xiàn)的幾起案子缤削,更是在濱河造成了極大的恐慌,老刑警劉巖吹榴,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異滚婉,居然都是意外死亡图筹,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)远剩,“玉大人扣溺,你說(shuō)我怎么就攤上這事」衔睿” “怎么了锥余?”我有些...
    開(kāi)封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)痢掠。 經(jīng)常有香客問(wèn)我驱犹,道長(zhǎng),這世上最難降的妖魔是什么足画? 我笑而不...
    開(kāi)封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任雄驹,我火速辦了婚禮,結(jié)果婚禮上淹辞,老公的妹妹穿的比我還像新娘医舆。我一直安慰自己,他們只是感情好象缀,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布蔬将。 她就那樣靜靜地躺著,像睡著了一般央星。 火紅的嫁衣襯著肌膚如雪娃胆。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天等曼,我揣著相機(jī)與錄音里烦,去河邊找鬼。 笑死禁谦,一個(gè)胖子當(dāng)著我的面吹牛胁黑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播州泊,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼丧蘸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了遥皂?” 一聲冷哼從身側(cè)響起力喷,我...
    開(kāi)封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎演训,沒(méi)想到半個(gè)月后弟孟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡样悟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年拂募,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了庭猩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陈症,死狀恐怖蔼水,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情录肯,我是刑警寧澤趴腋,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站论咏,受9級(jí)特大地震影響优炬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜潘靖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一穿剖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧卦溢,春花似錦糊余、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至宣决,卻和暖如春蘸劈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尊沸。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工威沫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人洼专。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓棒掠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親屁商。 傳聞我的和親對(duì)象是個(gè)殘疾皇子烟很,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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