Android基于Cling开发DLNA应用

DLNA,Digital Living Network Alliance的简称,即数字生活网络联盟。其由消费性电子、移动电话以及电脑厂商组成。目标在于创建一套可以使得各厂商的产品互相连接,互相适应的工业标准,从而为消费者实现数字化生活。

UPnP/DLNA library for Java and Android。

GitHub最多关注,当前仍在维护,许可协议为LGPL或CDDL。

以下为中文译文:

Android上的Cling

Cling Core为Android应用提供了UPnP栈。由于如今大部分Android系统都是小型手持设备,所以通常你需要写控制端应用。然而你也可以写Android上的UPnP服务应用,其所有特性Cling Core都支持。

```

Android模拟器上的Cling

在写此时,Android模拟器还不支持接收UDP组播。不过,可以发送UDP组播。你能够发送一个组播UPnP搜寻,并接收UDP单播回应,继而发现正运行的设备。你发现不了在搜寻后新开启的设备,并且在设备关闭时也收不到消息。另外,其他在你网络的控制端应用,则不能发现你本地的Android设备或服务。在你测试应用时,所有这些情况都会使你感到困惑,所以除非你真得理解哪些有作用、哪些没有,不然你应当使用一个真正的设备。

这章阐述了你如何整合Cling到你的Android应用,使其成为一个共享的部件。

```

配置应用服务

你可以在Android应用主activity中实例化Cling UpnpService。另一方面,如果你好些activities都要要求访问UPnP栈,那么最好采用后台服务,android.app.Service。之后,任何想要访问UPnP栈的activity,都能够在需要时绑定或解绑该服务。

该服务组件的接口是org.teleal.cling.android.AndroidUpnpService:

view
source
print?

1.public interface AndroidUpnpService
{

2.public UpnpService
get();

3.public UpnpServiceConfiguration
getConfiguration();

4.public Registry
getRegistry();

5.public ControlPoint
getControlPoint();

6.}

activity通常访问已知UPnP设备的注册表,或者通过ControlPoint查询和控制UPnP设备。

你必须在AndroidManifest.xml内配置内建的服务实现:

view
source
print?

01.<manifest ...>

02.

03.<uses-permission android:name="android.permission.INTERNET"/>

04.<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

05.<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>

06.<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

07.

08.<application ...>

09.

10.<activity ...>

11....

12.</activity>

13.

14.<service android:name="org.teleal.cling.android.AndroidUpnpServiceImpl"/>

15.

16.</application>

17.

18.</manifest>

此Cling UPnP服务要求设备WiFi接口的访问权限,事实上其也只将会绑定网络接口。

此服务将会自动检测WiFi接口的关闭,并优雅地处理这种情形:任何客户端操作都会导致"no response from server"状态,而你的代码必须预料并处理该状态。

当服务组件创建或销毁时,会相应开启和关闭UPnP系统。这依赖于在你的activities里是如何访问此组件的。

activity如何访问服务

service的生命周期在Android中很好的被定义了。如果service还没启动的话,第一个绑定服务的activity将会启动它。当不再有activity绑定到service上时,操作系统将会销毁此service。

让我们写一个简单的UPnP浏览activity。它用于将所有你网络内的设备显示在一个列表内,并有一个菜单选项来触发搜寻。activity连接UPnP服务之后,会一直监听注册表内设备的增加和删除,所以显示的设备列表会实时更新。

以下是activity类的骨架:

view
source
print?

01.import android.app.ListActivity;

02.import android.content.ComponentName;

03.import android.content.Context;

04.import android.content.Intent;

05.import android.content.ServiceConnection;

06.import android.os.Bundle;

07.import android.os.IBinder;

08.import android.view.Menu;

09.import android.view.MenuItem;

10.import android.widget.ArrayAdapter;

11.import android.widget.Toast;

12.import org.teleal.cling.android.AndroidUpnpService;

13.import org.teleal.cling.android.AndroidUpnpServiceImpl;

14.import org.teleal.cling.model.meta.Device;

15.import org.teleal.cling.model.meta.LocalDevice;

16.import org.teleal.cling.model.meta.RemoteDevice;

17.import org.teleal.cling.registry.DefaultRegistryListener;

18.import org.teleal.cling.registry.Registry;

19.

