onvif 开发之video streamer---onvif实现功能和经验
有了前几篇的基础,现在可以正式开始onvif的实现工作,其中一项非常重要的部分就是视频流的对接,即能够在符合onvif标准的监控客户端软件里接收到设备端NVT发来的RTSP视频流。这里,我所用的客户端软件是Onvif Device Manager v2.2。【来自http://blog.csdn.net/ghostyu】
ONVIF Profile S Specification文档描述了Device或者说DVT和Client可以使用的一种Profile,Profile这个词在计算机领域非常常见,我们可以理解成一种方案、配置、框架等。
文档里描述了如果实现VideoStream,device和client应该具备的条件,当然如果实现文档的所有条件,就可以说该设备符合Profile S
如果单纯实现VideoStream,只需完成下列命令。
1、GetProfiles
2、GetStreamUri
填充rtsp路径,例如:rtsp://192.168.1.201/petrov.m4e
3、Media Streaming using RTSP
这里使用开源的live555,完成rtsp功能
4、GetVideoEncoderConfiguration
5、GetVideoEncoderConfigurationOptions
6、GetCapabilities
NVC为了获取DVT所支持的功能的命令
参考文档:
1、ONVIF Profile S Specification
描述ProfileS是什么样的一个东西,如何实现
2、Reference_of_ONVIF_Development_v1.01.02
Onvif DVT设计参考,指明了一条道路,但没有具体内容
3、ONVIF-Media-Service-Spec-v220
Onvif Media的说明介绍
4、http://www.onvif.org/onvif/ver20/util/operationIndex.html
onvif几乎全部命令的详细说明,非常重要。该文档告诉我们结构体成员的意义和如何填充。Onvif开发其实就是各种结构体的填充。
一、产生onvif源码框架
1、从wsdl生成C头文件
wsdl2h -o onvif.h -c -s -t .\typemap.dat http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl http://www.onvif.org/onvif/ver10/event/wsdl/event.wsdl http://www.onvif.org/onvif/ver10/display.wsdl http://www.onvif.org/onvif/ver10/deviceio.wsdl http://www.onvif.org/onvif/ver20/imaging/wsdl/imaging.wsdl http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl http://www.onvif.org/onvif/ver20/ptz/wsdl/ptz.wsdl http://www.onvif.org/onvif/ver10/receiver.wsdl http://www.onvif.org/onvif/ver10/recording.wsdl http://www.onvif.org/onvif/ver10/search.wsdl http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl http://www.onvif.org/onvif/ver10/replay.wsdl http://www.onvif.org/onvif/ver20/analytics/wsdl/analytics.wsdl http://www.onvif.org/onvif/ver10/analyticsdevice.wsdl http://www.onvif.org/onvif/ver10/schema/onvif.xsd http://www.onvif.org/ver10/actionengine.wsdl
跟前一篇discovery唯一不同的是,这里多了很多wsdl文件,这次创建完整的onvif代码框架
2、从头文件生成源码框架
soapcpp2 -c onvif.h -x -I /root/onvif/gsoap-2.8/gsoap/import -I /root/onvif/gsoap-2.8/gsoap/
产生的C文件比较庞大,最大的有十几兆,大部分的内容没有复用导致。
二、创建soap运行环境
int main(int argc, char **argv)
{
int m, s;
struct soap add_soap;
int server_udp;
server_udp = create_server_socket_udp();
//bind_server_udp1(server_udp);
pthread_t thrHello;
pthread_t thrProbe;
//pthread_create(&thrHello,NULL,main_Hello,server_udp);
//sleep(2);
pthread_create(&thrProbe,NULL,main_Probe,server_udp);
soap_init(&add_soap);
soap_set_namespaces(&add_soap, namespaces);
if (argc < 0) {
printf("usage: %s <server_port> \n", argv[0]);
exit(1);
} else {
m = soap_bind(&add_soap, NULL, 80, 100);
if (m < 0) {
soap_print_fault(&add_soap, stderr);
exit(-1);
}
fprintf(stderr, "Socket connection successful: master socket = %d\n", m);
for (;;) {
s = soap_accept(&add_soap);
if (s < 0) {
soap_print_fault(&add_soap, stderr);
exit(-1);
}
fprintf(stderr, "Socket connection successful: slave socket = %d\n", s);
soap_serve(&add_soap);
soap_end(&add_soap);
}
}
return 0;
}
注意,这里绑定了80端口,onvif使用的是http请求,然后附带xml,其实正常的是将onvif集成到web服务器中,普通的http请求有web服务器处理,onvif的http请求则有soap处理。我们这里的做法也可行,只不过onvif的访问web服务器的功能是无法使用的。
三、RTSP视频对接
1、实现GetCapabilities命令
客户端发送GetCapabilities命令来得到设备端的能力,然后依据GetCapabilities返回的结果再来进行下一步操作
在__tds__GetCapabilities函数中我们只需要填充Media部分和一些必要的即可
//想要对接RTSP视频,必须设置Media
tds__GetCapabilitiesResponse->Capabilities->Media = (struct tt__MediaCapabilities*)soap_malloc(soap, sizeof(struct tt__MediaCapabilities));
tds__GetCapabilitiesResponse->Capabilities->Media->XAddr = (char *) soap_malloc(soap, sizeof(char) * LARGE_INFO_LENGTH);
strcpy(tds__GetCapabilitiesResponse->Capabilities->Media->XAddr, _IPv4Address);
tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities = (struct tt__RealTimeStreamingCapabilities*)soap_malloc(soap, sizeof(struct tt__RealTimeStreamingCapabilities));
tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTPMulticast = (int *)soap_malloc(soap, sizeof(int));
*tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTPMulticast = _false;
tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTP_USCORETCP = (int *)soap_malloc(soap, sizeof(int));
*tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTP_USCORETCP = _true;
tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTP_USCORERTSP_USCORETCP = (int *)soap_malloc(soap, sizeof(int));
*tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->RTP_USCORERTSP_USCORETCP = _true;
tds__GetCapabilitiesResponse->Capabilities->Media->StreamingCapabilities->Extension = NULL;
tds__GetCapabilitiesResponse->Capabilities->Media->Extension = NULL;
tds__GetCapabilitiesResponse->Capabilities->Media->__size = 0;
tds__GetCapabilitiesResponse->Capabilities->Media->__any = 0;
另外必要填充的还有
//下面的重要,这里只实现视频流,需要设置VideoSources
tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->VideoSources = TRUE;
tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->VideoOutputs = FALSE;
tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->AudioSources = FALSE;
tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->AudioOutputs = FALSE;
tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->RelayOutputs = FALSE;
tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->__size = 0;
tds__GetCapabilitiesResponse->Capabilities->Extension->DeviceIO->__any = NULL;
tds__GetCapabilitiesResponse->Capabilities->Extension->Display = NULL;
tds__GetCapabilitiesResponse->Capabilities->Extension->Recording = NULL;
tds__GetCapabilitiesResponse->Capabilities->Extension->Search = NULL;
tds__GetCapabilitiesResponse->Capabilities->Extension->Replay = NULL;
tds__GetCapabilitiesResponse->Capabilities->Extension->Receiver = NULL;
tds__GetCapabilitiesResponse->Capabilities->Extension->AnalyticsDevice = NULL;
tds__GetCapabilitiesResponse->Capabilities->Extension->Extensions = NULL;
tds__GetCapabilitiesResponse->Capabilities->Extension->__size = 0;
tds__GetCapabilitiesResponse->Capabilities->Extension->__any = NULL;
2、实现GetServices命令
int __tds__GetServices(struct soap* soap, struct _tds__GetServices *tds__GetServices, struct _tds__GetServicesResponse *tds__GetServicesResponse)
{
DBG("__tds__GetServices\n");
/*该函数很必要*/
char _IPAddr[INFO_LENGTH];
int i = 0;
sprintf(_IPAddr, "http://%03d.%03d.%03d.%03d/onvif/services", 192, 168, 1, 233);
tds__GetServicesResponse->__sizeService = 1;
tds__GetServicesResponse->Service = (struct tds__Service *)soap_malloc(soap, sizeof(struct tds__Service));
tds__GetServicesResponse->Service[0].XAddr = (char *)soap_malloc(soap, sizeof(char) * INFO_LENGTH);
tds__GetServicesResponse->Service[0].Namespace = (char *)soap_malloc(soap, sizeof(char) * INFO_LENGTH);
strcpy(tds__GetServicesResponse->Service[0].Namespace, "http://www.onvif.org/ver10/events/wsdl");
strcpy(tds__GetServicesResponse[0].Service->XAddr, _IPAddr);
tds__GetServicesResponse->Service[0].Capabilities = NULL;
tds__GetServicesResponse->Service[0].Version = (struct tt__OnvifVersion *)soap_malloc(soap, sizeof(struct tt__OnvifVersion));
tds__GetServicesResponse->Service[0].Version->Major = 0;
tds__GetServicesResponse->Service[0].Version->Minor = 3;
tds__GetServicesResponse->Service[0].__any = (char **)soap_malloc(soap, sizeof(char *));
tds__GetServicesResponse->Service[0].__any[0] = (char *)soap_malloc(soap, sizeof(char) * INFO_LENGTH);
strcpy(tds__GetServicesResponse->Service[0].__any[0],"why1");
tds__GetServicesResponse->Service[0].__any[1] = (char *)soap_malloc(soap,sizeof(char) * INFO_LENGTH);
strcpy(tds__GetServicesResponse->Service[0].__any[1],"why2");
tds__GetServicesResponse->Service[0].__size = NULL;
tds__GetServicesResponse->Service[0].__anyAttribute = NULL;
return SOAP_OK;
}
3、实现GetVideoSources命令
int __tmd__GetVideoSources(struct soap* soap, struct _trt__GetVideoSources *trt__GetVideoSources, struct _trt__GetVideoSourcesResponse *trt__GetVideoSourcesResponse)
{
DBG("__tmd__GetVideoSources\n");
int size1;
size1 = 1;
trt__GetVideoSourcesResponse->__sizeVideoSources = size1;
trt__GetVideoSourcesResponse->VideoSources = (struct tt__VideoSource *)soap_malloc(soap, sizeof(struct tt__VideoSource) * size1);
trt__GetVideoSourcesResponse->VideoSources[0].Framerate = 30;
trt__GetVideoSourcesResponse->VideoSources[0].Resolution = (struct tt__VideoResolution *)soap_malloc(soap, sizeof(struct tt__VideoResolution));
trt__GetVideoSourcesResponse->VideoSources[0].Resolution->Height = 720;
trt__GetVideoSourcesResponse->VideoSources[0].Resolution->Width = 1280;
trt__GetVideoSourcesResponse->VideoSources[0].token = (char *)soap_malloc(soap, sizeof(char)*INFO_LENGTH);
strcpy(trt__GetVideoSourcesResponse->VideoSources[0].token,"GhostyuSource_token"); //注意这里需要和GetProfile中的sourcetoken相同
trt__GetVideoSourcesResponse->VideoSources[0].Imaging =(struct tt__ImagingSettings*)soap_malloc(soap, sizeof(struct tt__ImagingSettings));
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Brightness = (float*)soap_malloc(soap,sizeof(float));
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Brightness[0] = 128;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->ColorSaturation = (float*)soap_malloc(soap,sizeof(float));
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->ColorSaturation[0] = 128;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Contrast = (float*)soap_malloc(soap,sizeof(float));
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Contrast[0] = 128;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->IrCutFilter = (int *)soap_malloc(soap,sizeof(int));
*trt__GetVideoSourcesResponse->VideoSources[0].Imaging->IrCutFilter = 0;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Sharpness = (float*)soap_malloc(soap,sizeof(float));
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Sharpness[0] = 128;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->BacklightCompensation = (struct tt__BacklightCompensation*)soap_malloc(soap, sizeof(struct tt__BacklightCompensation));
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->BacklightCompensation->Mode = 0;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->BacklightCompensation->Level = 20;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Exposure = NULL;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Focus = NULL;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WideDynamicRange = (struct tt__WideDynamicRange*)soap_malloc(soap, sizeof(struct tt__WideDynamicRange));
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WideDynamicRange->Mode = 0;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WideDynamicRange->Level = 20;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WhiteBalance = (struct tt__WhiteBalance*)soap_malloc(soap, sizeof(struct tt__WhiteBalance));
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WhiteBalance->Mode = 0;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WhiteBalance->CrGain = 0;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->WhiteBalance->CbGain = 0;
trt__GetVideoSourcesResponse->VideoSources[0].Imaging->Extension = NULL;
trt__GetVideoSourcesResponse->VideoSources[0].Extension = NULL;
return SOAP_OK;
}
__tmd__GetVideoSources最重要的是token的填充,必须要和下面profile中的sourcetoken相同,需要匹配到这个视频源
4、实现GetProfiles命令
size = 1;
trt__GetProfilesResponse->Profiles =(struct tt__Profile *)soap_malloc(soap, sizeof(struct tt__Profile) * size);
trt__GetProfilesResponse->__sizeProfiles = size;
i=0;
trt__GetProfilesResponse->Profiles[i].Name = (char *)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);
strcpy(trt__GetProfilesResponse->Profiles[i].Name,"my_profile");
trt__GetProfilesResponse->Profiles[i].token= (char *)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);
strcpy(trt__GetProfilesResponse->Profiles[i].token,"token_profile");
trt__GetProfilesResponse->Profiles[i].fixed = _false;
trt__GetProfilesResponse->Profiles[i].__anyAttribute = NULL;
除了上面的基本信息,还需要填充两大项:VideoSourceConfiguration和VideoEncoderConfiguration,一个用于描述视频源的信息,另外一个描述视频的编码信息
先给VideoSourceConfiguration分配空间
trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration = (struct tt__VideoSourceConfiguration *)soap_malloc(soap,sizeof(struct tt__VideoSourceConfiguration ));
trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Name = (char *)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);
trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->token = (char *)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);
trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->SourceToken = (char *)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);
trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Bounds = (struct tt__IntRectangle *)soap_malloc(soap,sizeof(struct tt__IntRectangle));
然后在填充它
/*注意SourceToken*/
strcpy(trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Name,"VS_Name");
strcpy(trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->token,"VS_Token");
strcpy(trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->SourceToken,"GhostyuSource_token"); /*必须与__tmd__GetVideoSources中的token相同*/
trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->UseCount = 1;
trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Bounds->x = 1;
trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Bounds->y = 1;
trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Bounds->height = 720;
trt__GetProfilesResponse->Profiles[i].VideoSourceConfiguration->Bounds->width = 1280;
如果是指针必须先用soap_malloc分配内存,然后才能赋值
下面是VideoEncoderConfiguration
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration = (struct tt__VideoEncoderConfiguration *)soap_malloc(soap,sizeof(struct tt__VideoEncoderConfiguration));
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Name = (char *)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->token= (char *)soap_malloc(soap,sizeof(char)*MAX_PROF_TOKEN);
strcpy(trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Name,"VE_Name1");
strcpy(trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->token,"VE_token1");
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->UseCount = 1;
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Quality = 10;
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Encoding = 1;//JPEG = 0, MPEG4 = 1, H264 = 2;
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Resolution = (struct tt__VideoResolution *)soap_malloc(soap, sizeof(struct tt__VideoResolution));
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Resolution->Height = 720;
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->Resolution->Width = 1280;
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->RateControl = (struct tt__VideoRateControl *)soap_malloc(soap, sizeof(struct tt__VideoRateControl));
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->RateControl->FrameRateLimit = 30;
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->RateControl->EncodingInterval = 1;
trt__GetProfilesResponse->Profiles[i].VideoEncoderConfiguration->RateControl->BitrateLimit = 500;
5、GetVideoSourceConfiguration和GetVideoEncoderConfiguration
int __trt__GetVideoSourceConfiguration(struct soap* soap, struct _trt__GetVideoSourceConfiguration *trt__GetVideoSourceConfiguration, struct _trt__GetVideoSourceConfigurationResponse *trt__GetVideoSourceConfigurationResponse)
{
DBG("__trt__GetVideoSourceConfiguration\n");
//该函数必要,live video需要
return SOAP_OK;
}
int __trt__GetVideoEncoderConfiguration(struct soap* soap, struct _trt__GetVideoEncoderConfiguration *trt__GetVideoEncoderConfiguration, struct _trt__GetVideoEncoderConfigurationResponse *trt__GetVideoEncoderConfigurationResponse)
{
DBG("__trt__GetVideoEncoderConfiguration\n");
return SOAP_OK;
}
6、GetVideoEncoderConfigurationOptions
int __trt__GetVideoEncoderConfigurationOptions(struct soap* soap, struct _trt__GetVideoEncoderConfigurationOptions *trt__GetVideoEncoderConfigurationOptions, struct _trt__GetVideoEncoderConfigurationOptionsResponse *trt__GetVideoEncoderConfigurationOptionsResponse)
{
DBG("__trt__GetVideoEncoderConfigurationOptions\n");
//该函数必要,video streaming需要
return SOAP_OK;
}
以上5、6不分的代码直接返回SOAP_OK即可,正常来说是应该填充的,这里不影响RTSP Video Stream,暂时就不去动它
四、运行live555MediaServer服务器
live555官网有很多测试文件,我这里用的是MPEG4的测试文件路劲为rtsp://192.168.1.201/petrov.m4e
五、启动Onvif Device Manager测试
有一个问题,OnvifDeviceManager的并不能自动发现设备(OnvifTestTool可以),还好它提供了手动添加功能
单击add,添加如下内容:http://192.168.1.233/onvif/device_service
注意,我在程序中固定了两个IP:linux192.168.1.233,windows:192.168.1.201,这里需看情况修改
测试截图:
1、Live video
2、Video streaming
3、Profiles
最后是运行的live555 rtsp服务器
终端打印的DEBUG信息
源代码下载地址:http://download.csdn.net/detail/ghostyu/4796093
http://blog.csdn.net/ghostyu/article/details/8208428
http://blog.chinaunix.net/uid-23381466-id-3799058.html
onvif 开发之video streamer---onvif实现功能和经验的更多相关文章
- Onvif开发之Linux下gsoap的使用及移植
一直以来都是在CSDN上面学习别人的东西,很多次想写点什么但是又无从写起.由于公司项目需要,最近一段时间在研究onvif,在网上找了很多资料,发现资料是非常多,但是很少有比较全的资料,或者资料太多无从 ...
- Android开发之DatePickerDialog与TimePickerDialog的功能和使用方法具体解释
DatePickerDialog与TimePickerDialog的功能比較简单,使用方法也非常easy.仅仅要以下两步就可以. Ø 通过newkeyword创建DatePickerDialog.T ...
- iOS开发之Video转GIF
前言 最近遇到需要将video转化为gif的问题,网上找的在线转换限制太多,索性就自己写了一个工具APP.文章末尾有开源代码和打包好的APP,如有需要请自行下载. 效果图 核心代码 来源 class ...
- UWP开发之Template10实践二:拍照功能你合理使用了吗?(TempState临时目录问题)
最近在忙Asp.Net MVC开发一直没空更新UWP这块,不过有时间的话还是需要将自己的经验和大家分享下,以求共同进步. 在上章[UWP开发之Template10实践:本地文件与照相机文件操作的MVV ...
- iOS 开发之 GCD 不同场景使用
header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em} .markdown-body{overflow:hidden} .markdo ...
- 高效开发之SASS篇 灵异留白事件——图片下方无故留白 你会用::before、::after吗 link 与 @import之对比 学习前端前必知的——HTTP协议详解 深入了解——CSS3新增属性 菜鸟进阶——grunt $(#form :input)与$(#form input)的区别
高效开发之SASS篇 作为通往前端大神之路的普通的一只学鸟,最近接触了一样稍微高逼格一点的神器,特与大家分享~ 他是谁? 作为前端开发人员,你肯定对css很熟悉,但是你知道css可以自定义吗?大家 ...
- Android混合开发之WebViewJavascriptBridge实现JS与java安全交互
前言: 为了加快开发效率,目前公司一些功能使用H5开发,这里难免会用到Js与Java函数互相调用的问题,这个Android是提供了原生支持的,不过存在安全隐患,今天我们来学习一种安全方式来满足Js与j ...
- Android混合开发之WebView与Javascript交互
前言: 最近公司的App为了加快开发效率选择了一部分功能采用H5开发,从目前市面的大部分App来讲,大致分成Native App.Web App.Hybrid App三种方式,个人觉得目前以Hybri ...
- UWP开发之Template10实践:本地文件与照相机文件操作的MVVM实例(图文付原代码)
前面[UWP开发之Mvvmlight实践五:SuspensionManager中断挂起以及复原处理]章节已经提到过Template10,为了认识MvvmLight的区别特做了此实例. 原代码地址:ht ...
随机推荐
- Berkely DB Java Edition学习笔记
Berkely DB对于高并发.要求速度快的应用来说是个不错的选择,mysql就是用BDB实现的(mysql的后台) ,mysql快,BDB比mysql还要快N倍.BDB是一种嵌入式的.非关系数据库, ...
- (19)python scrapy框架
安装scrapy pycharm 建个纯python工程 settings里 环境变量设置 C:\Python27;C:\Python27\Scripts; 下载win32api https://so ...
- NanoPC-T3 64位裸机编程 —— 启动和运行状态切换
参考: https://github.com/metro94/s5p6818_spl https://github.com/trebisky/Fire3/tree/master/Boot_NSIH h ...
- Best Cow Fences
题目描述 Farmer John's farm consists of a long row of N (1 <= N <= 100,000)fields. Each field cont ...
- MariaDB半同步复制
1.主从复制原理 MySQL的二进制日志(binglog)会记录所有对数据库进行更改的操作,也就是说只要是会对数据库产生修改的操作都会被记录到二进制日志中去.记录二进制日志的主要目的有两方面:a.恢复 ...
- luogu P1616 疯狂的采药
题目背景 此题为NOIP2005普及组第三题的疯狂版. 此题为纪念LiYuxiang而生. 题目描述 LiYuxiang是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师.为此,他想拜附近最有威望的 ...
- 搭建 Maven ---基础入门
这篇随笔从最基础的控制台 搭建maven讲,后面再升入的讲解IDEA搭建Maven. 一,Maven是什么?作用是什么? Maven是一个项目管理工具,它包含了一个项目对象模型 (Project O ...
- commons-lang3-StringUtils
字符串工具类 abbreviate(String str, int maxWidth) 返回一个指定长度加省略号的字符串,maxWidth必须大于3 StringUtils.abbreviate( ...
- 向现有mvc程序中加入devexpress report
Open your ASP.NET MVC project. In the main menu of Visual Studio, click the DEVEXPRESS submenu and s ...
- advanced-performance-troubleshooting-waits-latches-spinlocks
https://www.sqlskills.com/blogs/paul/advanced-performance-troubleshooting-waits-latches-spinlocks/