前言
上篇文章是關(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)容包括:
- 抽出發(fā)現(xiàn)設(shè)備所需接口
- 發(fā)現(xiàn)設(shè)備步驟的實(shí)現(xiàn)
- 原理的分析
三喳坠、手機(jī)與tv通信
這部分也是通過(guò)Cling DLNA類庫(kù)來(lái)實(shí)現(xiàn)手機(jī)對(duì)tv的控制鞠评。
內(nèi)容包括:
- 控制設(shè)備步驟
- 控制設(shè)備代碼實(shí)現(xiàn)
- 手機(jī)如何控制tv
- tv將自己的信息如何通知手機(jī)
- 原理的分析
源碼分析階段
我們先從入口開(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)抖棘。
這段代碼仍然有很多疑惑的地方:
- UpnpServiceConfiguration 是什么茂腥? 有什么作用?
- UpnpServiceConfiguration 包含了一個(gè) Executor 它如何工作的切省?
- ProtocolFactory 協(xié)議工廠最岗? 它跟協(xié)議有什么關(guān)系嗎?
- 這些亂七八糟的怎么連接起來(lái)的朝捆?
我們帶著這些問(wèn)題來(lái)解析源碼般渡。
首先 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):
- ClingExecutor 繼承 ThreadPoolExecutor(線程池) 說(shuō)明它就是執(zhí)行線程池相關(guān)配置等工作的。
- 里面提到了 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):
- 這個(gè)路由是什么鬼凡人?
- 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è)備列表的溅固?
- 這些設(shè)備保存在哪里?
- 我們?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é)一下:
- AndroidUpnpServiceConfiguration 它就用于配置 android 環(huán)境,比如一些網(wǎng)絡(luò)苛秕、xml解析肌访、全局使用的一些方法之類的。那么如果我們有什么特殊的需要艇劫,也可以自己定義一個(gè)配置吼驶,然后增加一些自己需要的方法等。
- ClingExecutor 是一個(gè)執(zhí)行者店煞,發(fā)現(xiàn)設(shè)備蟹演、控制設(shè)備命令都是由它來(lái)執(zhí)行
- 發(fā)現(xiàn)設(shè)備實(shí)際是通過(guò) SendingSearch 來(lái)實(shí)現(xiàn)的,控制設(shè)備則是 ActionCallback(下篇文章會(huì)提到)
- 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):
- 設(shè)備控制 的代碼實(shí)現(xiàn)
- 設(shè)備控制 原理分析