20.public class UpnpBrowser extends ListActivity
{

21.

22.private ArrayAdapter<DeviceDisplay>
listAdapter;

23.

24.private AndroidUpnpService
upnpService;

25.

26.private ServiceConnection
serviceConnection = ...

27.

28.private RegistryListener
registryListener = new BrowseRegistryListener();

29.

30.@Override

31.public void onCreate(Bundle
savedInstanceState) {

32.super.onCreate(savedInstanceState);

33.

34.listAdapter
=

35.new ArrayAdapter(

36.this,

37.android.R.layout.simple_list_item_1

38.);

39.setListAdapter(listAdapter);

40.

41.getApplicationContext().bindService(

42.new Intent(this,
AndroidUpnpServiceImpl.class),

43.serviceConnection,

44.Context.BIND_AUTO_CREATE

45.);

46.}

47.

48.@Override

49.protected void onDestroy()
{

50.super.onDestroy();

51.if (upnpService
!= null) {

52.upnpService.getRegistry().removeListener(registryListener);

53.}

54.getApplicationContext().unbindService(serviceConnection);

55.}

56.

57....

58.

59.}

60.```

我们采用Android运行时默认提供的布局和ListActivity父类。注意这个类可以是你应用的主activity,或者进一步上升进任务的堆栈。listAdapter黏合了Cling Registry上设备的增加移除事件与展示在用户界面的列表项目。

当没有后台服务绑定到该activity时,upnpService变量为null。绑定和解绑发生在onCreate()和onDestroy()回调,所以activity绑定服务和它的生存周期一样长。

```

暂停后台的UPnP服务

当一个activity不再活动时(停止或暂停状态),它仍会绑定着UPnP服务。UPnP服务将会持续运行,即使应用不再可见。由于UPnP服务的注册表一定会定期维护发现的设备、刷新本地设备的通告、删除过期的GENA事件订阅等,将会消耗你设备的CPU和电量。当activity onPause()或onStop()方法被调用时,你可以调用Registry#pause()来通知UPnP服务不再维护注册表。之后,你可以通过Registry#resume()来恢复后台服务,或同时用Registry#isPaused()检查状态。请阅读这些方法的Javadoc了解详细信息,以及暂停注册表维护对于设备、服务和GENA订阅的意义。

```

以下是使用ServiceConnection处理绑定和解绑服务:

view
source
print?

01.private ServiceConnection
serviceConnection = new ServiceConnection() {

02.

03.public void onServiceConnected(ComponentName
className, IBinder service) {

04.upnpService
= (AndroidUpnpService) service;

05.

06.//
Refresh the list with all known devices

07.listAdapter.clear();

08.for (Device
device : upnpService.getRegistry().getDevices()) {

09.registryListener.deviceAdded(device);

10.}

11.

12.//
Getting ready for future device advertisements

13.upnpService.getRegistry().addListener(registryListener);

14.

15.//
Search asynchronously for all devices

16.upnpService.getControlPoint().search();

17.}

18.

19.public void onServiceDisconnected(ComponentName
className) {

20.upnpService
= null;

21.}

22.};

23.```

首先,所有已知的UPnP设备能够被查询和显示(如果UPnP服务刚开启且到到目前还没有设备通告它的存在)。

然后,给UPnP服务的注册表增加一个监听者。该监听者将会处理在你网络上发现的设备的增加和移除,并在更新在用户界面列表内显示的项目。当activity销毁时,BrowseRegistryListener会被移除。

最后,通过发送一个搜寻消息给所有UPnP设备,你会开启异步搜索,此时这些设备将通告它们的存在。注意这个搜寻消息不是每次连接服务都需要的。这只需一次,在当主activity和应用启动时,其会将已知设备写入注册表。

以下是BrowseRegistryListener,他的任务就是更新列表项的显示:

view
source
print?

