前言
又来到了源码分析,说实话在写文章之前 我并没有很仔细的阅读过 Cling 的源码,所以说 我也只是个菜比。但我会竭尽所能的把我所了解的东西分享出来,我希望对那些做 DLNA 的童鞋有所帮助。阅读源码的好处,首先就是能够更了解它的原理,这能帮助我们更好的使用它。同时,阅读源码可以能提升我们逻辑思维能力,以及设计能力,让我们能够设计出更简洁的代码。
其实我并不喜欢看别人写的源码分析,我更喜欢自己去看源码。
所以,我一直在想,
为什么要看别人写的源码分析?
- 这样做是不是能更快速的了解源码?
- 是不是源码难懂?
所以。。。
这篇文章会把 Cling 库控制设备流程大致讲一下,同时我会告诉你 我阅读 Cling 源码的过程,这样 你以后看源码的思路会更清晰一点 (多好的人啊,我都被自己感动了)。
先从控制设备流程开始说起,然后根据这个流程一一展开,介绍整体结构。这个过程也保持之前的模式:带着问题 一步一步的寻找答案。
关于 android 投屏技术系列:
一、知识概念
这章主要讲一些基本概念, 那些 DLNA 类库都是基于这些概念来做的,了解这些概念能帮助你理清思路,同时可以提升开发效率,遇到问题也能有个解决问题的清晰思路。
二、手机与tv对接
这部分是通过Cling DLNA类库来实现发现设备的。
内容包括:
- 抽出发现设备所需接口
- 发现设备步骤的实现
- 原理的分析
三、手机与tv通信
这部分也是通过Cling DLNA类库来实现手机对tv的控制。
内容包括:
- 控制设备步骤
- 控制设备代码实现
- 手机如何控制tv
- tv将自己的信息如何通知手机
- 原理的分析
关于控制设备
上篇文章解释了 什么是控制设备;控制设备过程是怎样的;以及代码的实现。
那么,Cling 源码 是怎么实现这个过程的呢?
为了简单明了,我们先忽视那些底层的东西,从最简单的开始看。
为什么要这样?
因为如果不忽视这些底层的东西,你会很容易陷入迷茫的状态。而先把外层简单的逻辑梳理清楚以后,再深入了解底层的就更容易看懂一点。
我们理一下控制设备相关的类和方法有哪些?
是否记得 控制设备 使用的三步曲?(不记得的可以翻上面的链接看,不看也不要紧)
- 获取tv设备控制服务:通过选中的设备执行
device.findService(serviceType);
- 获取控制点:通过执行
UpnpService.getControlPoint()
- 执行指定控制命令:通过执行
ControlPoint.execute(命令)
于是,我们可以根据这些入手,看它们之间是如何连接起来的,然后它们分别做了什么事情。
Service service = device.findService(serviceType);
这段代码 返回的是 服务 Service
serviceType 就是服务类型,比如 AVTransport ...
我们知道,控制设备 是通过 控制点 执行控制命令。
下面就是控制设备播放的操作:
Service avtService = device.findService(new UDAServiceType("AVTransport"));
ControlPoint controlPoint = UpnpService.getControlPoint();
controlPoint.execute(new Play(avtService) {
@Override
public void success(ActionInvocation invocation) {
super.success(invocation);
// to do success
}
@Override
public void failure(ActionInvocation invocation, UpnpResponse operation, String defaultMsg) {
// to do failure
}
}
});
可见,controlPoint.execute(new Play(avtService)
这一句很关键。
它告诉我们
- ControlPoint 有一个 execute 方法
- 执行命令时 传入了一个 Play ,Play(服务)
在分析 发现设备 源码的时候,我们得出 controlPoint.execute(..) 是通过 ExecutorService.submit(...) 执行的。 最后的执行者是 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 extends ThreadPoolExecutor。
而 ThreadPoolExecutor 也是继承于 ExecutorService。
那么 最后执行的就是 ClingExecutor.submit(Runnable)。
submit 里面的参数类型是 Runnable。那么我们看看这个 Play 是不是一个 Runnable.
我们看一下这个 Play:
public abstract class Play extends ActionCallback {
private static Logger log = Logger.getLogger(Play.class.getName());
public Play(Service service) {
this(new UnsignedIntegerFourBytes(0), service, "1");
}
public Play(Service service, String speed) {
this(new UnsignedIntegerFourBytes(0), service, speed);
}
public Play(UnsignedIntegerFourBytes instanceId, Service service) {
this(instanceId, service, "1");
}
public Play(UnsignedIntegerFourBytes instanceId, Service service, String speed) {
super(new ActionInvocation(service.getAction("Play")));
getActionInvocation().setInput("InstanceID", instanceId);
getActionInvocation().setInput("Speed", speed);
}
@Override
public void success(ActionInvocation invocation) {
log.fine("Execution successful");
}
}
Play 继承 ActionCallback 这个 ActionCallback 是什么?
service.getAction("Play")? 那么这个 Play 就是一个action了。
而 Play 继承 ActionCallback。 我们可以猜测,其它 action 也是继承 ActionCallback 的,那还有哪些操作呢?
ActionCallback的儿女们
这些类 跟 Play 的设计是一样的。
好了,我们看一下 ActionCallback:
public abstract class ActionCallback implements Runnable {
...
protected final ActionInvocation actionInvocation;
protected ControlPoint controlPoint;
public void run() {
Service service = actionInvocation.getAction().getService();
// Local execution
if (service instanceof LocalService) {
LocalService localService = (LocalService)service;
// Executor validates input inside the execute() call immediately
localService.getExecutor(actionInvocation.getAction()).execute(actionInvocation);
if (actionInvocation.getFailure() != null) {
failure(actionInvocation, null);
} else {
success(actionInvocation);
}
// Remote execution
} else if (service instanceof RemoteService){
if (getControlPoint() == null) {
throw new IllegalStateException("Callback must be executed through ControlPoint");
}
RemoteService remoteService = (RemoteService)service;
// Figure out the remote URL where we'd like to send the action request to
URL controLURL;
try {
controLURL = remoteService.getDevice().normalizeURI(remoteService.getControlURI());
} catch(IllegalArgumentException e) {
failure(actionInvocation, null, "bad control URL: " + remoteService.getControlURI());
return ;
}
// Do it
SendingAction prot = getControlPoint().getProtocolFactory().createSendingAction(actionInvocation, controLURL);
prot.run();
IncomingActionResponseMessage response = prot.getOutputMessage();
if (response == null) {
failure(actionInvocation, null);
} else if (response.getOperation().isFailed()) {
failure(actionInvocation, response.getOperation());
} else {
success(actionInvocation);
}
}
}
...
}
就如你所想到的, ActionCallback implements Runnable
在 run 方法里可看到,它是如何回调回来的。我们以 Remote execution 为例
首先获取远程设备的 url,就是远程设备地址,然后执行了这么一段代码:
SendingAction prot = getControlPoint().getProtocolFactory().createSendingAction(actionInvocation, controLURL);
prot.run();
我怀疑这个就是发送指令的过程。
IncomingActionResponseMessage response = prot.getOutputMessage();
这段应该就是获取请求结果了,之后就回调成功失败。
我们看看那段是不是发送指令过程,
如果是,它是如何做到的?
getControlPoint() 无疑就是获取到控制点。
getProtocolFactory() 返回的是一个协议工厂。
听这个名字,是不是创建协议的东西?
我们继续...
...
getProtocolFactory().createSendingAction(actionInvocation, controLURL);
像这句,最后执行了 createSendingAction 的方法,我猜想应该是发送指令的终极方法。
ProtocolFactory 的实现类是 ProtocolFactoryImpl。
我们看看 createSendingAction 这个终极方法到底干了什么?
public SendingAction createSendingAction(ActionInvocation actionInvocation, URL controlURL) {
return new SendingAction(getUpnpService(), actionInvocation, controlURL);
}
它创建了一个 SendingAction 这个名字就很直白.
继续进去 发现只是创建了它,赋了一些值给它,然后就没有然后呢
肿么回事?
噢? 我们再回去看看:
SendingAction prot = getControlPoint().getProtocolFactory().createSendingAction(actionInvocation, controLURL);
prot.run();
这下明白了, run() 之后 开始执行了
SendingAction 里 没发现 run 方法
public class SendingAction extends SendingSync {
...
}
噢? 那我们看看 SendingSync
其实也木有 run 方法
public abstract class SendingSync extends SendingAsync {
...
}
哥们,闹够了没? 终于 run 方法出现了
public abstract class SendingAsync implements Runnable {
public void run() {
try {
execute();
} catch (Exception ex) {
Throwable cause = Exceptions.unwrap(ex);
if (cause instanceof InterruptedException) {
log.log(Level.INFO, "Interrupted protocol '" + getClass().getSimpleName() + "': " + ex, cause);
} else {
throw new RuntimeException(
"Fatal error while executing protocol '" + getClass().getSimpleName() + "': " + ex, ex
);
}
}
}
protected abstract void execute() throws RouterException;
}
这个 run 方法其实只是执行了 execute
哎,我们又得回到它的子类看实现了
execute 最后执行的是 SendingAction 的 executeSync()
protected IncomingActionResponseMessage executeSync() throws RouterException {
return invokeRemote(getInputMessage());
}
我们看看 invokeRemote (注意看代码中的注释)
protected IncomingActionResponseMessage invokeRemote(OutgoingActionRequestMessage requestMessage) throws RouterException {
Device device = actionInvocation.getAction().getService().getDevice();
log.fine("Sending outgoing action call '" + actionInvocation.getAction().getName() + "' to remote service of: " + device);
IncomingActionResponseMessage responseMessage = null;
try {
// hello, 看这里 。。 这里就是重点
StreamResponseMessage streamResponse = sendRemoteRequest(requestMessage);
if (streamResponse == null) {
log.fine("No connection or no no response received, returning null");
actionInvocation.setFailure(new ActionException(ErrorCode.ACTION_FAILED, "Connection error or no response received"));
return null;
}
responseMessage = new IncomingActionResponseMessage(streamResponse);
if (responseMessage.isFailedNonRecoverable()) {
log.fine("Response was a non-recoverable failure: " + responseMessage);
throw new ActionException(
ErrorCode.ACTION_FAILED, "Non-recoverable remote execution failure: " + responseMessage.getOperation().getResponseDetails()
);
} else if (responseMessage.isFailedRecoverable()) {
handleResponseFailure(responseMessage);
} else {
handleResponse(responseMessage);
}
return responseMessage;
} catch (ActionException ex) {
log.fine("Remote action invocation failed, returning Internal Server Error message: " + ex.getMessage());
actionInvocation.setFailure(ex);
if (responseMessage == null || !responseMessage.getOperation().isFailed()) {
return new IncomingActionResponseMessage(new UpnpResponse(UpnpResponse.Status.INTERNAL_SERVER_ERROR));
} else {
return responseMessage;
}
}
}
手机发送控制命令给投屏端的方法就是 sendRemoteRequest 它。。
protected StreamResponseMessage sendRemoteRequest(OutgoingActionRequestMessage requestMessage)
throws ActionException, RouterException {
try {
log.fine("Writing SOAP request body of: " + requestMessage);
getUpnpService().getConfiguration().getSoapActionProcessor().writeBody(requestMessage, actionInvocation);
log.fine("Sending SOAP body of message as stream to remote device");
return getUpnpService().getRouter().send(requestMessage);
} catch (RouterException ex) {
Throwable cause = Exceptions.unwrap(ex);
if (cause instanceof InterruptedException) {
if (log.isLoggable(Level.FINE)) {
log.fine("Sending action request message was interrupted: " + cause);
}
throw new ActionCancelledException((InterruptedException)cause);
}
throw ex;
} catch (UnsupportedDataException ex) {
if (log.isLoggable(Level.FINE)) {
log.fine("Error writing SOAP body: " + ex);
log.log(Level.FINE, "Exception root cause: ", Exceptions.unwrap(ex));
}
throw new ActionException(ErrorCode.ACTION_FAILED, "Error writing request message. " + ex.getMessage());
}
}
writeBody(requestMessage, actionInvocation)
看到没。 就在这里将控制命令写入进来了。
然后就是
getUpnpService().getRouter().send(requestMessage);
发送出去了。
流程就是这样的,有没有疑惑的地方呢?
知道这些又有何用?知道这些就能让我过好这一生吗?
哎,过好这一生 这么容易就好了 我还写什么文章啊。
来,说说你的故事虽然无法确保能否过好这一生,但是有机会提升自己能力也是一件好事啊
duibudui
对不对..
我们先总结上面的内容,然后我分享阅读源码的方法以及 Cling 中的要点。
总结 控制设备
ControlPointImpl
是控制点的实现类,它有一个 execute 的方法,来执行控制命令。
执行 Play 其实就是执行控制点的 execute(ActionCallback callback) 这个方法
ActionCallback
它继承 Runnable,所有类似 Play 的指令都是继承它的。
这些指令最后的执行是 ActionCallback 的 run 方法;在 run 方法中执行了 SendingAction 的方法来发送指令
SendingAction
它也是继承 Runnable,它最后通过 sendRemoteRequest 这个方法完成指令的发送。
下面,我分享一下我阅读源码的方法。
我阅读源码都是从简单的入口开始入手,先跟随入口 一步一步往下走,不要在中途被其它东西打断,从入口开始就像游戏中的一条主线任务,了解完这个主线任务对你了解整个故事情节 都是有很大帮助的。
走完这段流程之后,你就有两个选择:要么继续其它的主线任务(其它执行入口),要么看支线任务(那些重要方法和重要类)。
其实这两种选择都是可以的,看你的权衡。
以 Cling 为例:
我们刚刚走完 控制设备 的主流程,我们可以走 发现设备 的主流程(上上篇文章走过了)
我们还可以看一下这些主要的方法和类
比如:
- 在 Cling 中协议是怎么创建的? 以及它是怎么跟其它部分连接起来的?
- 那些 Service 有哪些?
- 投屏端的回调有哪些内容?这些回调是怎么工作的?
- 里面的路由 又是怎么回事?它在整个过程中起了什么作用?
...
有很多很多可以分析的地方,我们还可以在发现源码的优缺点,学习它设计得好的地方,有空还可以想想 是不是也存在有待提高的地方?,反正学无止境... 对吧...
下面是源码地址,啊啊啊啊 啊 明天又是周一了
网友评论