编码阶段
新版-------------------
我们接着往下更新,本篇文章要做的是获取定位信息显示在页面,那么首先我们需要修改一下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()方法中进行调用,如下图所示:
下面运行一下吧,记得用手机真机运行,如下图所示:
五、文章源码
欢迎 Star 和 Fork
第二篇文章源码地址: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
欢迎 Star 和 Fork