01.class BrowseRegistryListener extends DefaultRegistryListener
{

02.

03.@Override

04.public void remoteDeviceDiscoveryStarted(Registry
registry, RemoteDevice device) {

05.deviceAdded(device);

06.}

07.

08.@Override

09.public void remoteDeviceDiscoveryFailed(Registry
registry, final RemoteDevice device, final Exception ex) {

10.runOnUiThread(new Runnable()
{

11.public void run()
{

12.Toast.makeText(

13.BrowseActivity.this,

14."Discovery
failed of ‘" + device.getDisplayString() + "‘: " +

15.(ex
!= null ? ex.toString() : "Couldn‘t retrieve device/service descriptors"),

16.Toast.LENGTH_LONG

17.).show();

18.}

19.});

20.deviceRemoved(device);

21.}

22.

23.@Override

24.public void remoteDeviceAdded(Registry
registry, RemoteDevice device) {

25.deviceAdded(device);

26.}

27.

28.@Override

29.public void remoteDeviceRemoved(Registry
registry, RemoteDevice device) {

30.deviceRemoved(device);

31.}

32.

33.@Override

34.public void localDeviceAdded(Registry
registry, LocalDevice device) {

35.deviceAdded(device);

36.}

37.

38.@Override

39.public void localDeviceRemoved(Registry
registry, LocalDevice device) {

40.deviceRemoved(device);

41.}

42.

43.public void deviceAdded(final Device
device) {

44.runOnUiThread(new Runnable()
{

45.public void run()
{

46.DeviceDisplay
d = new DeviceDisplay(device);

47.int position
= listAdapter.getPosition(d);

48.if (position
>= 0) {

49.//
Device already in the list, re-set new value at same position

50.listAdapter.remove(d);

51.listAdapter.insert(d,
position);

52.} else {

53.listAdapter.add(d);

54.}

55.}

56.});

57.}

58.

59.public void deviceRemoved(final Device
device) {

60.runOnUiThread(new Runnable()
{

61.public void run()
{

62.listAdapter.remove(new DeviceDisplay(device));

63.}

64.});

65.}

66.}

67.```

鉴于性能的原因,当发现设备时,我们会直到一个完整的hydrated(所有设备被检索和验证)设备元数据模型可用时才执行等待。我们响应尽可能得快,同时只当remoteDeviceAdded()方法被调用时才去等待。甚至当搜索仍在运行时,我们仍旧显示所有设备。在台式电脑上你通常不需要关心这个,不过,Android手持设备效率慢,并且UPnP使用好些臃肿的XML描述符来交换关于设备和服务的元数据。有时,在设备和它的服务完全可用前,这可能会花费数秒钟。而remoteDeviceDiscoveryStarted()和remoteDeviceDiscoveryFailed()方法在搜索处理时会尽快被调用。顺便说一句,如果设备有相同的UDN就表示相等的(a.equal(b)),但它们可能不会完全一致(a==b)。

注意注册表将会在分开的线程中调用监听者方法。你必须在UI线程中更新显示列表数据。

activity中以下两个方法增加了用来搜寻的菜单,如此用户才能手动的刷新列表:

view
source
print?

01.```

02.@Override

03.public boolean onCreateOptionsMenu(Menu
menu) {

04.menu.add(0, 0, 0,
R.string.search_lan)

05..setIcon(android.R.drawable.ic_menu_search);

06.return true;

07.}

08.

09.@Override

10.public boolean onOptionsItemSelected(MenuItem
item) {

11.if (item.getItemId()
== 0 && upnpService != null) {

12.upnpService.getRegistry().removeAllRemoteDevices();

13.upnpService.getControlPoint().search();

14.}

15.return false;

16.}

17.```

最后,DeviceDisplay类是一个非常简单的JavaBean,只提供一个toString()方法来呈现列表信息。通过修改此方法,你能够显示任何关于UPnP设备的信息:

view
source
print?

01.class DeviceDisplay
{

02.Device
device;

03.

04.public DeviceDisplay(Device
device) {

05.this.device
= device;

06.}

07.

08.public Device
getDevice() {

09.return device;

10.}

11.

12.@Override

13.public boolean equals(Object
o) {

14.if (this ==
o) return true;

15.if (o
== null || getClass() != o.getClass()) return false;

16.DeviceDisplay
that = (DeviceDisplay) o;

17.return device.equals(that.device);

18.}

19.

20.@Override

21.public int hashCode()
{

22.return device.hashCode();

23.}

24.

25.@Override

26.public String
toString() {

27.//
Display a little star while the device is being loaded

28.return device.isFullyHydrated()
? device.getDisplayString() : device.getDisplayString() + " *";

29.}

30.}

31.```

还有我们必须覆盖相等操作,这样我们才可以用DeviceDisplay实例作为便捷的处理,从列表中手动地移除和增加设备。

优化服务行为

UPnP服务运行时会消耗内存和CPU。尽管通常在一个正常的机器上没有什么问题,但在Android手持设备上就可能会有了。如果你禁用Cling UPnP服务的某些功能,或者设置暂停且在合适时恢复它,你可以留有更多的内存和电量。

