所有由liby发布的文章

Windows平台下Qt5.1 for Android的安卓开发环境

前段时间Qt5.1正式版发布,支持Android和IOS的开发,强大的可移植性和跨平台特性非常的吸引人,并且使用C++。由于之前做过Qt在嵌入式平台的应用,所以顺便试试新版本的Qt如何,再看看Qt5.1 for Android是否足够优秀呢,环境搭建如下,可能会有疏漏之处。
整个配置过程可以参考官方Wiki
首先是各种软件的下载,先不管目前对开发有没有用吧,整上再说,包括Perl、Mingw等等。
Perl
MSYS
MinGW
Ant
JDK
NDK
SDK
Qt5.1 for Android

有些版本可能会有更新,根据自己的情况下载需要的版本。我最终下载的文件名如下(文件顺序与上对应):
strawberry-perl-5.16.3.1-32bit.msi
msys+7za+wget+svn+git+mercurial+cvs-rev13.7z
x32-4.8.1-release-win32-dwarf-rev3.7z
apache-ant-1.9.2-bin.zip
jdk-7u25-windows-i586.exe
android-ndk-r9-windows-x86.zip
adt-bundle-windows-x86-20130729.zip
qt-windows-opensource-5.1.0-android-x86-win32-offline.exe

各个文件解压或则安装,现在需要设置一下环境变量。我先说一下我的文件路径如下(路径顺序与上对应):
C:\strawberry
C:\mingw-builds\msys
C:\mingw-builds\mingw32
C:\apache-ant-1.9.2
C:\Program Files\Java\jdk1.7.0_25
C:\android-ndk-r9
C:\adt-bundle-windows-x86-20130729
C:\Qt\Qt5.1.0

现在来设置环境变量,所有操作全部在环境变量中的系统变量里面。
增加变量名JAVA_HOME,变量值C:\Program Files\Java\jdk1.7.0_25
增加变量名CLASSPATH,变量值.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\toos.jar
修改变量名Path,增加变量值C:\strawberry\perl\bin;%JAVA_HOME%\bin;C:\mingw-builds\mingw32\bin;C:\adt-bundle-windows-x86-20130729\sdk\platform-tools;C:\adt-bundle-windows-x86-20130729\sdk\tools;C:\android-ndk-r9;

这样环境变量也就设置完了,测试下是否可用,CMD下分别输入以下命令是否发生错误:
java -version
javac -version
gcc -v
emulator -version
adb -version
mingw32-make -version

然后是Qt的设置如下图:

Qt5.1 performance

至于SDK Manager的相关内容在这不多说了,至此你可以开始C++开发Android应用了!

————————

2013.8.15

经过目前测试NDK暂时无法使用r9版本(当然,不排除是我的环境问题),会出现Qt无法自动找到构建工具的情况,虽然可以编译也可以在模拟器上运行,但是无法生成APK。如下图:

kit

主要原因如上图,路径不正确,正确路径应该是”\prebuilt\winodws\bin”。
不论是添加“ANDROID_NDK_HOST=windows”环境变量,还是手动添加构建套件Kit都无法解决问题。前者将如下一张图中ANDROID_NDK_HOST的值变为空值,手动修改无效;后者会导致Qt版本和编译器出错。
使用android-ndk-r8e-windows-x86.zip则正常,暂且不知道原因,但是应该和NDK的环境变量有关,一切参考官方WIKI为准。至于官方文档提到的

Add some environment variables:
set “ANDROID_NDK_PLATFORM=android-9”
set “ANDROID_TARGET_ARCH=armeabi-v7a”
set “ANDROID_BUILD_TOOLS_REVISION=17.0.0”
set “ANDROID_NDK_HOST=windows-x86_64”
or
set “ANDROID_NDK_HOST=windows”
.. depending on which NDK you downloaded.

可以尝试不添加,我使用android-ndk-r8e-windows-x86.zip这个NDK并且设置好NDK路径以后,Qt自动添加变量,但不会在系统环境变量中修改。如下图:

environment

可以生成APK,可以安装运行,如下图:

Screenshot_2013-08-15-14-33-17

Google Maps API 代码示例

之前在Qt上做过一个关于Google Map相关程序,用到了Google Maps API,下面是一个示例样本,有很多不尽之处,仅供参考。

[code lang=”javascript”]
<html>
<head>
<style type="text/css">
div#map_canvas
{
position:relative;
}
div#crosshair
{
position:absolute;
top:50%;
height:19px;
width:19px;
left:50%;
display:block;
margin-left:-8px;
background:url(crosshair.gif);
background-repeat:no-repeat;
background-position:center;
}
#panel {
position:absolute;
top:5px;
left:50%;
width:536px;
margin:0 0 0 -268px;
}

