Android 天气APP(二)获取定位信息

上一篇:Android 天气APP(一)开发准备

新版-------------------

  我们接着往下更新,本篇文章要做的是获取定位信息显示在页面,那么首先我们需要修改一下activity_main.xml的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_address_detail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

这里我给了TextView一个id,现在内容是Hello World!,当我们运行App时这里就会变成当前的地址。

一、使用ViewBinding

  在上一篇文章中,我们做了ViewBinding功能的开启,说到过我们不需要再写findViewById了,那么我们现在来使用一下ViewBinding,进入MainActivity,修改代码如下所示:

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import com.llw.goodweather.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
    
    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
    }
}

  这里的ActivityMainBinding 就是我们开启ViewBinding功能之后,编译器根据xml布局去自动生成的编译时类,里面就写好了findViewById了,它的用法比较简单,先声明,再赋值,最后设置到setContentView(),注意这个类的命名方式,和xml的文件名称有关,我们的xml文件名称是activity_main.xml,所以生成的时候ActivityMain,再加上Binding。如果你这里爆红就说明你没有进行导包,鼠标点击爆红的地方,Alt + Enter,选择有import的哪一项就可以了,下面我们可以使用binding点出控件,如下图所示:

在这里插入图片描述
  这里点出来的控件也是遵守驼峰命名的,我们还可以知道这个控件是一个什么控件,ViewBinding的使用就是这么简单,当然了在不同的View上使用也会不同,碰到了,我们再讲。

二、初始化SDK

  在使用之前我们需要先对SDK进行初始化,定位SDK中除了那个jar包,还有一些so库文件,这些文件也需要正常加载到项目中,才能正常使用SDK功能,在app的build.gradle中添加如下代码:

    //加载so文件
    sourceSets{
        main{
            jniLibs.srcDir 'libs'
            jni.srcDirs = []    //disable automatic ndk-build
        }
    }

添加位置如下图所示:

在这里插入图片描述

  然后Sync Now进行同步一下,就可以使用了,不过我们的初始化还没有完成,因为工信部提出了关于个人信息保护的要求,所以需要在使用SDK功能之前同意隐私政策,而我们又不想写重复的代码,那么就可以这样来做,我们自定义一个Application,在初始化的时候同意隐私政策即可。

com.llw.goodweather下新建一个WeatherApp类,里面的代码如下所示:

public class WeatherApp extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        //使用定位需要同意隐私合规政策
        LocationClient.setAgreePrivacy(true);
    }
}

要使这个类生效,我们需要在AndroidManifest.xml中进行配置,如下图所示:

在这里插入图片描述

三、初始化定位

  因为这个项目可能用到定位的功能不止一个Activity,所以我们可以将功能封装起来,我们在com.llw.goodweather包下新建一个location包,里面新建一个接口LocationCallback,代码如下:

/**
 * 定位接口
 */
public interface LocationCallback {
    /**
     * 接收定位
     * @param bdLocation 定位数据
     */
    void onReceiveLocation(BDLocation bdLocation);
}

然后通过在location包下新建一个自定义监听类MyLocationListener,代码如下:

/**
 * 自定义定位监听类
 */
public class MyLocationListener extends BDAbstractLocationListener {

    private final String TAG = MyLocationListener.class.getSimpleName();

    //定位回调
    private LocationCallback callback;

    //需要定位的页面调用此方法进行接口回调处理
    public void setCallback(LocationCallback callback) {
        this.callback = callback;
    }