调整注册表维护

当服务运行时,后台有好些东西在执行。首先,有一个服务的注册表和其维护线程。如果你写一个控制端,后台注册表维护者将会定期从远程服务更新你对外的GENA订阅。当没有通知断开网络时,它也会到期并移除任何远程服务。如果你正提供服务,你的设备通告将被注册表维护者刷新,并在GENA订阅没及时更新时移除它。注册表维护者为了有效得防止UPnP网络上的过时状态,所以所有参与者会实时更新其他参与者的视图等等。

默认情况下,注册表维护者会每秒运行并检查是否有事要做(当然,大多数情况下没事做)。然而默认的Android配置有5秒的间隔休眠,所以这已经花费了更少的后台CPU占用时间 — 不过你的应用可能会暴露稍微过时的信息。在UpnpServiceConfiguration你可以通过覆盖getRegistryMaintenanceIntervalMillis()进一步的调整设置。在Android上,你必须子类化服务实现来提供一个新的配置。

view
source
print?

01.```

02.public class MyUpnpService extends AndroidUpnpServiceImpl
{

03.

04.@Override

05.protected AndroidUpnpServiceConfiguration
createConfiguration(WifiManager wifiManager) {

06.return new AndroidUpnpServiceConfiguration(wifiManager)
{

07.

08.@Override

09.public int getRegistryMaintenanceIntervalMillis()
{

10.return 7000;

11.}

12.

13.};

14.}

15.}

16.```

此时不要忘了在AndroidManifest.xml内配置MyUpnpService,而不是原先的实现。当在你的activities里绑定服务时,也必须使用该类型。

暂停和恢复注册表维护

另外一个更有效同时也不是很复杂的优化是,每当你的activites不再需要UPnP服务时,暂停和恢复注册表。这通常发生在当activity不在前台(暂停),甚至不再显示(停止)时。默认情况下,activity状态改变对UPnP服务没有影响,除非你在activities生命周期的回调内绑定和解绑服务。

除了绑定和解绑服务,你也可以在activity onPause()或onStop()方法被调用时,通过调用Registry#pause()来暂停注册表。之后,你可以通过Registry#resume()来恢复后台服务,或同时用Registry#isPaused()检查状态。

请阅读这些方法的Javadoc了解详细信息,以及暂停注册表维护对于设备、服务和GENA订阅的意义。根据你的应用要做什么,否则这种小的优化可能不值得处理这些效果。另一方面,你的应用应当能够处理失败的GENA订阅续期,或者消失的远程设备。

配置搜索

最有效的优化是UPnP设备有选择性的搜索。尽管UPnP服务的网络传输层在后台会保持运行(线程正等待且socket被绑定),这个特性允许你有选择且快速的丢弃搜索信息。

举例来说,如果你正在写一个控制端,且不通告你想要控制的服务(对其他设备没兴趣),那么你可以丢弃所有接收的搜索信息。另一方面,如果你只提供设备和服务,所有搜索信息(除了你自身服务的搜索信息)可能都可以被丢弃,你对其他远程设备和其服务一点都不会有兴趣。

一旦UDP数据包内容可用,该搜索信息就会被Cling选择并偷偷的丢弃,所以不需要进一步得解析和处理,同时CPU时间和内存消耗显著得减少,即使当你在Android手持设备上后台持续运行UPnP服务。

为了配置你的控制端应用支持哪些服务,需要覆盖前面章节展示的服务接口并提供一组ServiceType实例:

view
source
print?

01.```

02.public class MyUpnpService extends AndroidUpnpServiceImpl
{

03.

04.@Override

05.protected AndroidUpnpServiceConfiguration
createConfiguration(WifiManager wifiManager) {

06.return new AndroidUpnpServiceConfiguration(wifiManager)
{

07.

08.@Override

09.public ServiceType[]
getExclusiveServiceTypes() {

10.return new ServiceType[]
{

11.new UDAServiceType("SwitchPower")

12.};

13.}

14.

15.};

16.}

17.}

18.```

这个配置将会忽略所有不通告chemas-upnp-org:SwitchPower:1的任何通告。这是我们控制端要处理的,不需要其他任何东西了。如果你返回一个空的数组(默认行为),所有服务和设备将会发现以及没有通告会被丢弃。