</style>
<link href="/maps/documentation/javascript/examples/default.css" rel="stylesheet">
<script type="text/javascript" src="http://map.google.com/maps/api/js?sensor=false&language=zh-cn&libraries=panoramio"></script>
<script src="https://ssl.panoramio.com/wapi/wapi.js?v=1"></script>
<script type="text/javascript">
var map;
var geocoder;
var addressresults;
var lastrelease;
var lastcenter;
var maker;
var markers = [];
var directionsDisplay;
var directionsService = new google.maps.DirectionsService();
var start;
var end;

/**
* The HomeControl adds a control to the map that
* returns the user to the control’s defined home.
*/

// Define a property to hold the Home state
HomeControl.prototype.home_ = null;

// Define setters and getters for this property
HomeControl.prototype.getHome = function() {
return this.home_;
}

HomeControl.prototype.setHome = function(home) {
this.home_ = home;
}

/** @constructor */
function HomeControl(controlDiv, map, home) {

// We set up a variable for this since we’re adding
// event listeners later.
var control = this;

// Set the home property upon construction
control.home_ = home;

// Set CSS styles for the DIV containing the control
// Setting padding to 5 px will offset the control
// from the edge of the map
controlDiv.style.padding = ‘5px’;

// Set CSS for the control border
var goHomeUI = document.createElement(‘div’);
goHomeUI.style.backgroundColor = ‘white’;
goHomeUI.style.borderStyle = ‘solid’;
goHomeUI.style.borderWidth = ‘2px’;
goHomeUI.style.cursor = ‘pointer’;
goHomeUI.style.textAlign = ‘center’;
goHomeUI.title = ‘Click to set the map to Home’;
controlDiv.appendChild(goHomeUI);

// Set CSS for the control interior
var goHomeText = document.createElement(‘div’);
goHomeText.style.fontFamily = ‘Arial,sans-serif’;
goHomeText.style.fontSize = ’12px’;
goHomeText.style.paddingLeft = ‘4px’;
goHomeText.style.paddingRight = ‘4px’;
goHomeText.innerHTML = ‘<b>Home</b>’;
goHomeUI.appendChild(goHomeText);

// Set CSS for the setHome control border
var setHomeUI = document.createElement(‘div’);
setHomeUI.style.backgroundColor = ‘white’;
setHomeUI.style.borderStyle = ‘solid’;
setHomeUI.style.borderWidth = ‘2px’;
setHomeUI.style.cursor = ‘pointer’;
setHomeUI.style.textAlign = ‘center’;
setHomeUI.title = ‘Click to set Home to the current center’;
controlDiv.appendChild(setHomeUI);

// Set CSS for the control interior
var setHomeText = document.createElement(‘div’);
setHomeText.style.fontFamily = ‘Arial,sans-serif’;
setHomeText.style.fontSize = ’12px’;
setHomeText.style.paddingLeft = ‘4px’;
setHomeText.style.paddingRight = ‘4px’;
setHomeText.innerHTML = ‘<b>Set Home</b>’;
setHomeUI.appendChild(setHomeText);

// Setup the click event listener for Home:
// simply set the map to the control’s current home property.
google.maps.event.addDomListener(goHomeUI, ‘click’, function() {
var currentHome = control.getHome();
map.setCenter(currentHome);
});

// Setup the click event listener for Set Home:
// Set the control’s home to the current Map center.
google.maps.event.addDomListener(setHomeUI, ‘click’, function() {
var newHome = map.getCenter();
control.setHome(newHome);
});
}

function initialize() {

directionsDisplay = new google.maps.DirectionsRenderer();

var myLatlng = new google.maps.LatLng(30.3363695555555,112.215555555555);

var myOptions = {
zoom: 17,//缩放比例
center: myLatlng,//设置地图中心为myLatlng
disableDefaultUI: true,
mapTypeControl: true,//地图类型控件
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DEFAULT,
position: google.maps.ControlPosition.TOP_RIGHT
},
panControl: true,//平移控件
panControlOptions: {
position: google.maps.ControlPosition.RIGHT_CENTER
},
zoomControl: true,//缩放控件
zoomControlOptions: {
style: google.maps.ZoomControlStyle.DEFAULT,
position: google.maps.ControlPosition.LEFT_CENTER
},
overviewMapControl: true,//鹰眼控件
mapTypeId: google.maps.MapTypeId.ROADMAP,
}////////opitions

map = new google.maps.Map(document.getElementById("map_canvas"), myOptions)/////////////////google map func below this row