    @Override
    public void onReceiveLocation(BDLocation bdLocation) {
        if (callback == null) {
            Log.e(TAG, "callback is Null!");
            return;
        }
        callback.onReceiveLocation(bdLocation);
    }
}

  这里我们将百度SDK定位的定位结果返回工程抽离出来,用接口回调的方式,减少Activity使用时的成本,哪里使用就哪里实现接口接口。

  那么我们现在要在MainActivity中使用要怎么做呢?在MainActivity中新增一个初始化定位的方法,代码如下所示:

	public LocationClient mLocationClient = null;
    private final MyLocationListener myListener = new MyLocationListener();
	
	/**
     * 初始化定位
     */
    private void initLocation() {
        try {
            mLocationClient = new LocationClient(getApplicationContext());
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (mLocationClient != null) {
            myListener.setCallback(this);
            //注册定位监听
            mLocationClient.registerLocationListener(myListener);
            LocationClientOption option = new LocationClientOption();
            //如果开发者需要获得当前点的地址信息,此处必须为true
            option.setIsNeedAddress(true);
            //可选,设置是否需要最新版本的地址信息。默认不需要,即参数为false
            option.setNeedNewVersionRgc(true);
            //需将配置好的LocationClientOption对象,通过setLocOption方法传递给LocationClient对象使用
            mLocationClient.setLocOption(option);
        }
    }

  不出意外的话你会有一个地方报错,就是this,这里就是我上面提到的你需要实现,重写自己的方法,有一个快捷的方式,鼠标点击这个this,使用Alt + Enter出现一个弹窗,这里选择第四项,如下图所示:

在这里插入图片描述

回车后你会发现多了一个onReceiveLocation()方法,代码如下:

    @Override
    public void onReceiveLocation(BDLocation bdLocation) {
        
    }

我们可以在这里面通过bdLocation得到一些需要的信息,例如:

	@Override
    public void onReceiveLocation(BDLocation bdLocation) {
        double latitude = bdLocation.getLatitude();    //获取纬度信息
        double longitude = bdLocation.getLongitude();    //获取经度信息
        float radius = bdLocation.getRadius();    //获取定位精度,默认值为0.0f
        String coorType = bdLocation.getCoorType();
        //获取经纬度坐标类型,以LocationClientOption中设置过的坐标类型为准
        int errorCode = bdLocation.getLocType();//161  表示网络定位结果
        //获取定位类型、定位错误返回码,具体信息可参照类参考中BDLocation类中的说明
        String addr = bdLocation.getAddrStr();    //获取详细地址信息
        String country = bdLocation.getCountry();    //获取国家
        String province = bdLocation.getProvince();    //获取省份
        String city = bdLocation.getCity();    //获取城市
        String district = bdLocation.getDistrict();    //获取区县
        String street = bdLocation.getStreet();    //获取街道信息
        String locationDescribe = bdLocation.getLocationDescribe();    //获取位置描述信息
        binding.tvAddressDetail.setText(addr);//设置文本显示
    }

  这里我们可以获取到的信息我简单列举了一些,然后我们设置了binding.tvAddressDetail.setText(addr);,这样如果我们定位成功,那么TextView就会显示当前的地址内容。

现在初始化定位已经写好了,那么下面需要写一个方法来启动定位,在MainActivity中新增如下方法代码:

	private void startLocation() {
        if (mLocationClient != null) {
            mLocationClient.start();
        }
    }

四、检查和请求权限

  要能够启动定位,需要请求权限。在做权限请求之前,我们需要先想清楚业务逻辑,首先我们要请求那些权限、其次要检查Android版本、最后如果权限通过了则不要重复请求,先声明一些变量。

	//权限数组
    private final String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION};
    //请求权限意图
    private ActivityResultLauncher<String[]> requestPermissionIntent;

  这里我们通过意图的方式动态请求多个权限,这个意图是Activity Result API中的用法,这个组件也是Jetpack中的,意图可以做的事情是很多的,请求权限只是其中之一,我们先写一个方法用来实例化意图,在请求返回之后就启动定位,在MainActivity中新增方法,代码如下所示:

	private void registerIntent() {
        //请求权限意图
        requestPermissionIntent = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
            boolean fineLocation = Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_FINE_LOCATION));
            if (fineLocation) {
                //权限已经获取到,开始定位
                startLocation();
            }
        });
    }

  这个意图有一个特别的地方需要在Activity初始化之前进行注册,因此在这里我们需要在onCreate中调用,位置要注意一下,如下图所示:

在这里插入图片描述

下面我们一个请求权限的方法,代码如下:

	private void requestPermission() {
        //因为项目的最低版本API是23,所以肯定需要动态请求危险权限,只需要判断权限是否拥有即可
        if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            //开始权限请求
            requestPermissionIntent.launch(permissions);
            return;
        }
        //开始定位
        startLocation();
    }

  这里在请求权限之前进行了一个检查,如果有权限则就直接开始定位了。最后我们在onCreate()方法中进行调用,如下图所示:

在这里插入图片描述

下面运行一下吧,记得用手机真机运行,如下图所示:

在这里插入图片描述

五、文章源码

欢迎 StarFork

第二篇文章源码地址:GoodWeather-New-2

旧版-------------------

二、编写代码

1. 获取当前所在位置信息

先修改activity_main.xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:gravity="center"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
	<!--显示详细定位信息-->
    <TextView
        android:id="@+id/tv_address_detail"
        android:padding="20dp"
        android:gravity="center"
        android:textColor="#000"
        android:textSize="18sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

</LinearLayout>

① 绑定控件

然后进入到MainActivity.java
鼠标右键点击布局文件activity_main 选择Generate
在这里插入图片描述

在这里插入图片描述
现在就可以用这个插件,点击红色边框中的Generate ButterKnife Iniertions
在这里插入图片描述
然后Confirm提交即可
在这里插入图片描述

② Android版本判断

之前提到Android6.0之后有运行时权限这么一说,所以要先判断是什么版本。在这之前先写一个Toast工具类。
新建了一个utils包用于存放工具类。
在这里插入图片描述

工具类代码如下:

package com.llw.goodweather.utils;

import android.content.Context;
import android.widget.Toast;

/**
 * 消息提示工具类
 */
public class ToastUtils {
    public static void showLongToast(Context context, CharSequence llw) {
        Toast.makeText(context.getApplicationContext(), llw, Toast.LENGTH_LONG).show();
    }