如果你正在写一个控制端应用而不是服务应用,你可以让getExclusiveServiceTypes()方法返回null。这将会完全禁用搜索,此时所有设备和服务的通告一接收就会被丢弃。

时间: 2024-11-07 01:11:11

Android基于Cling开发DLNA应用的相关文章

Android基于Eclipse开发环境的搭建全总结

开始学习Android应用程序开发,首先碰到的就是开发环境的搭建.说实话此类文章网上很多,我想总结的有以下几点,以示区分: 一.简单的开发环境搭建 二.Eclipse背景和颜色配置改变 开始进入正题: 一.简单开发环境搭建 首先下载最新版本的Eclipse,如下: 我觉得这个就不用给网址了,度娘就好. 然后是下载ADT(Android Development Tools),这个官网被墙掉了,但是也可以在各处下载到.在Eclipse编译IDE环境中,需安装ADT Plug-in,这是Android

cling开发DLNA找不到class的问题!

============问题描述============ 刚刚研究cling可是发现做的demo中,运行失败,一直不解原因,一直报错这个是错误的信息 08-11 09:49:49.194: E/dalvikvm(19398): Could not find class 'org.fourthline.cling.transport.impl.AsyncServletStreamServerImpl$1', referenced from method org.fourthline.cling.t

基于Android 4.4 开发的多窗体系统 开放源代码

Hi, 这是我基于Android 4.4开发的多窗体系统,还有非常多不足,还请多多不吝赐教啊,代码已经所有开源. 视频地址 源代码地址 Done: 1. APP以窗体化显示 在 PhoneWindowManager::layoutWindowLw() 中通过packageName过滤,使指定的APP以非全屏尺寸显示,由于一个APP一般是以一个task为单位,显示多个activity,因此採用packageName过滤的方法. 2. 多个APP同一时候处于 onResume 状态 改动AMS中通知

Android源码开发利器——Java源码调试(基于4.1.2)

原文地址:http://blog.csdn.net/jinzhuojun/article/details/8868038 调试Android Java源码 草帽的后花园--Neo 写在之前的话:这里主要是以调试Java源码为主,应该说是在system_process之后的源码,这对于调试和修改frameworks层的人来说真是一个利器,但至于为什么在system_process之后,我还在分析,如果有结果我会更新此文章,并正在尝试调试C++的代码,就是native中的代码,如果这个可行那将会大大

Android 基于XMPP Smack openfire 开发的聊天室

Android基于XMPP Smack openfire 开发的聊天室

基于Android 4.4 开发的多窗口系统 开放源码

Hi, 这是我基于Android 4.4开发的多窗口系统,还有很多不足,还请多多指教啊,代码已经全部开源. 视频地址 源码地址 Done: 1. APP以窗口化显示 在 PhoneWindowManager::layoutWindowLw() 中通过packageName过滤,使指定的APP以非全屏尺寸显示,因为一个APP通常是以一个task为单位,显示多个activity,因此采用packageName过滤的方法. 2. 多个APP同时处于 onResume 状态 修改AMS中通知onPaus

基于QT 5.7.0 for Android 的 Windows 开发环境搭建

基于QT 5.7.0 for Android 的 Windows 开发环境搭建 本文属于转载原文地址https://my.oschina.net/armsky/blog/740645 一.下载软件1.jdk:jdk-8u102-windows-i586.exehttp://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.htmlhttp://download.oracle.com/otn-pub/ja

IDEA基于kotlin开发android程序配置小结

IDEA功能极其强大,和微软的宇宙第一IDE不相上下.用了很长时间,对它配置的完善性产生了近乎迷信的感情.似乎只要走正常渠道,用它来配置,没有不成功的. IDEA是开发android原生程序的利器,android studio即是基于IDEA开发的工具.AndroidStudio能干的,IDEA同样能干的很好.kotlin是jetbrain开发的语言,堪称android上的swift,而且完美兼容java,配合起来相当爽利.但偏偏在kotlin的配置上,栽了个大跟头. 在android里引入ko

android 基于百度地图api开发定位以及获取详细地址

一:百度地图开发必须要到百度开发平台android开发api下载相应的库,已经申请百度地图开发key. 二:新建项目baidumaplocation.设计main.xml文件这里注意的是MapView控件必须使用来自百度库封装好的com.baidu.mapapi.MapView .设计代码如下: Xml代码   <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&q