// add marker
google.maps.event.addListener(map, ‘click’, function(event) {
addMarker(event.latLng);//addmarker func,navigation func
});

// Create the DIV to hold the control and
// call the HomeControl() constructor passing
// in this DIV.
var homeControlDiv = document.createElement(‘div’);
var homeControl = new HomeControl(homeControlDiv, map, myLatlng);
homeControlDiv.index = 1;
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv);

//real time traffic
var trafficLayer = new google.maps.TrafficLayer();
trafficLayer.setMap(map);

geocoder = new google.maps.Geocoder();

google.maps.event.addListener(map,"center_changed",centerChanged);

lastrelease = new Date();
lastcenter = new Date();

setInterval(function() {
if ((new Date()).getSeconds() – lastcenter.getSeconds() >1){
if (lastrelease.getTime() < lastcenter.getTime()) {
codeLatLng();
}
}
},1000);
}

function centerChanged()
{
lastcenter = new Date();
addressresults = null;
}

function detectCity(city)
{
geocoder.geocode({‘address’:city},resultPrase);
}

function resultPrase(results,status)
{
if (status == "OK"){
if (status != google.maps.GeocoderStatus.ZERO_RESULTS){
map.setZoom(15);
map.setCenter(results[0].geometry.location);
new google.maps.Marker({map:map,position:results[0].geometry.location})
}
else{
alert("can’t find the address");
}
}
else{
alert("google map error with the reason: "+status);
}
}

function codeLatLng()
{
lastrelease = new Date();
geocoder.geocode({‘location’:map.getCenter()},resultsLatLng);
}

function resultsLatLng(results,status)
{
addressresults = results;
if (status == "OK") {
if (status !="ZERO_RESULTS") {
//from google map v3
var detail= addressresults[addressresults.length-3].address_components[0].long_name;
//from google map v2
var fullname = addressresults[0].formatted_address;
if (status == google.maps.GeocoderStatus.OK)
mapNews.getAddress(fullname,detail);
}
else{
mapNews.findFailed();
}
}
else{
alert("google map error with the reason: +",status);
}
}

//animation marker style bounce
function toggleBounce() {
if (marker.getAnimation() != null) {
marker.setAnimation(null);
} else {
marker.setAnimation(google.maps.Animation.BOUNCE);
}
}

// Add a marker to the map and push to the array.
function addMarker(location) {//一
marker = new google.maps.Marker({
position: location,
//DROP MODE
draggable:false,
animation: google.maps.Animation.DROP,
map: map
});
//creat animation marker style:bounce
//google.maps.event.addListener(marker, ‘click’, toggleBounce);
markers.push(marker);

//get the latlng ,creat infowindow and display latlng
geocoder.geocode({ ‘location’: location }, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var lat = results[0].geometry.location.lat();//获得维度
var lng = results[0].geometry.location.lng();//获得经度
var add = results[0].formatted_address//获得地址

var infowindow = new google.maps.InfoWindow({
content: ‘<strong>纬度:</strong>’ + lat + ‘<br/><strong>经度:</strong>’ + lng + ‘<br/><strong>地址:</strong>’ + add
});
//弹出infowindow气泡窗口显示上述信息
google.maps.event.addListener(marker, ‘click’, function(){
infowindow.open(map, marker);
});
}
});

//if markers >=2 ready for direction
if (markers.length >= 2) {
if (markers.length == 2) {
start = markers[0].getPosition();//路径规划起点
end = markers[1].getPosition();//路径规划终点
}
if (markers.length > 2) {
for(var i=0;i<markers.length;i++) {
start = markers[(markers.length)-2].getPosition();
end = markers[(markers.length)-1].getPosition();
}}

//get direcitons route and display
directionsDisplay.setMap(map);
var request = {
origin:start,
destination:end,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(response);
setTimeout(function() {
directionsDisplay.setMap(null) }, 10000);//定时清除线路 10s
}
});
}
}

// Sets the map on all markers in the array.
function setAllMap(map) {
for (var i = 0; i < markers.length; i++) {
markers[i].setMap(map);
}}

// Removes the overlays from the map, but keeps them in the array.
function clearOverlays() {
setAllMap(null);
}

// Shows any overlays currently in the array.
function showOverlays() {
setAllMap(map);
}

// Deletes all markers in the array by removing references to them.
function deleteOverlays() {
clearOverlays();
markers = [];
}