    public static void showShortToast(Context context, CharSequence llw) {
        Toast.makeText(context.getApplicationContext(), llw, Toast.LENGTH_SHORT).show();
    }
}

然后在代码中做版本业务逻辑的判断。

	//权限判断
    private void permissionVersion(){
        if(Build.VERSION.SDK_INT >= 23){//6.0或6.0以上
            //动态权限申请
            
        }else {//6.0以下
            //发现只要权限在AndroidManifest.xml中注册过,均会认为该权限granted  提示一下即可
            ToastUtils.showShortToast(this,"你的版本在Android6.0以下,不需要动态申请权限。");
        }
    }

之后在onCreate方法中调用

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        permissionVersion();//权限判断
    }

③ 权限申请

	private RxPermissions rxPermissions;//权限请求框架
	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        rxPermissions = new RxPermissions(this);//实例化这个权限请求框架,否则会报错
        permissionVersion();//权限判断

    }
	//动态权限申请
    private void permissionsRequest() {
        rxPermissions.request(Manifest.permission.ACCESS_FINE_LOCATION)
                .subscribe(granted -> {
                    if (granted) {//申请成功
                        //得到权限之后开始定位

                    } else {//申请失败
                        ToastUtils.showShortToast(this, "权限未开启");
                    }
                });
    }

接下来在 permissionVersion 方法里面进行权限的申请
在这里插入图片描述

这个思路就相当的清晰了,一环扣一环,这样写的好处是便于理解,千万不要把什么东西都往onCreate里面塞,那样不仅增加代码阅读难度,也会提高BUG出现的频率。

④ 初始化LocationClient类

请在主线程中声明LocationClient类对象,该对象初始化需传入Context类型参数。

	//定位器
    public LocationClient mLocationClient = null;
    private MyLocationListener myListener = new MyLocationListener();
	//开始定位
    private void startLocation() {
        //声明LocationClient类
        mLocationClient = new LocationClient(this);
        //注册监听函数
        mLocationClient.registerLocationListener(myListener);
        LocationClientOption option = new LocationClientOption();

        //如果开发者需要获得当前点的地址信息,此处必须为true
        option.setIsNeedAddress(true);
        //可选,设置是否需要最新版本的地址信息。默认不需要,即参数为false
        option.setNeedNewVersionRgc(true);
        //mLocationClient为第二步初始化过的LocationClient对象
        //需将配置好的LocationClientOption对象,通过setLocOption方法传递给LocationClient对象使用
        mLocationClient.setLocOption(option);
        //启动定位
        mLocationClient.start();

    }

这时,你会发现myListener会有红线报错,是因为我们没有实现这个接口,下面来实现,这个方法和onCreate是平级的,你只要是写在MainActivity的{}里面,想放那里就放那里

⑤ 实现BDAbstractLocationListener接口

	/**
     * 定位结果返回
     */
    private class MyLocationListener extends BDAbstractLocationListener {
        @Override
        public void onReceiveLocation(BDLocation location) {
            double latitude = location.getLatitude();    //获取纬度信息
            double longitude = location.getLongitude();    //获取经度信息
            float radius = location.getRadius();    //获取定位精度,默认值为0.0f
            String coorType = location.getCoorType();
            //获取经纬度坐标类型,以LocationClientOption中设置过的坐标类型为准
            int errorCode = location.getLocType();//161  表示网络定位结果
            //获取定位类型、定位错误返回码,具体信息可参照类参考中BDLocation类中的说明
            String addr = location.getAddrStr();    //获取详细地址信息
            String country = location.getCountry();    //获取国家
            String province = location.getProvince();    //获取省份
            String city = location.getCity();    //获取城市
            String district = location.getDistrict();    //获取区县
            String street = location.getStreet();    //获取街道信息
            String locationDescribe = location.getLocationDescribe();    //获取位置描述信息
            tvAddressDetail.setText(addr);//设置文本显示
        }
    }

⑥ 显示定位结果

permissionsRequest() 方法中得到权限后调用定位方法,定位得到数据后在监听器里返回详细地址。
在这里插入图片描述
运行一下,请运行在自己的手机上,别使用虚拟机和模拟器(PS: 如果你运行报错了,请把你的错误信息贴出来,我好判断是什么问题)

在这里插入图片描述
点击 仅使用期间允许或者始终允许 之后就可以得到定位地址了。
在这里插入图片描述
现在位置已经拿到了,接下来就是通过这个位置来查询当天的天气了。如果你运行之后没有拿到地址,并且你已经打开了手机的定位开关和网络连接。那么此时你肯定是用模拟器或者虚拟机运行的,定位SDK中不适配模拟器和虚拟机,所以请使用真机运行

源码地址:GoodWeather
欢迎 StarFork

下一篇:Android 天气APP(三)访问天气API与数据请求

  • 32
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 172
    评论
评论 172
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

初学者-Study

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值