前言
上篇文章是关于发现设备代码实现过程,本来这两篇文章是一起的,写着写着发现实在是太长了,我担心会看着会消化不良,所以分开了。
关于 android 投屏技术系列:
一、知识概念
这章主要讲一些基本概念, 那些 DLNA 类库都是基于这些概念来做的,了解这些概念能帮助你理清思路,同时可以提升开发效率,遇到问题也能有个解决问题的清晰思路。
二、手机与tv对接
这部分是通过Cling DLNA类库来实现发现设备的。
内容包括:
- 抽出发现设备所需接口
- 发现设备步骤的实现
- 原理的分析
三、手机与tv通信
这部分也是通过Cling DLNA类库来实现手机对tv的控制。
内容包括:
- 控制设备步骤
- 控制设备代码实现
- 手机如何控制tv
- tv将自己的信息如何通知手机
- 原理的分析
源码分析阶段
我们先从入口开始:
upnpService.getControlPoint().search();
可见是 控制点执行的 search 方法。而 ControlPoint 的实现类是 ControlPointImpl:
ControlPointImpl.search() 如下:
public void search() {
search(new STAllHeader(), MXHeader.DEFAULT_VALUE);
}
STAllHeader 是什么玩意?进去看看
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 ? 这个就是发现设备的协议, ":" 后面就是一个筛选。
好了,我们继续返回到 search 中。
ControlPointImpl.search() 实际调用的是
public void search(UpnpHeader searchType, int mxSeconds) {
log.fine("Sending asynchronous search for: " + searchType.getString());
getConfiguration().getAsyncProtocolExecutor().execute(
getProtocolFactory().createSendingSearch(searchType, mxSeconds)
);
}
下面解释一下:
getConfiguration() 返回的对象是 UpnpServiceConfiguration
getAsyncProtocolExecutor() 返回的对象是一个执行者 Executor
getProtocolFactory() 返回的对象是 ProtocolFactory
看起来最后执行的是 ProtocolFactory.createSendingSearch() 方法进行的设备发现。
这段代码仍然有很多疑惑的地方:
- UpnpServiceConfiguration 是什么? 有什么作用?
- UpnpServiceConfiguration 包含了一个 Executor 它如何工作的?
- ProtocolFactory 协议工厂? 它跟协议有什么关系吗?
- 这些乱七八糟的怎么连接起来的?
我们带着这些问题来解析源码。
首先 UpnpServiceConfiguration 是不是有点面熟?其实在前面AndroidUpnpServiceImpl 中就有一个方法:
public UpnpServiceConfiguration getConfiguration();
这个 UpnpServiceConfiguration 在 AndroidUpnpServiceImpl 中实际上是 AndroidUpnpServiceConfiguration。在 AndroidUpnpServiceImpl onCreate 时构造的。
其实这个东西,它是用于配置环境的,比如 AndroidUpnpServiceConfiguration 它就用于配置 android 环境,比如一些网络、xml解析、全局使用的一些方法之类的。所以想想 从这里面获取执行者(这个执行者是:ClingExecutor)也比较正常了。那么如果我们有什么特殊的需要,也可以自己定义一个配置,然后增加一些自己需要的方法等。
上面提到了 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);
}
}
}
通过源码我们可以知道两点:
- ClingExecutor 继承 ThreadPoolExecutor(线程池) 说明它就是执行线程池相关配置等工作的。
- 里面提到了 ClingThreadFactory,说明线程是在这里创建的。
ClingExecutor 在 AndroidUpnpServiceConfiguration 父类 DefaultUpnpServiceConfiguration 构造中 构造的:
protected ExecutorService getDefaultExecutorService() {
return defaultExecutorService;
}
protected ExecutorService createDefaultExecutorService() {
return new ClingExecutor();
}
在 发现设备 中,是通过获取 AndroidUpnpServiceConfiguration 的执行者,返回的就是这个 ClingExecutor
public Executor getAsyncProtocolExecutor() {
return getDefaultExecutorService();
}
在 控制设备 中,也是通过获取 AndroidUpnpServiceConfiguration 中的执行者,它返回的还是这个 ClingExecutor
public ExecutorService getSyncProtocolExecutorService() {
return getDefaultExecutorService();
}
所以在 AndroidUpnpServiceConfiguration 中所有的返回执行者都是返回的它。
而且控制点的命令都是通过这个执行者来完成的,说明它担任了 Cling 中很重要的角色。
执行过程的话,简单来说就是发一个指令(这个指令要么是 发现设备协议 要么是 控制设备协议 的指令),Executor 执行的 就是一个 Runnable 了。在 发现设备中 这个 Runnable 的实现类是 SendingSearch;控制设备 中实现类是 ActionCallback。(这篇文章 主要分析一下 发现设备,控制设备 下篇文章来看)
下面我们看一下 SendingSearch 的精简版源码:
public abstract class SendingAsync implements Runnable {
...
}
public class SendingSearch extends SendingAsync {
...
// 发现设备 最后执行方法, 是它 是它 就是它。。
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;
}
}
}
...
}
我们一起分析一下,你要听 简单版 还是 复杂版?
简单版:
重要代码:getUpnpService().getRouter().send(msg);
翻译出来就是 向路由 发消息;
这个消息 msg 是 OutgoingSearchRequest 的实例,OutgoingSearchRequest 里面就封装了 发现设备 的请求内容。
复杂版:
%……¥……%%&%@#%$$%&&((
(太复杂了,系统无法翻译)
不调你口味了。。 还是详细看一下
这里面的疑点:
- 这个路由是什么鬼?
- SendingSearch 在哪定义的?
首先 getRouter 是不是在哪看过? 是的 在上篇文章,
AndroidUpnpServiceImpl 里面看过。在 AndroidUpnpServiceImpl 里,getRouter() 是 AndroidRouter 对象。
public class AndroidRouter extends RouterImpl {
...
}
getUpnpService().getRouter().send(msg); send 方法实际在 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 它的实现类是 DatagramIOImpl,send 方法其实就是发io流给路由了。
这个路由 就是封装了一些网络相关的内容,包括网络地址、发送io流的内容等等。
回想一下发现设备流程,我们首先确保 android手机 跟 tv盒子在同一个网络下(这样 tv盒子其实向路由发送了自己的信息),然后 我们的手机设备告诉路由 我们需要什么样的设备(支持投屏),路由通过我们的需求在设备列表中筛选完之后 我们就得到了这些设备。
好了,发现设备流程还剩最后一步就结束了:
向路由发完消息之后 我怎么得到设备列表的?
- 这些设备保存在哪里?
- 我们如何被通知到设备的改变?
这些设备保存在哪里?
这些设备是保存在 RegistryImpl 中:
保存的设备有两种:一个是 RemoteItems(远程设备,不是当前设备);另一个是 LocalItems(本地设备,就是当前设备)。他们就相当于列表。
我们如何被通知到设备的改变?
是否记得上篇文章提到的,监听。发现设备之后 会回调到我们定义的监听。
其实在 lan 层会截获到路由发的消息,然后会通知到我们。
总结一下:
- AndroidUpnpServiceConfiguration 它就用于配置 android 环境,比如一些网络、xml解析、全局使用的一些方法之类的。那么如果我们有什么特殊的需要,也可以自己定义一个配置,然后增加一些自己需要的方法等。
- ClingExecutor 是一个执行者,发现设备、控制设备命令都是由它来执行
- 发现设备实际是通过 SendingSearch 来实现的,控制设备则是 ActionCallback(下篇文章会提到)
- SendingSearch 其实就是向路由发消息,告诉路由 我需要什么样的设备,路由会筛选,通过我们定义的监听返回给我们。
点击查看详细代码
大功告成,我终于可以开心的玩耍了
下集预告:
我们现在已经成功发现了 tv盒子,我们要投屏必须要控制它的 播放、暂停、停止、拖拽等操作。下集我们会一步步 来实现这些操作。
下集看点:
- 设备控制 的代码实现
- 设备控制 原理分析