function addphotolay() {
// The photoDiv defines the DIV within the info window for
// displaying the Panoramio photo within its PhotoWidget.
var photoDiv = document.createElement(‘div’);

// The PhotoWidget width and height are expressed as number values,
// not string values.
var photoWidgetOptions = {
width: 320,
height: 240
};

// We construct a PhotoWidget here with a blank (null) request as we
// don’t yet have a photo to populate it.
var photoWidget = new panoramio.PhotoWidget(photoDiv, null,photoWidgetOptions);

var infoWindow = new google.maps.InfoWindow();
var panoramioLayer = new google.maps.panoramio.PanoramioLayer({
suppressInfoWindows: true
});

panoramioLayer.setMap(map);

google.maps.event.addListener(panoramioLayer, ‘click’, function(e) {
var photoRequestOptions = {
ids: [{
‘photoId’: e.featureDetails.photoId,
‘userId’: e.featureDetails.userId
}]
};
photoWidget.setRequest(photoRequestOptions);
photoWidget.setPosition(0);
infoWindow.setPosition(e.latLng);
infoWindow.open(map);
infoWindow.setContent(photoDiv);
});
}
</script>
</head>
<body style="margin:0px; padding:0px;" onLoad="initialize()">
<div id="map_canvas" style="width:100%; height:100%"></div>
<div id="panel">
<input onClick="clearOverlays();" type=button value="Hide Overlays">
<input onClick="showOverlays();" type=button value="Show All Overlays">
<input onClick="deleteOverlays();" type=button value="Delete Overlays">
<input onClick="addphotolay();" type=button value="Show Photo">
<input onclick=’parent.location="javascript:location.reload()"’ type=button value="Reload" >
</div>
<div id="crosshair"></div>
</body>
</html>
[/code]

利用Tasker通话录音

配置文件: 开始通话录音 (2)
	事件: 电话被挂机
进入: 马上 (3)

配置文件: 结束通话录音 (4)
	事件: 电话空闲
进入: 马上 (5)

Task: 马上 (3)
	A1: 录制声音 [ 文件:RECORD/temp.3gp 源:默认 最大尺寸:0 Codec:AMR Narrowband Format:3GPP ]

Task: 马上 (5)
	A1: 停止录制声音 
	A2: 等待 [ MS:0 秒:5 分:0 小时:0 天:0 ] 
	A3: 复制文件 [ 来自:RECORD/temp.3gp 发往:RECORD/%CONUM_%DATE_%TIME.3gp 使用Root:关 ] 
	A4: 删除文件 [ 文件:RECORD/temp.3gp 粉碎级别:0 使用Root:关 ]

本来应该是录音的时候文件名就已经是%CONUM_%DATE_%TIME.3gp这个形式了,但是%CONUM这个参数必须
通话结束之后才能获取到,而且还需要Delay一段时间,所以就有了A2这个步骤。A4步骤是为了防止“未接来电”
导致的不必要录音。

2013.2.25更新

由于%CONUM这个参数只是outgoing时的对方号码,没有incoming时的号码,这样就导致录音文件的命名
有些混乱。通过分别录制outgoing与incoming后,再分别重命名录音文件来达到目的,缺点是暂时没能解决不必
要的未接来电录音。

配置文件: 开始打电话录音 (3)
	事件: 电话被挂机
	State: Call [ Type:Outgoing 数字:* ]
进入: 马上 (4)

配置文件: 结束通话录音 (5)
	事件: 电话空闲
进入: 马上 (6)
退出: 马上 (17)

配置文件: 开始接电话录音 (7)
	事件: 电话振铃 [ 来电者:* ]
	State: Call [ Type:Incoming 数字:* ]
进入: 马上 (20)

Task: 马上 (4)
	A1: 录制声音 [ 文件:RECORD/temp_out.mp4 源:默认 最大尺寸:0 Codec:AMR Narrowband Format:MP4 ] 

Task: 马上 (6)
	A1: 停止录制声音 
	A2: 等待 [ MS:0 秒:10 分:0 小时:0 天:0 ] 
	A3: 复制文件 [ 来自:RECORD/temp_out.mp4 发往:RECORD/%CONUM_%DATE_%TIME_%CODUR_%OUT.mp4 使用Root:关 ] 
	A4: 删除文件 [ 文件:RECORD/temp_out.mp4 粉碎级别:0 使用Root:关 ] 

Task: 马上 (17)
	A1: 复制文件 [ 来自:RECORD/temp_in.mp4 发往:RECORD/%CNUM_%DATE_%TIME_%CODUR_%IN.mp4 使用Root:关 ] 
	A2: 删除文件 [ 文件:RECORD/temp_in.mp4 粉碎级别:0 使用Root:关 ] 

Task: 马上 (20)
	A1: 录制声音 [ 文件:RECORD/temp_in.mp4 源:默认 最大尺寸:0 Codec:AMR Narrowband Format:MP4 ]