• 《第一行代码:Android篇》学习笔记(十一)


    本文和接下来的几篇文章为阅读郭霖先生所著《第一行代码:Android(篇第2版)》的学习笔记,按照书中的内容顺序进行记录,书中的Demo本人全部都做过了。
    每一章节本人都做了详细的记录,以下是我学习记录(包含大量书中内容的整理和自己在学习中遇到的各种bug及解决方案),方便以后阅读和查阅。最后,非常感激郭霖先生提供这么好的书籍。

    第11章 Android特色开发——基于位置的服务

    本章中,将要学习一些全新的Android技术,这些技术有别于传统的PC或Web领域的应用技术,是只有在移动设备上才能实现的。

    基于位置的服务(Location Based Service)。由于移动设备相比于电脑可以随身携带,我们通过地理定位的技术就可以随时得知自己所在的位置,从而围绕这一点开发出很多有意思的应用。

    11.1 基于位置的服务简介

    基于位置的服务简称LBS,其实它本身并不是什么时髦的技术,主要的工作原理就是利用无线电通讯网络或GPS等定位方式来确定出移动设备所在的位置,而这种定位技术早在很多年前就已经出现了。

    在过去移动设备的功能极其有限,即使定位到了设备所在的位置,也就仅仅只是定位到了而已,我们并不能在位置的基础上进行一些其他的操作。而现在就大大不同了,有了Android系统作为载体,可以利用定位出的位置进行许多丰富多彩的操作。比如说天气预报程序可以根据用户所在的位置自动选择城市,发微博的时候我们可以向朋友们晒一下自己在哪里,不认识路的时候随时打开地图就可以查询路线,等等。

    首先要清楚,基于位置的服务所围绕的核心就是要先确定出用户所在的位置。

    通常有两种技术方式可以实现,一种是通过GPS定位,一种是通过网络定位:

    • GPS定位的工作原理是基于手机内置的GPS硬件直接和卫星交互来获取当前的经纬度信息,这种定位方式精确度非常高,但缺点是只能在室外使用,室内基本无法接收到卫星的信号。
    • 网络定位的工作原理是根据手机当前网络附近的三个基站进行测速,以此计算出手机和每个基站之间的距离,再通过三角定位确定出一个大概的位置,这种定位方式精确度一般,但优点是在室内室外都可以使用。

    Android对这两种定位方式都提供了相应的API支持,但是由于一些特殊原因,Google的网络服务在中国不可访问,从而导致网络定位方式的API失效。而GPS定位虽然不需要网络,但是必须要在室外才可以使用,因此你在室内开发的时候很有可能会遇到不管使用哪种定位方式都无法成功定位的情况。

    基于以上原因,本书中不再讲解Android原生定位API的用法了,而是使用一些国内第三方公司的SDK。目前国内在这一领域做得比较好的一个是百度,一个是高德,本章我们就来学习一下百度在LBS方面提供的丰富多彩的功能。

    11.2 申请成为开发者

    下面内容与书中记录的不一样了,书中的方式我注册不了...现在记录自己是如何申请成为百度开发者的流程:

    要想在自己的应用程序里使用百度的LBS功能,你得拥有一个百度账号才能进行申请。然后,登录网住:http://lbsyun.baidu.com/apiconsole/key

    image

    填入真实信息,进行邮箱验证等,注册的账号:

    image

    结果在进行人脸识别认证的时候,我的始终无法识别。最后,申请了高德开发者,步骤如下:

    1.网址:https://console.amap.com/dev/index

    2.进行认证,支付宝、邮箱就可以了:

    image

    image

    image

    image

    所以,以后Demo 书上是百度,我用的是高德了。

    首先,创建新应用:

    image

    其次,新应用旁边有一个添加按钮,点击添加:

    image

    这个发布版安全码SHA1,开发模式(debug)和发布模式(release)下的 SHA1 值是不同的,发布 apk时 需要根据发布apk对应的keystore重新配置Key,获取发布模式下的SHA1的方法(可点击如何获取,学习获取步骤),通过Android Studio获取测试版SHA1:

    1、打开Android Studio的Terminal工具。

    2、输入命令:keytool -v -list -keystore keystore文件路径。

    3、输入Keystore密码。

    image

    image

    其中,91:16:04:30:C0:8B:6E:53:92:47:57:E6:FB:10:EF:08:1B:73:E6:3E就是所需的SHA1指纹了,当然你的Android Studio中显示的指纹和我的肯定是不一样的。

    另外需要注意,目前我们使用的是debug.keystore文件所生成的指纹,这是Android自动生成的一个用于测试的签名文件。而当你的应用程序发布时还需要创建一个正式的签名文件,如果要得到它的指纹,可以在cmd中输入如下命令:

    keytool -list -v -keystore <签名文件路径>
    

    然后输入正确的密码就可以了。创建签名文件的方法我们将在第15章中学习。

    现在得到的这个SHA1指纹实际上是一个开发版的SHA1指纹,不过因为暂时我们还没有一个发布版的SHA1指纹,因此这两个值都填成一样的就可以了。

    上面这句话是个大坑!如果SHA1的发布版/测试版值都填入一样的,在最后很有可能出现:坐标一直为4.9E-324(这是导致定位坐标这样的原因之一,我之后在测试中,也改了不少。)

    image

    上面的代码,与原书不一致,都是经过查询网上资料改动过的,参考博客连接:

    1. 包名不一致(我的不是这个原因)https://www.cnblogs.com/hoyong/articles/11918642.html
    2. 室内用不了GPS....(我用的WIFI/移动4G)
    3. 网络不好,联网失败(我的网非常好,所以不是这个原因)
    4. Android的模拟器是不能定位的,所以要使用真机(我的不是这个原因,但是这个确实是真的,在我排查全部bug后,手机上测试OK,到Android的模拟器上运行,为上图:坐标一直为4.9E-324,不显示定位方式)
    5. 权限错误/so库加载错误(我的不是这个原因)https://www.jianshu.com/p/b394de3d989c

    然而,在是上面3中的博客里,最后一条就是SHA1指纹发布版和测试版不能一样!!!我测试了,对我来说是正确的解决办法。

    下面,记录一下如何申请并找到SHA1指纹发布版步骤:

    1. 找到apk签名路径,可通过AS中build-generate signed bundle/apk -apk 进入如图界面:

    image

    image

    1. 检查一下该目录(E:\androidKey)中是否存在zhouzhou.jks,没有的话,查看文件是否被隐藏了:

    image

    其中key store path 为签名路径。

    1. 操作和获取开发版SHA1 基本一致,只需将debug.keystore换成签名路径即可,如图:

    image

    1. 最后,将在开发平台更换发布版SHA1重新提交。注:项目最好clean一下。

    下面开始,继续记录书上的内容,

    最后还剩下一个包名选项,虽然目前我们的应用程序还不存在,但可以先将包名预定下来,比如就叫com.zhouzhou. lbstest,这样所有的内容就都填写完整了,如图为百度地图开放平台创建的应用信息:

    (注:第二天百度的人脸识别认证我整成功了,为了和书上保持一致,我接下来选择用百度地图开放平台做Demo。事实上,高德平台步骤也差不多,以后有机会会尝试的)

    image

    接下来点击提交,应用就应该创建成功了,如图:

    image

    其中,i6VD2fHKM3msMfZtIOXAhFSzDiYGFIwL(访问应用(AK))就是申请到的API Key,有了它就可以进行后续的LBS开发工作了,那么我们马上开始吧。

    11.3 使用百度定位

    新建一个LBSTest项目,包名应该就会自动被命名为com.zhouzhou.lbstest。另外需要注意,本章中所写的代码建议都在手机上运行,虽然模拟器中也提供了模拟地理位置的功能,但在手机上可以得到真实的位置数据,你的感受会更加深刻。

    11.3.1 准备LBS SDK

    在开始编码之前,我们还需要先将百度LBS开放平台的SDK准备好,目前百度地图Android地图SDK境内服务对非商业目的使用的开发者不收取任何费用,开发者可自行下载:http://lbsyun.baidu.com/index.php?title=androidsdk/sdkandev-download放心使用。

    image

    本章中,我们会用到基础地图和定位功能这两个SDK,将它们勾选上,然后点击“开发包”下载按钮即可,如图:

    image

    下载完成后对该压缩包解压,其中会有一个libs目录,这里面的内容就是我们所需要的一切了,如图:

    image

    libs目录下的内容又分为两部分,BaiduLBS_Android.jar这个文件是Java层要使用到的,其他子目录下的so文件是Native层要用到的。so文件是用C/C++语言进行编写,然后再用NDK编译出来的。当然这里我们并不需要去编写C/C++的代码,因为百度都已经做好了封装,但是我们需要将libs目录下的每一个文件都放置到正确的位置。

    首先,观察一下当前的项目结构,会发现app模块下面有一个libs目录,这里就是用来存放所有的Jar包的,我们将BaiduLBS_Android.jar复制到这里,如图:

    image

    接下来展开src/main目录,右击该目录→New→Directory,再创建一个名为jniLibs的目录,这里就是专门用来存放so文件的,然后把压缩包里的其他所有目录直接复制到这里,如图:

    image

    另外,虽然所有新创建的项目中,app/build.gradle文件都会默认配置以下这段声明(不整这个也行,上面BaiduLBS_Android.jar复制之后,直接Add As Library...即可,dependencies中会被自动添加一行implementation files('libs\\BaiduLBS_Android.jar'),注意,Add As Library...和下面的implementation fileTree(include: ['*.jar'], dir: 'libs')方式要二选一,不要都写上,会报冲突!!!):

    dependencies {
        implementation fileTree(include: ['*.jar'], dir: 'libs')
    }
    

    这表示会将libs目录下所有以.jar结尾的文件添加到当前项目的引用中。(但是由于我们是直接将Jar包复制到libs目录下的,并没有修改gradle文件,因此不会弹出我们平时熟悉的Sync Now提示。这个时候必须手动点击一下AndroidStudio顶部工具栏中的Sync按钮,不然项目将无法引用到Jar包中提供的任何接口。)

    点击Sync按钮之后,libs目录下的jar文件就会多出一个向右的箭头,这就表示项目已经能引用到这些Jar包了,如图:

    image

    这样我们就把LBS的SDK都准备好了,接下来开始编码吧。

    11.3.2 确定自己位置的经纬度

    首先修改activity_main.xml中的代码,如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <TextView
            android:id="@+id/position_text_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    

    然后,修改AndroidManifest.xml文件中的代码,如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.zhouzhou.lbstest">
        <!-- 这个权限用于进行网络定位-->
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
        <!-- 这个权限用于访问GPS定位-->
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
        <!-- 用于访问wifi网络信息,wifi信息会用于进行网络定位-->
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
        <!-- 获取运营商信息,用于支持提供运营商信息相关的接口-->
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
        <!-- 这个权限用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
        <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <!-- 写入扩展存储,向扩展卡写入数据,用于写入离线定位数据-->
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
        <!-- 访问网络,网络定位需要上网-->
        <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
            tools:ignore="ProtectedPermissions" />
        <uses-permission android:name="android.permission.WAKE_LOCK" />
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.LBSTest">
    
            <!-- meta-data中android:value=在11.2节申请到的API Key -->
            <meta-data
                android:name="com.baidu.lbsapi.API_KEY"
                android:value="LGvkANF1s4zZK1MkK0XL6fYiL2TkV4L4" />
            <activity
                android:name=".MainActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <service
                android:name="com.baidu.location.f"
                android:enabled="true"
                android:process=":remote">
            </service>
        </application>
    
    </manifest>
    

    AndroidManifest.xml文件改动比较多。可以看到,这里首先添加了很多行权限声明,每一个权限都是百度LBS SDK内部要用到的。然后,在<application>标签的内部添加了一个<meta-data>标签,这个标签的android:name部分是固定的,必须填com.baidu. lbsapi.API_KEY,android:value部分则应该填入我们在11.2节申请到的API Key。最后,还需要再注册一个LBS SDK中的服务,不用对这个服务的名字感到疑惑,因为百度LBS SDK中的代码都是混淆过的。

    接下来修改MainActivity中的代码,如下所示:

    package com.zhouzhou.lbstest;
    
    import androidx.annotation.NonNull;
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.core.app.ActivityCompat;
    import androidx.core.content.ContextCompat;
    
    import android.Manifest;
    import android.content.pm.PackageManager;
    import android.os.Bundle;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.baidu.location.BDLocation;
    import com.baidu.location.BDLocationListener;
    import com.baidu.location.LocationClient;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MainActivity extends AppCompatActivity {
        public LocationClient mLocationClient;
        private TextView positionText;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //没有它,会报错:Please recheck the setAgreePrivacy interface
            mLocationClient.setAgreePrivacy(true);
            try {
                //首先创建了一个LocationClient的实例,LocationClient的构建函数接收一个Context参数,
                //这里调用getApplicationContext()方法来获取一个全局的Context参数并传入。
                mLocationClient = new LocationClient(getApplicationContext());
            } catch (Exception e) {
                    e.printStackTrace();
                }
                //然后调用LocationClient的registerLocationListener()方法来注册一个定位监听器,当获取到位置信息的时候,就会回调这个定位监听器。
                mLocationClient.registerLocationListener(new MyLocationListener());
                setContentView(R.layout.activity_main);
                positionText = (TextView) findViewById(R.id.position_text_view);
                //怎样才能在运行时一次性申请3个权限呢?
                //创建一个空的List集合,然后依次判断这3个权限有没有被授权,
                //如果没被授权就添加到List集合中,最后将List转换成数组,
                //再调用ActivityCompat.requestPermissions()方法一次性申请。
                List<String> permissionList = new ArrayList<>();
                if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                    permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
                }
                if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
                    permissionList.add(Manifest.permission.READ_PHONE_STATE);
                }
                if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
                }
                if (! permissionList.isEmpty()) {
                    String [] permissions = permissionList.toArray(new String[permissionList.size()]);
                    ActivityCompat.requestPermissions(MainActivity.this,permissions,1);
                } else {
                    requestLocation();
                }
        }
        private void requestLocation() {
            mLocationClient.start();
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            switch (requestCode) {
                case 1:
                    if (grantResults.length > 0) {
                        for (int result : grantResults) {
                            if (result != PackageManager.PERMISSION_GRANTED) {
                                Toast.makeText(this,"必须同意所有权限才能使用本程序",Toast.LENGTH_SHORT).show();
                                finish();
                                return;
                            }
                        }
                        requestLocation();
                    } else {
                        Toast.makeText(this,"发生未知错误",Toast.LENGTH_SHORT).show();
                        finish();
                    }
                    break;
                default:
            }
        }
    
        public class MyLocationListener implements BDLocationListener {
            /**
             * MyLocationListener的onReceiveLocation()方法中:
             * 通过BDLocation的getLatitude()方法获取当前位置的纬度,
             * 通过getLongitude()方法获取当前位置的经度,
             * 通过getLocType()方法获取当前的定位方式,最终将结果组装成一个字符串,显示到TextView上面。
             */
            @Override
            public void onReceiveLocation(BDLocation bdLocation) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        StringBuilder currentPosition = new StringBuilder();
                        currentPosition.append("纬度:").append(bdLocation.getLatitude()).append("\n");
                        currentPosition.append("经度:").append(bdLocation.getLongitude()).append("\n");
                        currentPosition.append("定位方式:");
                        if (bdLocation.getLocType() == BDLocation.TypeGpsLocation) {
                            currentPosition.append("GPS");
                        } else if (bdLocation.getLocType() == BDLocation.TypeNetWorkLocation) {
                            currentPosition.append("网络");
                        }
                        positionText.setText(currentPosition);
                    }
                });
            }
        }
    }
    

    注意:上面代码中多了一行:

    mLocationClient.setAgreePrivacy(true);
    

    如果没有这一行,报错:...Please recheck the setAgreePrivacy interface,忘记截图了。

    1. 问题描述: 根据官方文档配置完后,地图无法显示,也不显示网格图层,会报location调用隐私政策接口异常。
    2. 解决办法: 除了在Application.java中正常初始化context信息之外,在调用地图的activity中的onCreat()方法中,添加一句LocationClient.setAgreePrivacy(true);
    3. 参考博客:解决百度地图sdk v7.5.0版本,地图不能正常显示问题(隐私政策)https://blog.csdn.net/qq_43962511/article/details/123840858

    运行时权限的用法,由于我们在AndroidManifest.xml中声明了很多权限,参考一下7.2.1小节中的危险权限表格可以发现,其中ACCESS_COARSE_LOCATION、ACCESS_FINE_LOCATION、READ_PHONE_STATE、WRITE_EXTERNAL_STORAGE这4个权限是需要进行运行时权限处理的,不过由于ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION都属于同一个权限组,因此两者只要申请其一就可以了。

    那么怎样才能在运行时一次性申请3个权限呢?

    这里我们使用了一种新的用法,首先创建一个空的List集合,然后依次判断这3个权限有没有被授权,如果没被授权就添加到List集合中,最后将List转换成数组,再调用ActivityCompat. requestPermissions()方法一次性申请。

    除此之外,onRequestPermissionsResult()方法中对权限申请结果的逻辑处理也和之前有所不同,这次我们通过一个循环将申请的每个权限都进行了判断,如果有任何一个权限被拒绝,那么就直接调用finish()方法关闭当前程序,只有当所有权限都被用户同意了,才会调用requestLocation()方法开始地理位置定位。

    requestLocation()方法中的代码比较简单,只是调用了一下LocationClient的start()方法就能开始定位了。定位的结果会回调到我们前面注册的监听器当中,也就是MyLocationListener。

    现在运行一下程序了。毫无疑问,打开程序首先就会弹出运行时权限的申请对话框,注意,与书中不同,我的Demo手机上是一项一项选择的是否同意(3次授权):

    image

    然后就会立刻开始定位了,结果如图:

    image

    可以看到,设备当前的经纬度信息已经成功定位出来了。

    不过,在默认情况下,调用LocationClient的start()方法只会定位一次,如果我们正在快速移动中,怎样才能实时更新当前的位置呢?

    为此,百度LBS SDK提供了一系列的设置方法,来允许我们更改默认的行为,修改MainActivity中的代码,如下所示:

    public class MainActivity extends AppCompatActivity {
        ...
        private void requestLocation() {
            initLocation();
            mLocationClient.start();
        }
        private void initLocation() {
            LocationClientOption option = new LocationClientOption();
            // 表示每5秒钟会更新一下当前的位置
            option.setScanSpan(5000);
            mLocationClient.setLocOption(option);
        }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mLocationClient.stop();
        }
        ...
    }
    

    这里增加了一个initLocation()方法,在initLocation()方法中我们创建了一个LocationClientOption对象,然后调用它的setScanSpan()方法来设置更新的间隔。

    最后要记得,在活动被销毁的时候一定要调用LocationClient的stop()方法来停止定位,不然程序会持续在后台不停地进行定位,从而严重消耗手机的电量。

    重新运行程序,拿着手机随处移动,界面上的经纬度信息也会跟着一起变化。(测试,有效!!!)

    11.3.3 选择定位模式

    Android中主要有两种:一种是通过GPS定位,一种是通过网络定位。

    上一小节中的例子中,一直是使用的网络定位。那么如何才能切换到精确度更高的GPS定位呢?

    本小节我们就来学习一下。首先,GPS定位功能必须要由用户主动去启用才行,不然任何应用程序都无法使用GPS获取到手机当前的位置信息。进入手机的设置→位置信息(我的手机没有位置信息选项,下图是我Android的模拟器上的截取的),如图:

    image

    可以通过顶部的开关来控制定位功能是开启还是关闭,另外,点击“模式”可以选择具体的定位模式,我的手机与书上的不一样,我下拉手机菜单栏,看到GPS,手指长按GPS跳到GPS设置界面,如下图所示:

    image

    其中,

    • 高精确度模式,表示允许使用GPS、无线网络、蓝牙或移动网络来进行定位;
    • 节电模式,表示仅允许使用无线网络、蓝牙或移动网络来进行定位;
    • 仅限设备模式,表示仅允许使用GPS来进行定位。

    也就是说,如果我们想要使用GPS定位功能,这里必须要选择高精确度模式,或者仅限设备模式。

    当然,你并不需要担心一旦启用GPS定位功能后,手机的电量就会直线下滑,这只是表明你已经同意让应用程序来对你的手机进行GPS定位了,但只有当定位操作真正开始的时候,才会影响到手机的电量。

    开启了GPS定位功能之后,再回来看一下代码。我们可以在initLocation()方法中对百度LBS SDK的定位模式进行指定,一共有3种模式可选:Hight_Accuracy、Battery_Saving和Device_Sensors。

    • Hight_Accuracy 表示高精确度模式,会在GPS信号正常的情况下优先使用GPS定位,在无法接收GPS信号的时候使用网络定位。
    • Battery_Saving 表示节电模式,只会使用网络进行定位。
    • Device_Sensors 表示传感器模式,只会使用GPS进行定位。

    Hight_Accuracy是默认的模式,也就是说,我们即使不修改任何代码,只要拿着手机走到室外去,让手机可以接收到GPS信号,就会自动切换到GPS定位模式了。

    当然我们也可以强制指定只使用GPS进行定位,修改MainActivity中的代码,如下所示:

    public class MainActivity extends AppCompatActivity {
       ...
        private void initLocation() {
            LocationClientOption option = new LocationClientOption();
            // 表示每5秒钟会更新一下当前的位置
            option.setScanSpan(5000);
           // 调用了setLocationMode()方法来将定位模式指定成传感器模式,也就是说只能使用GPS进行定位
            option.setLocationMode(LocationClientOption.LocationMode.Device_Sensors);
            mLocationClient.setLocOption(option);
        }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mLocationClient.stop();
        }
    	...
    }
    

    这里调用了setLocationMode()方法来将定位模式指定成传感器模式,也就是说只能使用GPS进行定位。重新运行一下程序,然后拿着你的手机走到室外去,切记:如果想要使用GPS定位功能,手机上的GPS模式,必须要选择高精确度模式,或者仅限设备模式 ,结果如图:

    image

    11.3.4 看得懂的位置信息

    虽然成功获取到了设备当前位置的经纬度信息,但这种经纬度的值一般人是根本看不懂。

    为了能够更加直观地阅读,我们还需要学习一下如何获取看得懂的位置信息。百度LBS SDK在这方面提供了非常好的支持,我们只需要进行一些简单的接口调用就能得到当前位置各种丰富的地址信息。

    修改MainActivity中的代码,如下所示:

    public class MainActivity extends AppCompatActivity {
        ...
        private void initLocation() {
            LocationClientOption option = new LocationClientOption();
            // 表示每5秒钟会更新一下当前的位置
            option.setScanSpan(5000);
            // 调用了LocationClientOption的setIsNeedAddress()方法,并传入true,表示需要获取当前位置详细的地址信息。
            option.setIsNeedAddress(true);
            // 调用了setLocationMode()方法来将定位模式指定成传感器模式,也就是说只能使用GPS进行定位
            option.setLocationMode(LocationClientOption.LocationMode.Device_Sensors);
            mLocationClient.setLocOption(option);
        }
        ...
        public class MyLocationListener implements BDLocationListener {
            @Override
            public void onReceiveLocation(BDLocation bdLocation) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        StringBuilder currentPosition = new StringBuilder();
                        currentPosition.append("纬度:").append(bdLocation.getLatitude()).append("\n");
                        currentPosition.append("经度:").append(bdLocation.getLongitude()).append("\n");
                        currentPosition.append("国家:").append(bdLocation.getCountry()).append("\n");
                        currentPosition.append("省:").append(bdLocation.getProvince()).append("\n");
                        currentPosition.append("市:").append(bdLocation.getCity()).append("\n");
                        currentPosition.append("区:").append(bdLocation.getDistrict()).append("\n");
                        currentPosition.append("街道:").append(bdLocation.getStreet()).append("\n");
                        currentPosition.append("定位方式:");
                        if (bdLocation.getLocType() == BDLocation.TypeGpsLocation) {
                            currentPosition.append("GPS");
                        } else if (bdLocation.getLocType() == BDLocation.TypeNetWorkLocation) {
                            currentPosition.append("网络");
                        }
                        positionText.setText(currentPosition);
                    }
                });
            }
        }
    }
    

    在MyLocationListener的onReceiveLocation()方法就可以获取到各种丰富的地址信息了,调用getCountry()方法可以得到当前所在国家,调用getProvince()方法可以得到当前所在省份,以此类推。

    需要注意,由于获取地址信息一定需要用到网络,因此即使我们将定位模式指定成了Device_Sensors,也会自动开启网络定位功能。现在重新运行一下程序,结果如图:

    image

    可以看到,手机当前位置的地址信息已经成功显示出来了。如果你带着手机移动了较远的距离,界面上显示的位置也会跟着一起变化的。

    11.4 使用百度地图

    手机地图能够随时随地进行查看,并且轻松构建出行路线,使用起来明显更加地方便。应用程序里也是可以加入地图功能的,比如优步中使用的就是百度地图。本节我们就来学习一下这方面的知识。

    11.4.1 让地图显示出来

    让地图显示出来,这个例子我决定我像书中介绍的再LBSTest应用中做了,想再自己做一遍使用百度地图的流程,所以又新建了一个LBSTest2应用,用这个来记录一下。

    • 第一步:Android Studio 中:

      • 新建LBSTest2应用

      • 下载SDK本地依赖

      • 将开发包拷贝至工程

        • 添加jar文件(BaiduLBS_Android.jar),Add As Library...

        • 添加so文件(在src/main/目录下新建jniLibs目录(如果您的项目中已经包含该目录不用重复创建),在下载的开发包中拷贝项目中需要的CPU架构对应的so文件文件夹到jniLibs目录)

          注意:Jar文件和so文件的版本号必须一致,并且保证Jar文件与so文件是同一版本包取出的。

    image

    (注:第一步主要参考官方文档地址的地址:https://lbsyun.baidu.com/index.php?title=androidsdk/guide/create-project/androidstudio

    image

    • 第三步:配置AndroidManifest.xml文件
    <application>  
        <meta-data  
            android:name="com.baidu.lbsapi.API_KEY"  
            android:value="开发者 key" />  
    </application>
    
    • 第四步: 在<application/>外部添加如下权限声明:
    <!-- 访问网络,进行地图相关业务数据请求,包括地图数据,路线规划,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.WRITE_EXTERNAL_STORAGE" />
    

    注:自Android6.0起部分权限的使用需要开发者在代码中动态申请。

    参考博客:解决百度地图sdk v7.5.0版本,地图不能正常显示问题,https://blog.csdn.net/qq_43962511/article/details/123840858

    // 是否同意隐私政策,默认为false
    SDKInitializer.setAgreePrivacy(getApplicationContext(), true);
    
    • 第五步: 在布局文件中添加地图容器

    MapView是View的一个子类,用于在Android View中放置地图。MapView的使用方法与Android提供的其他View一样。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <com.baidu.mapapi.map.MapView
            android:id="@+id/bmapView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clickable="true" />
    </LinearLayout>
    
    • 第六步:地图初始化

    注意:在SDK各功能组件使用之前都需要调用“SDKInitializer.initialize(getApplicationContext())”,因此建议在应用创建时初始化SDK引用的Context为全局变量。

    新建一个自定义的Application——DemoApplication,在其onCreate方法中完成SDK的初始化。示例代码如下:

    (其中,“是否同意隐私政策”是之前没有添加,出来bug后,上网查到的。try/catch 是网上查到的)

    package com.zhouzhou.lbstest2;
    
    import android.app.Application;
    
    import com.baidu.mapapi.CoordType;
    import com.baidu.mapapi.SDKInitializer;
    import com.baidu.mapapi.common.BaiduMapSDKException;
    
    public class DemoApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            // 是否同意隐私政策,默认为false
            SDKInitializer.setAgreePrivacy(getApplicationContext(), true);
            try {
                //在使用SDK各组件之前初始化context信息,传入ApplicationContext
                SDKInitializer.initialize(getApplicationContext());
                //自4.3.0起,百度地图SDK所有接口均支持百度坐标和国测局坐标,用此方法设置您使用的坐标类型.
                //包括BD09LL和GCJ02两种坐标,默认是BD09LL坐标。
                SDKInitializer.setCoordType(CoordType.BD09LL);
    
            } catch (BaiduMapSDKException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 第七步:在AndroidManifest.xml文件中声明该Application
    <application
           android:name=".DemoApplication"
    

    image

    • 第八步:创建地图Activity,管理MapView生命周期

    注意:在项目中使用地图的时候要特别注意合理地管理地图生命周期,这非常重要。

    以下示例代码简述对地图生命周期的管理:

    package com.zhouzhou.lbstest2;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Bundle;
    import com.baidu.mapapi.map.MapView;
    
    public class MainActivity extends AppCompatActivity {
        private MapView mMapView = null;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            //获取地图控件引用
            mMapView = (MapView) findViewById(R.id.bmapView);
        }
        @Override
        protected void onResume() {
            super.onResume();
            //在activity执行onResume时执行mMapView. onResume (),实现地图生命周期管理
            mMapView.onResume();
        }
        @Override
        protected void onPause() {
            super.onPause();
            //在activity执行onPause时执行mMapView. onPause (),实现地图生命周期管理
            mMapView.onPause();
        }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //在activity执行onDestroy时执行mMapView.onDestroy(),实现地图生命周期管理
            mMapView.onDestroy();
        }
    }
    

    完成以上工作即可在您的应用中显示地图:

    image

    11.4.2 移动到我的位置

    这是一张默认的地图,显示的是北京市中心的位置,而你可能希望看到更加精细的地图信息,比如说自己所在位置的周边环境。显然,通过缩放和移动的方式来慢慢找到自己的位置是一种很愚蠢的做法。那么本小节我们就来学习一下,如何才能在地图中快速移动到自己的位置。

    百度LBS SDK的API中提供了一个BaiduMap类,它是地图的总控制器,调用MapView的getMap()方法就能获取到BaiduMap的实例,如下所示:

    BaiduMap baiduMap = mapView.getMap();
    

    有了BaiduMap后,我们就能对地图进行各种各样的操作了,比如设置地图的缩放级别以及将地图移动到某一个经纬度上。

    百度地图将缩放级别的取值范围限定在3到19之间,其中小数点位的值也是可以取的,值越大,地图显示的信息就越精细。比如我们想要将缩放级别设置成12.5,就可以这样写:

    MapStatusUpdate update = MapStatusUpdateFactory.zoomTo(12.5f);
    baiduMap.animateMapStatus(update);
    

    其中MapStatusUpdateFactory的zoomTo()方法接收一个float型的参数,就是用于设置缩放级别的,这里我们传入12.5f。zoomTo()方法返回一个MapStatusUpdate对象,我们把这个对象传入BaiduMap的animateMapStatus()方法当中即可完成缩放功能。

    那么怎样才能让地图移动到某一个经纬度上呢?

    这就需要借助LatLng类。其实LatLng并没有什么太多的用法,主要就是用于存放经纬度值的,它的构造方法接收两个参数,第一个参数是纬度值,第二个参数是经度值。之后调用MapStatusUpdateFactory的newLatLng()方法将LatLng对象传入,newLatLng()方法返回的也是一个MapStatusUpdate对象,我们再把这个对象传入BaiduMap的animateMapStatus()方法当中,就可以将地图移动到指定的经纬度上了,写法如下:

    LatLng ll = new LatLng(39.915,116.404);
    MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll);
    baiduMap.animateMapStatus(update);
    

    上述代码就实现了将地图移动到北纬39.915度、东经116.404度这个位置的功能。

    了解了这些知识之后,接下来再去实现将地图快速移动到自己位置的功能就变得非常简单了。首先我们可以利用在11.3节中所学的定位技术来获得自己当前位置的经纬度,之后再按照上述的方法来将地图移动到指定的位置就可以了。

    那么下面我们就来继续完善LBSTest这个项目,加入“移动到我的位置”这个功能。修改MainActivity中的代码,如下所示:

    package com.zhouzhou.lbstest2;
    
    import androidx.annotation.NonNull;
    import androidx.appcompat.app.AppCompatActivity;
    import androidx.core.app.ActivityCompat;
    import androidx.core.content.ContextCompat;
    
    import android.Manifest;
    import android.content.pm.PackageManager;
    import android.os.Bundle;
    import android.widget.Toast;
    
    import com.baidu.location.BDLocation;
    import com.baidu.location.BDLocationListener;
    import com.baidu.location.LocationClient;
    import com.baidu.mapapi.map.BaiduMap;
    import com.baidu.mapapi.map.MapStatusUpdate;
    import com.baidu.mapapi.map.MapStatusUpdateFactory;
    import com.baidu.mapapi.map.MapView;
    import com.baidu.mapapi.model.LatLng;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MainActivity extends AppCompatActivity {
        public LocationClient mLocationClient;
        private MapView mMapView = null;
        private BaiduMap baiduMap;
        private boolean isFirstLocate = true;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //没有它,会报错:Please recheck the setAgreePrivacy interface
            mLocationClient.setAgreePrivacy(true);
            try {
                mLocationClient = new LocationClient(getApplicationContext());
            } catch (Exception e) {
                e.printStackTrace();
            }
            mLocationClient.registerLocationListener(new MyLocationListener());
            setContentView(R.layout.activity_main);
            //获取地图控件引用
            mMapView = (MapView) findViewById(R.id.bmapView);
            baiduMap = mMapView.getMap();
            List<String> permissionList = new ArrayList<>();
            if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
            }
            if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(Manifest.permission.READ_PHONE_STATE);
            }
            if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
            }
            if (! permissionList.isEmpty()) {
                String [] permissions = permissionList.toArray(new String[permissionList.size()]);
                ActivityCompat.requestPermissions(MainActivity.this,permissions,1);
            } else {
                requestLocation();
            }
        }
        private void requestLocation() {
            mLocationClient.start();
        }
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            switch (requestCode) {
                case 1:
                    if (grantResults.length > 0) {
                        for (int result : grantResults) {
                            if (result != PackageManager.PERMISSION_GRANTED) {
                                Toast.makeText(this,"必须同意所有权限才能使用本程序",Toast.LENGTH_SHORT).show();
                                finish();
                                return;
                            }
                        }
                        requestLocation();
                    } else {
                        Toast.makeText(this,"发生未知错误",Toast.LENGTH_SHORT).show();
                        finish();
                    }
                    break;
                default:
            }
        }
        private void navigateTo(BDLocation location) {
            if (isFirstLocate) {
                LatLng ll = new LatLng(location.getLatitude(),location.getLongitude());
                MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll);
                baiduMap.animateMapStatus(update);
                update = MapStatusUpdateFactory.zoomTo(16f);
                baiduMap.animateMapStatus(update);
                isFirstLocate = false;
            }
        }
        public class MyLocationListener implements BDLocationListener {
    
            @Override
            public void onReceiveLocation(BDLocation bdLocation) {
                if (bdLocation.getLocType() == BDLocation.TypeGpsLocation || bdLocation.getLocType() == BDLocation.TypeNetWorkLocation) {
                    navigateTo(bdLocation);
                }
            }
        }
        @Override
        protected void onResume() {
            super.onResume();
            //在activity执行onResume时执行mMapView. onResume (),实现地图生命周期管理
            mMapView.onResume();
        }
        @Override
        protected void onPause() {
            super.onPause();
            //在activity执行onPause时执行mMapView. onPause (),实现地图生命周期管理
            mMapView.onPause();
        }
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mLocationClient.stop();
            //在activity执行onDestroy时执行mMapView.onDestroy(),实现地图生命周期管理
            mMapView.onDestroy();
        }
    }
    

    主要是加入了一个navigateTo()方法。这个方法,先是将BDLocation对象中的地理位置信息取出并封装到LatLng对象中,然后调用MapStatusUpdateFactory的newLatLng()方法并将LatLng对象传入,接着将返回的MapStatus-Update对象作为参数传入到BaiduMap的animateMapStatus()方法当中,和上面介绍的用法是一模一样的。

    并且,这里为了让地图信息可以显示得更加丰富一些,我们将缩放级别设置成了16。需要注意,上述代码当中我们使用了一个isFirstLocate变量,这个变量的作用是为了防止多次调用animateMapStatus()方法,因为将地图移动到我们当前的位置只需要在程序第一次定位的时候调用一次就可以了。

    另外,当定位到设备当前位置的时候,我们在onReceiveLocation()方法中直接把BDLocation对象传给navigateTo()方法,这样就能够让地图移动到设备所在的位置了。

    注意:出现一些因为没有添加权限而产生的bug,所以,在AndroidManifest.xml中又添加了几项权限,整体如下:

    <!-- 访问网络,进行地图相关业务数据请求,包括地图数据,路线规划,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.WRITE_EXTERNAL_STORAGE" />
    <!-- 这个权限用于进行网络定位 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <!-- 这个权限用于访问GPS定位 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <!-- WIFI -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
    <!-- PHONE -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.CALL_PHONE"/>
    

    现在重新运行一下程序,结果如图:

    image

    11.4.3 让“我”显示在地图上

    已经可以让地图显示我们周边的环境了。通常情况下手机地图上应该都会有一个小光标,用于显示设备当前所在的位置,并且如果设备正在移动的话,那么这个光标也会跟着一起移动。那么我们现在就继续对现有代码进行扩展,让“我”能够显示在地图上。

    百度LBS SDK当中提供了一个MyLocationData.Builder类,这个类是用来封装设备当前所在位置的,我们只需将经纬度信息传入到这个类的相应方法当中就可以了,如下所示:

    MyLocationData.Builder locationBuilder = new MyLocationData.Builder();
    locationBuilder.latitude(39.915);
    locationBuilder.longitude(116.404);
    

    MyLocationData.Builder类还提供了一个build()方法,当我们把要封装的信息都设置完成之后,只需要调用它的build()方法,就会生成一个MyLocationData的实例,然后再将这个实例传入到BaiduMap的setMyLocationData()方法当中,就可以让设备当前的位置显示在地图上了,写法如下:

    MyLocationData locationData = locationBuilder.build();
    baiduMap.setMyLocationData(locationData);
    

    大体思路就是这个样子,下面我们开始来实现一下,修改MainActivity中的代码,如下所示:

    public class MainActivity extends AppCompatActivity {
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //没有它,会报错:Please recheck the setAgreePrivacy interface
            mLocationClient.setAgreePrivacy(true);
            try {
                mLocationClient = new LocationClient(getApplicationContext());
            } catch (Exception e) {
                e.printStackTrace();
            }
            mLocationClient.registerLocationListener(new MyLocationListener());
            setContentView(R.layout.activity_main);
            //获取地图控件引用
            mMapView = (MapView) findViewById(R.id.bmapView);
            baiduMap = mMapView.getMap();
            //根据百度地图的限制,想要使用这一功能,一定要事先调用BaiduMap的setMyLocationEnabled()方法将此功能开启,否则设备的位置将无法在地图上显示。
            baiduMap.setMyLocationEnabled(true);
            ...
        private void navigateTo(BDLocation location) {
            if (isFirstLocate) {
                LatLng ll = new LatLng(location.getLatitude(),location.getLongitude());
                MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll);
                baiduMap.animateMapStatus(update);
                update = MapStatusUpdateFactory.zoomTo(16f);
                baiduMap.animateMapStatus(update);
                isFirstLocate = false;
            }
            MyLocationData.Builder locationBuilder = new MyLocationData.Builder();
            locationBuilder.latitude(location.getLatitude());
            locationBuilder.longitude(location.getLongitude());
            MyLocationData locationData = locationBuilder.build();
            baiduMap.setMyLocationData(locationData);
        }
        ...
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //在activity执行onDestroy时执行mMapView.onDestroy(),实现地图生命周期管理
            mLocationClient.stop();
            mMapView.onDestroy();
            //在程序退出的时候,也要记得将BaiduMap的setMyLocationEnabled()方法功能给关闭掉
            baiduMap.setMyLocationEnabled(false);
        }
    }
    

    在navigateTo()方法中,添加了MyLocationData的构建逻辑,将Location中包含的经度和纬度分别封装到了MyLocationData.Builder当中,最后把MyLocationData设置到了BaiduMap的setMyLocationData()方法当中。

    注意:这段逻辑必须写在isFirstLocate这个if条件语句的外面,因为让地图移动到我们当前的位置只需要在第一次定位的时候执行,但是设备在地图上显示的位置却应该是随着设备的移动而实时改变的。

    image

    想要更加深入地研究百度LBS的各种用法,可以到官方网站上面参考开发指南,地址是:http://lbsyun.baidu.com。另外,百度LBS SDK的版本未来随时都有可能更新,也许更新之后会导致书上的例子无法正常运行,因此除了照着图书学习之外,根据官网的开发指南来进行学习也是非常重要的

    11.5 Git时间——版本控制工具的高级用法

    打开Git Bash,并进入到LBSTest2这个项目的根目录,然后执行提交操作:

    git init
    git add
    git commit -m "First Commit"
    

    这样就将准备工作完成了,下面就让我们开始学习关于Git的高级用法。

    11.5.1 分支的用法

    分支是版本控制工具中比较高级且比较重要的一个概念,它主要的作用就是在现有代码的基础上开辟一个分叉口,使得代码可以在主干线和分支线上同时进行开发,且相互之间不会影响。分支的工作原理示意图如图:

    image

    为什么需要建立分支呢?只在主干线上进行开发不是挺好的吗?

    通常情况下,只在主干线上进行开发是完全没有问题的,不过一旦涉及出版本的情况,如果不建立分支的话,你就会非常地头疼。举个简单的例子吧,比如说你们公司研发了一款不错的软件,最近刚刚完成,并推出了1.0版本。但是领导是不会让你们闲着的,马上提出了新的需求,让你们投入到了1.1版本的开发工作当中。过了几个星期,1.1版本的功能已完成了一半,但是这个时候有用户反馈,之前上线的1.0版本发现了几个重大的bug,严重影响软件的正常使用。领导也相当重视这个问题,要求你们立刻修复这些bug,并重新发布1.0版本,但这个时候你就非常为难了,你会发现根本没法去修复这些bug。因为现在1.1版本已开发一半了,如果在现有代码的基础上修复这些bug,那么更新的1.0版本将会带有一半1.1版本的功能!

    但是如果你使用了分支的话,就完全不会存在这个让人头疼的问题。你只需要在发布1.0版本的时候建立一个分支,然后在主干线上继续开发1.1版本的功能。当1.0版本上发现任何bug的时候,就在分支线上进行修改,然后发布新的1.0版本,并记得将修改后的代码合并到主干线上。这样的话,不仅可以轻松解决掉1.0版本存在的bug,而且保证了主干线上的代码也已经修复了这些bug,当1.1版本发布时就不会有同样的bug存在了。

    分支的英文名是branch,如果想要查看当前的版本库当中有哪些分支,可以使用git branch这个命令,结果如图:

    image

    由于目前LBSTest2项目中还没有创建过任何分支,因此只有一个master分支存在,这也就是前面所说的主干线。接下来我们尝试去创建一个分支,命令如下:

    git branch version1.0
    

    这样就创建了一个名为version1.0的分支,我们再次输入git branch这个命令来检查一下,结果如图:

    image

    可以看到,果然有一个叫作version1.0的分支出现了。你会发现,master分支的前面有一个“*”号,说明目前我们的代码还是在master分支上的,怎样切换到version1.0这个分支上——使用checkout命令即可,如下所示:

    git checkout version1.0
    

    再次输入git branch来进行检查,结果如图:

    image

    可以看到,我们已经把代码成功切换到version1.0这个分支上了。

    需要注意的是,在version1.0分支上修改并提交的代码将不会影响到master分支。同样的道理,在master分支上修改并提交的代码也不会影响到version1.0分支。因此,如果我们在version1.0分支上修复了一个bug,在master分支上这个bug仍然是存在的

    这时将修改的代码一行行复制到master分支上显然不是一种聪明的做法,最好的办法就是使用merge命令来完成合并操作,如下所示:

    git checkout master
    git merge version1.0
    

    仅仅这样简单的两行命令,就可以把在version1.0分支上修改并提交的内容合并到master分支上了。当然,在合并分支的时候还有可能出现代码冲突的情况,这个时候需要你自己解决这些冲突,Git在这里就无法帮助。

    最后,当我们不再需要version1.0这个分支的时候,可以使用如下命令将这个分支删除掉:

    git branch -D version1.0
    

    image

    11.5.2 与远程版本库协作

    如果你是一个人在开发,那么使用版本控制工具就远远无法发挥出它真正强大的功能。

    所有版本控制工具最重要的一个特点就是可以使用它来进行团队合作开发。每个人的电脑上都会有一份代码,当团队的某个成员在自己的电脑上编写完成了某个功能后,就将代码提交到服务器,其他的成员只需要将服务器上的代码同步到本地,就能保证整个团队所有人的代码都相同。这样的话,每个团队成员就可以各司其职,大家共同来完成一个较为庞大的项目。

    那么如何使用Git来进行团队合作开发呢?这就需要有一个远程的版本库,团队的每个成员都从这个版本库中获取到最原始的代码,然后各自进行开发,并且以后每次提交的代码都同步到远程版本库上就可以了。另外,团队中的每个成员最好都要养成经常从版本库中获取最新代码的习惯,不然的话,大家的代码就很有可能经常出现冲突。

    比如说现在有一个远程版本库的Git地址是https://github.com/example/test.git,就可以使用如下的命令将代码下载到本地:

    git clone https://github.com/example/test.git
    

    之后,你在这份代码的基础上进行了一些修改和提交,那么怎样才能把本地修改的内容同步到远程版本库上呢?这就需要借助push命令来完成了,用法如下所示:

    git push origin master
    

    其中origin部分指定的是远程版本库的Git地址,master部分指定的是同步到哪一个分支上,上述命令就完成了将本地代码同步到https://github.com/example/test.git这个版本库的master分支上的功能。

    如何将远程版本库上的修改同步到本地。Git提供了两种命令来完成此功能,分别是fetch和pull, fetch的语法规则和push是差不多的,如下所示:

    git fetch origin master
    

    执行这个命令后,就会将远程版本库上的代码同步到本地,不过同步下来的代码并不会合并到任何分支上去,而是会存放到一个origin/master分支上,这时我们可以通过diff命令来查看远程版本库上到底修改了哪些东西:

    git diff origin/master
    

    之后,再调用merge命令将origin/master分支上的修改合并到主分支上即可,如下所示:

    git merge origin/master
    

    而pull命令则是相当于将fetch和merge这两个命令放在一起执行了,它可以从远程版本库上获取最新的代码并且合并到本地,用法如下所示:

    git pull origin/master
    

    11.6 小结与点评

    主要学习了基于位置服务的工作原理和用法,借助百度提供的LBS SDK,可以随时确定自己当前位置的经纬度,并且还能获取到具体的省、市、区、街道等地址。又学习了百度地图的用法,不仅成功地将地图信息显示了出来,还综合利用了前面所学到的定位技术实现了一个较为完整的例子。对Git的用法进行了更深一步的探究,对分支和远程版本库的使用都有了一定层次的了解。

    下一章中我们将会学习Android 5.0系统中新增的一套全新的知识点——Material Design。

  • 相关阅读:
    红队核心工具介绍
    cms漏洞总结(二)
    cms漏洞总结(二)
    cms漏洞总结(二)
    cms漏洞总结(二)
    cms漏洞总结(二)
    cms漏洞总结(二)
    cms漏洞总结 (一)
    好看的樱花落特效
    SELinux 案例 1
  • 原文地址:https://www.cnblogs.com/1693977889zz/p/16256367.html
Copyright © 2020-2023  润新知