软工实训报告
17326032 林文锋 18级软件工程
实验环境
实验完成时间:2020/12/23 18:00
Windows 10系统详细配置如下:
笔记本详细配置如下:
Android Studio版本:
其他信息如工程gradle
信息都在工程文件中可以找到。
实验摘要
通过对少量百度地图API的调用以及对传感器的使用,达到摇一摇定位的目的
实验目的
- 学会使用加速度传感器
- 学会使用地磁传感器
- 学会获取经纬度
- 接入百度地图 API
- 掌握少量的百度地图 API 接口
实验内容
- 初始界面仍为摇一摇
- 跳转后的界面为百度地图
- 地图定位在目前的经纬度
增加内容:
- 摇一摇跳转至附近的娱乐场所
- 记录摇一摇得到的娱乐场所列表
- 点击摇一摇得到的娱乐场所可以调用百度地图API的步行导航功能
实验步骤
一、 获取百度地图API使用权限,申请百度地图AK
获取开发密钥(AK)
百度教程地址:http://lbsyun.baidu.com/index.php?title=androidsdk/guide/create-project/ak
登录百度账号
登录后将进入API控制台,如下图:
点击“创建应用”开始申请开发密钥,如下图:
填写应用名称,注意应用类型选择“Android SDK”、正确填写SHA1 和 程序包名(SHA1和包名的获取方法见下文)。如图:
通过android studio的AndroidManifest.xml文件查看PackageName
通过命令行查看SHA1(默认没有密码,口令直接回车即可)
上面找到的其实是debug(开发板SHA1),发布版SHA1其实是另一种方法:
使用任意android studio工程生成apk,步骤:Build->Build Bundle(s)/APK(s)->Build APK(s)
在工程目录/app/build/outputs/apk/debug中更改apk的后缀为zip文件并解压
进入解压后的META-INF目录
在META-INF目录下打开cmd,输入命令 :keytool -printcert -file CERT.RSA 这里将会显示出MD5和SHA1签名
使用上面的SHA1重新申请key,PackageName:AndroidMainifest.xml中的包名。
输入得到的PackageName和SHA1,并点击提交
就得到了访问应用的AK
二、 使用百度地图API 显示地图、定位
应用申请权限
<!-- 以下权限开启地图服务 --> <!-- 访问网络,进行地图相关业务数据请求,包括地图数据,路线规划,POI检索等 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- 获取网络状态,根据网络状态切换进行数据请求网络转换 --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 读取外置存储。如果开发者使用了so动态加载功能并且把so文件放在了外置存储区域,则需要申请该权限,否则不需要 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- 写外置存储。如果开发者使用了离线地图,并且数据写在外置存储区域,则需要申请该权限 --> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- 以下权限用于开启定位服务 --> <!-- 这个权限用于进行网络定位 --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <!-- 这个权限用于访问GPS定位 --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- 以下权限用于开启定位服务 --> <service android:name="com.baidu.location.f" android:enabled="true" android:process=":remote"/>
初始化SDK组件
//在使用SDK各组件之前初始化context信息,传入ApplicationContext SDKInitializer.initialize(this); //自4.3.0起,百度地图SDK所有接口均支持百度坐标和国测局坐标,用此方法设置您使用的坐标类型. //包括BD09LL和GCJ02两种坐标,默认是BD09LL坐标。 SDKInitializer.setCoordType(CoordType.BD09LL);
注意最好在任何其他组件都没有使用百度地图API之前就初始化
使用百度地图API
初始化
// ----------------------------------- 初始化百度地图 --------------------------------- // mMapView = findViewById(R.id.bmapView); mTextView = findViewById(R.id.tv); mPoiSearch = PoiSearch.newInstance(); mPoiSearch.setOnGetPoiSearchResultListener(this); // 更改地图类型 mBaiduMap = mMapView.getMap(); mBaiduMap.setMapType(BaiduMap.MAP_TYPE_SATELLITE); // 设置定位服务开始 mBaiduMap.setMyLocationEnabled(true); //定位初始化 mLocationClient = new LocationClient(getApplicationContext()); //通过LocationClientOption设置LocationClient相关参数 LocationClientOption option = new LocationClientOption(); option.setLocationMode(LocationClientOption.LocationMode.Device_Sensors); // 定位模式是仅限设备模式,也就是仅允许GPS来定位。 option.setOpenGps(true); // 打开gps option.setCoorType("bd09ll"); // 设置坐标类型 option.setScanSpan(1000); //设置打开自动回调位置模式,该开关打开后,期间只要定位SDK检测到位置变化就会主动回调给开发者 option.setIsNeedAddress(true); //可选,是否需要地址信息,默认为不需要,即参数为false //如果开发者需要获得当前点的地址信息,此处必须为true option.setNeedNewVersionRgc(true); //可选,设置是否需要最新版本的地址信息。默认需要,即参数为true // 自定义定位指针 // MyLocationConfiguration.LocationMode mCurrentMode = MyLocationConfiguration.LocationMode.COMPASS; // BitmapDescriptor mCurrentMarker = BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher_background); // MyLocationConfiguration myLocationConfiguration = new MyLocationConfiguration(mCurrentMode, true, // mCurrentMarker, 0xAAFFFF88, 0xAA00FF00); // mBaiduMap.setMyLocationConfiguration(myLocationConfiguration); //设置locationClientOption mLocationClient.setLocOption(option); //注册LocationListener监听器 MyLocationListener myLocationListener = new MyLocationListener(); mLocationClient.registerLocationListener(myLocationListener); //开启地图定位图层 mLocationClient.start(); // 搜索时不重复,使用HashSet来支持 mHashLocStr = new HashSet<>(); // 一开始搜索的半径和接收的结果数 mRadius = 1000; mPageCapacity = 10;
声明地址更改Listener并在Listener中更新位置信息
public class MainActivity extends AppCompatActivity implements OnGetPoiSearchResultListener, SensorEventListener{ ... /** * LocationListener 不断接收定位的回调并改变当前的位置信息 */ public class MyLocationListener extends BDAbstractLocationListener { private boolean isFirstLocate = true; @Override public void onReceiveLocation(BDLocation location) { //mapView 销毁后不在处理新接收的位置 if (location == null || mMapView == null){ return; } mBDLocation = location; addressAdapter.setStartLocation(mBDLocation); // 如果是第一次定位 LatLng ll = new LatLng(location.getLatitude(), location.getLongitude()); if (isFirstLocate) { isFirstLocate = false; //给地图设置状态 mBaiduMap.animateMapStatus(MapStatusUpdateFactory.newLatLng(ll)); } // 编辑LocData并改变当前地图的信息 MyLocationData locData = new MyLocationData.Builder() .accuracy(location.getRadius()) // 此处设置开发者获取到的方向信息,顺时针0-360 .direction(location.getDirection()).latitude(location.getLatitude()) .longitude(location.getLongitude()).build(); mBaiduMap.setMyLocationData(locData); Log.d("0", "onReceiveLocation: 定位到 " + location.getAddrStr()); mCity = location.getCity(); // 显示当前信息 StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(" 当前地址:" + location.getAddrStr()); stringBuilder.append(" 经度:" + location.getLatitude()); stringBuilder.append(" 纬度:"+ location.getLongitude()); // stringBuilder.append(" 状态码:"+ location.getLocType()); // stringBuilder.append(" 国家:" + location.getCountry()); // stringBuilder.append(" 城市:"+ location.getCity()); // stringBuilder.append(" 区:" + location.getDistrict()); // stringBuilder.append(" 街道:" + location.getStreet()); mTextView.setText(stringBuilder.toString()); } } }
将
MainActivity
声明为OnGetPoiSearchResultListener
来支持Poi检索功能,声明MyLocationListener
来支持定位功能生命周期管理
@Override protected void onResume() { mMapView.onResume(); super.onResume(); } @Override protected void onPause() { mMapView.onPause(); // 务必要在pause中注销 mSensorManager // 否则会造成界面退出后摇一摇依旧生效的bug if (mSensorManager != null) { mSensorManager.unregisterListener(this); } super.onPause(); } @Override protected void onDestroy() { mLocationClient.stop(); mBaiduMap.setMyLocationEnabled(false); mMapView.onDestroy(); mMapView = null; super.onDestroy(); }
在进行百度地图API的使用,需要特别注意生命周期的管理,否则可能造成定位不准,后台耗电量巨大的隐患。
三、 整合摇一摇来调用百度地图API
初始化摇一摇组件
// ---------------------------------初始化摇一摇--------------------------------------- // mHandler = new MyHandler(this); //初始化SoundPool mSoundPool = new SoundPool(1, AudioManager.STREAM_SYSTEM, 5); mWeiChatAudio = mSoundPool.load(this, R.raw.weichat_audio, 1); //获取Vibrator震动服务 mVibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); // 摇一摇的两个分离界面 mTopLayout = findViewById(R.id.top_layout); mBottomLayout = findViewById(R.id.bottom_layout); //获取 SensorManager 负责管理传感器 mSensorManager = ((SensorManager) getSystemService(SENSOR_SERVICE)); if (mSensorManager != null) { //获取加速度传感器 mAccelerometerSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (mAccelerometerSensor != null) { mSensorManager.registerListener(this, mAccelerometerSensor, SensorManager.SENSOR_DELAY_UI); } }
摇一摇调用
sensorManager
来监听手机的位置变化,如果三维的位置变化达到一定程度,我们就认为手机被摇晃了,此时监听位置变化的函数是onSensorChanged
函数,在这个函数中进行调整即可。调用传感器来调用百度地图API和动画
@Override public void onSensorChanged(SensorEvent event) { int type = event.sensor.getType(); if (type == Sensor.TYPE_ACCELEROMETER) { //获取三个方向值 float[] values = event.values; float x = values[0]; float y = values[1]; float z = values[2]; if ((Math.abs(x) > 17 || Math.abs(y) > 17 || Math .abs(z) > 17) && !isShake) { isShake = true; Thread thread = new Thread() { @Override public void run() { super.run(); try { Log.d(TAG, "onSensorChanged: 摇动"); searchPoiNearBy(); //开始震动 发出提示音 展示动画效果 mHandler.obtainMessage(START_SHAKE).sendToTarget(); Thread.sleep(500); //再来一次震动提示 mHandler.obtainMessage(AGAIN_SHAKE).sendToTarget(); Thread.sleep(500); mHandler.obtainMessage(END_SHAKE).sendToTarget(); } catch (InterruptedException e) { e.printStackTrace(); } } }; thread.start(); } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } /** * handler 接收摇一摇产生的信息并作出对应的操作 * START_SHAKE 开始摇晃 * AGAIN_SHAKE 没结束摇晃之前再次摇晃 * END_SHAKE 结束摇晃 */ private static class MyHandler extends Handler { private WeakReference<MainActivity> mReference; private MainActivity mActivity; public MyHandler(MainActivity activity) { mReference = new WeakReference<MainActivity>(activity); if (mReference != null) { mActivity = mReference.get(); } } @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case START_SHAKE: //This method requires the caller to hold the permission VIBRATE. mActivity.mVibrator.vibrate(300); //发出提示音 mActivity.mSoundPool.play(mActivity.mWeiChatAudio, 1, 1, 0, 0, 1); mActivity.startAnimation(false);//参数含义: (不是回来) 也就是说两张图片分散开的动画 break; case AGAIN_SHAKE: mActivity.mVibrator.vibrate(300); break; case END_SHAKE: //整体效果结束, 将震动设置为false mActivity.isShake = false; // 展示上下两种图片回来的效果 mActivity.startAnimation(true); break; } } }
如上代码,我们在
onSensorChanged
中调用了调用百度地图API的函数searchPoiNearBy
来寻找附近的娱乐场所:// 搜索附近的娱乐设施 private void searchPoiNearBy() { String cityStr = mCity; // 获取检索关键字 String keyWordStr = "娱乐"; if (TextUtils.isEmpty(cityStr) || TextUtils.isEmpty(keyWordStr)) { return; } LatLng ll = new LatLng(mBDLocation.getLatitude(), mBDLocation.getLongitude()); // 搜索附近的娱乐场所 mPoiSearch.searchNearby((new PoiNearbySearchOption()) .location(ll) .keyword(keyWordStr) .pageCapacity(mPageCapacity) .pageNum(0) .radius(mRadius)); }
该函数通过API
searchNearby
来查找附近的娱乐设施,最后通过本身的查找结果函数onGetPoiResult
来定位到该位置并加入到recycler view
中去:@Override // 在poiSearch完成之后对得到的结果进行处理并展示到recycler View中 public void onGetPoiResult(PoiResult poiResult) { List<PoiInfo> poiInfos = poiResult.getAllPoi(); if (poiInfos.size() <= 0 || poiResult.error == SearchResult.ERRORNO.RESULT_NOT_FOUND) { Toast.makeText(MainActivity.this, "未找到结果", Toast.LENGTH_LONG).show(); // 没找到,说明附近娱乐场所很少 mRadius *= 2; mPageCapacity *= 2; return; } // 将地图平移到 latLng 位置 int index = (int)(Math.random() * poiInfos.size()) % poiInfos.size(); PoiInfo poiInfo = poiInfos.get(index); int isSelected = 0; for (int i = 0; i < poiInfos.size(); i ++) { index = (int)(Math.random() * poiInfos.size()) % poiInfos.size(); poiInfo = poiInfos.get(index); if (!mHashLocStr.contains(poiInfo.getName())) { isSelected = 1; break; } } if (isSelected == 0) { // 找到了但是差不多都输出过,说明已经摇了很多次 mRadius *= 2; mPageCapacity *= 2; Toast.makeText(this, "你是真的挑三拣四,建议卸载本APP", Toast.LENGTH_SHORT).show(); return; } // 加入HashSet以避免重复 mHashLocStr.add(poiInfo.getName()); // 加入AddressAdapter以显示摇出的地址 addressAdapter.addItem(poiInfo); // 定位到摇到的地址的位置 LatLng latLng = poiInfos.get(index).getLocation(); MapStatusUpdate mapStatusUpdate = MapStatusUpdateFactory.newLatLng(latLng); mBaiduMap.setMapStatus(mapStatusUpdate); // 添加指示标志 MarkerOptions markerOptions = new MarkerOptions() .position(poiInfo.getLocation()) .icon(mBitmapDescWaterDrop); InfoWindow infoWindow = getPoiInfoWindow(poiInfo); markerOptions.infoWindow(infoWindow); Marker marker = (Marker) mBaiduMap.addOverlay(markerOptions); } // 在摇到的地址上方显示当前地址的名字 private InfoWindow getPoiInfoWindow(PoiInfo poiInfo) { TextView textView = new TextView(this); textView.setText(poiInfo.getName()); textView.setPadding(10, 5, 10, 5); textView.setBackground(this.getResources().getDrawable(R.drawable.bg_info)); InfoWindow infoWindow = new InfoWindow(textView, poiInfo.getLocation(), -150); return infoWindow; }
至于
RecyclerView
就是简单的线性view对ArrayList
进行的封装。生命周期管理
传感器是一个非常耗电的设备,并且极易影响到其他APP的使用,所以它的生命周期管理也十分重要:
@Override protected void onStart() { super.onStart(); //获取 SensorManager 负责管理传感器 mSensorManager = ((SensorManager) getSystemService(SENSOR_SERVICE)); if (mSensorManager != null) { //获取加速度传感器 mAccelerometerSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (mAccelerometerSensor != null) { mSensorManager.registerListener(this, mAccelerometerSensor, SensorManager.SENSOR_DELAY_UI); } } } @Override protected void onResume() { mMapView.onResume(); super.onResume(); } @Override protected void onPause() { mMapView.onPause(); // 务必要在pause中注销 mSensorManager // 否则会造成界面退出后摇一摇依旧生效的bug if (mSensorManager != null) { mSensorManager.unregisterListener(this); } super.onPause(); } @Override protected void onDestroy() { mLocationClient.stop(); mBaiduMap.setMyLocationEnabled(false); mMapView.onDestroy(); mMapView = null; super.onDestroy(); }
首先,和百度地图API一样的是在APP开始时初始化,在APP关闭时摧毁,不同的是,在应用未被关闭而是切换(Pause)期间,传感器必须被注销,否则在其他APP使用时仍然有摇一摇的功能,在重新回到该APP(Start)时,必须检查传感器的状态以保证摇一摇的功能可用。
实验结果
APP图标 | APP初始界面 |
---|---|
摇晃一次 | 调用百度地图进行导航 |
摇晃三次 | |
- 视频演示
注意事项
-
得到的APP不能使用Android Studio上的虚拟手机端来跑,因为虚拟的手机是没有GPS的,不能够正常使用定位的功能
-
得到的源码不一定能够在其他电脑上跑的通,因为百度地图API限制了SHA1和包名,所以如果想要正常的使用本APP,可以使用项目工程目录中
./app/build/outputs/apk/debug/app-debug.apk
这个文件来安装并运行或者通过更改
AndroidManifest.xml
中的百度地图APIkey
,用自己的百度地图APIkey
来调试本源码