中级实训Android学习记录——实训报告

软工实训报告

17326032 林文锋 18级软件工程

实验环境

实验完成时间:2020/12/23 18:00

Windows 10系统详细配置如下:

img

笔记本详细配置如下:

img

Android Studio版本:

image-20201223183633302

其他信息如工程gradle信息都在工程文件中可以找到。

实验摘要

通过对少量百度地图API的调用以及对传感器的使用,达到摇一摇定位的目的

实验目的

  • 学会使用加速度传感器
  • 学会使用地磁传感器
  • 学会获取经纬度
  • 接入百度地图 API
  • 掌握少量的百度地图 API 接口

实验内容

  • 初始界面仍为摇一摇
  • 跳转后的界面为百度地图
  • 地图定位在目前的经纬度

增加内容:

  • 摇一摇跳转至附近的娱乐场所
  • 记录摇一摇得到的娱乐场所列表
  • 点击摇一摇得到的娱乐场所可以调用百度地图API的步行导航功能

实验步骤

一、 获取百度地图API使用权限,申请百度地图AK

获取开发密钥(AK)

image-20201213224019543

  • 通过android studio的AndroidManifest.xml文件查看PackageName

    image-20201213224429114

  • 通过命令行查看SHA1(默认没有密码,口令直接回车即可)

    image-20201213224542526

    上面找到的其实是debug(开发板SHA1),发布版SHA1其实是另一种方法:

    1. 使用任意android studio工程生成apk,步骤:Build->Build Bundle(s)/APK(s)->Build APK(s)

      image-20201223184820207

    2. 在工程目录/app/build/outputs/apk/debug中更改apk的后缀为zip文件并解压

      image-20201223184858249

    3. 进入解压后的META-INF目录

      image-20201223184942741

    4. 在META-INF目录下打开cmd,输入命令 :keytool -printcert -file CERT.RSA 这里将会显示出MD5和SHA1签名

      image-20201223185132764

    5. 使用上面的SHA1重新申请key,PackageName:AndroidMainifest.xml中的包名。

  • 输入得到的PackageName和SHA1,并点击提交

    image-20201213224644442

  • 就得到了访问应用的AK

    image-20201213224723188

二、 使用百度地图API 显示地图、定位

  1. 应用申请权限

    <!-- 以下权限开启地图服务 -->
    <!-- 访问网络,进行地图相关业务数据请求,包括地图数据,路线规划,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"/>
    
  2. 初始化SDK组件

    //在使用SDK各组件之前初始化context信息,传入ApplicationContext
    SDKInitializer.initialize(this);
    //自4.3.0起,百度地图SDK所有接口均支持百度坐标和国测局坐标,用此方法设置您使用的坐标类型.
    //包括BD09LL和GCJ02两种坐标,默认是BD09LL坐标。
    SDKInitializer.setCoordType(CoordType.BD09LL);
    

    注意最好在任何其他组件都没有使用百度地图API之前就初始化

  3. 使用百度地图API

    1. 初始化

              // ----------------------------------- 初始化百度地图 --------------------------------- //
      
              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;
      
    2. 声明地址更改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("\n当前地址:" + location.getAddrStr());
                  stringBuilder.append("\n经度:" + location.getLatitude());
                  stringBuilder.append("\n纬度:"+ location.getLongitude());
      
      //            stringBuilder.append("\n状态码:"+ location.getLocType());
      //            stringBuilder.append("\n国家:" + location.getCountry());
      //            stringBuilder.append("\n城市:"+ location.getCity());
      //            stringBuilder.append("\n区:" + location.getDistrict());
      //            stringBuilder.append("\n街道:" + location.getStreet());
      
                  mTextView.setText(stringBuilder.toString());
              }
          }
      }
      

      MainActivity声明为OnGetPoiSearchResultListener来支持Poi检索功能,声明MyLocationListener来支持定位功能

    3. 生命周期管理

          @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

  1. 初始化摇一摇组件

    // ---------------------------------初始化摇一摇--------------------------------------- //
    
            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函数,在这个函数中进行调整即可。

  2. 调用传感器来调用百度地图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进行的封装。

  3. 生命周期管理

    传感器是一个非常耗电的设备,并且极易影响到其他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初始界面
image-20201223191503921 image-20201223191523333
摇晃一次 调用百度地图进行导航
image-20201223191608099 image-20201223191637337
摇晃三次
image-20201223191659419
  • 视频演示

注意事项

  • 得到的APP不能使用Android Studio上的虚拟手机端来跑,因为虚拟的手机是没有GPS的,不能够正常使用定位的功能

  • 得到的源码不一定能够在其他电脑上跑的通,因为百度地图API限制了SHA1和包名,所以如果想要正常的使用本APP,可以使用项目工程目录中./app/build/outputs/apk/debug/app-debug.apk这个文件来安装并运行

    或者通过更改AndroidManifest.xml中的百度地图APIkey,用自己的百度地图APIkey来调试本源码

posted @ 2020-12-23 19:57  沐锋丶  阅读(174)  评论(0编辑  收藏  举报