正如标题所示,今天带领大家来做修改地理位置的相关开发。
还是像往常一样,我们首先来分析如何获取目前所处的地理位置。很简单,获取地理位置无所谓通过手机定位,主要有两种方式定位,一个是高精度的GPS定位,直接通过与导航卫星进行交互获取所处的经纬度;另一个就是低精度的基站定位,通过连接手机附近的三个基站,使用三角定位计算出所处的位置。具体详细的定位相关的知识这里不再多说,有兴趣的小伙伴可以自行查看资料。
安卓针对以上两种的定位都提供了相应的Api可供访问调用,但是由于众所周知的原因,在国内通过网络定位无法访问到一些Api,所以就目前的国内手机定位方面,大多都是集成了第三方的SDK,其中高德和百度是做的最好的两个,也是被广泛使用的。
今天的讲解将会基于高德地图SDK来讲,我们的目标是拦截修改高德地图App的定位地点!
要想知道如何去做拦截,那么我们就要先分析一下高德地图SDK的源码,看看它在定位的时候都做了什么。有的小伙伴可能还没有集成过高德地图SDK,可能不是对它很了解,没关系,在这里我们只分析它的一小部分。
首先我们先看看高德地图SDK的官方文档,没有看过的小伙伴可以去看一下:高德地图SDK
这里我们关注的重点不在他的概述上,我们重点看一下【获取定位数据】这一个模块。为什么重点看这个呢?因为这里提供的都是获取相关数据的接口,包括经纬度,位置描述等等我们需要的信息。点击【获取定位数据】我们去看一下:
前面的三步主要讲述的都是获取数据的编写过程,我们不关注,这里我们只看第四步中AMapLocation类的核心方法解析:
我们看到这里呈现的都是AMapLocation类的get核心方法的介绍,其中包括了很多的位置信息,高德SDK正是通过在回调函数中解析AMapLocation类对象来获取相应的位置信息。
这了你可能要问了,AMapLocation类中有这么多的get方法,那么我们需要重点关注的是哪一个呢?我们这里重点关注的就是下面两个方法:
方法 |
返回值 |
返回值说明 |
方法效果 |
备注 |
---|---|---|---|---|
getLatitude() |
double |
纬度 |
获取纬度 |
V2.0.0版本起 |
getLongitude() |
double |
经度 |
获取经度 |
V2.0.0版本起 |
没错就是获取经纬度!为什么?因为其他的地理位置信息都是通过解析经纬度获得的,比如所处的城市,县市,街道,位置描述...等等的位置信息都是基于经纬度的解析,换句话说,如果我们改变了经纬度,那么相应的位置信息都会变化。这时候聪明的你肯定会想到了,这里我们拦截这两个方法,修改他们的返回值,改变经纬度,不就实现了位置更改么~
没错就是这样!不过还不要着急,既然这两个最重要的get方法在AMapLocation类中,那么我们还真的有必要去看一下AMapLocation类的源码,以便做出最好的处理。下面我们就去看一下这个AMapLocation类,它位于高德地图SDK中,这里你需要下载高德地图SDK才能看见它,我这里有一份源码,大家看一下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.amap.api.location;
import android.location.Location;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable.Creator;
import android.text.TextUtils;
import com.loc.cv;
import com.loc.dd;
import org.json.JSONObject;
public class AMapLocation extends Location implements Parcelable, Cloneable {
public static final int LOCATION_SUCCESS = 0;
public static final int ERROR_CODE_INVALID_PARAMETER = 1;
public static final int ERROR_CODE_FAILURE_WIFI_INFO = 2;
public static final int ERROR_CODE_FAILURE_LOCATION_PARAMETER = 3;
public static final int ERROR_CODE_FAILURE_CONNECTION = 4;
public static final int ERROR_CODE_FAILURE_PARSER = 5;
public static final int ERROR_CODE_FAILURE_LOCATION = 6;
public static final int ERROR_CODE_FAILURE_AUTH = 7;
public static final int ERROR_CODE_UNKNOWN = 8;
public static final int ERROR_CODE_FAILURE_INIT = 9;
public static final int ERROR_CODE_SERVICE_FAIL = 10;
public static final int ERROR_CODE_FAILURE_CELL = 11;
public static final int ERROR_CODE_FAILURE_LOCATION_PERMISSION = 12;
public static final int ERROR_CODE_FAILURE_NOWIFIANDAP = 13;
public static final int ERROR_CODE_FAILURE_NOENOUGHSATELLITES = 14;
public static final int ERROR_CODE_FAILURE_SIMULATION_LOCATION = 15;
public static final int ERROR_CODE_AIRPLANEMODE_WIFIOFF = 18;
public static final int ERROR_CODE_NOCGI_WIFIOFF = 19;
public static final int LOCATION_TYPE_GPS = 1;
public static final int LOCATION_TYPE_SAME_REQ = 2;
/** @deprecated */
public static final int LOCATION_TYPE_FAST = 3;
public static final int LOCATION_TYPE_FIX_CACHE = 4;
public static final int LOCATION_TYPE_WIFI = 5;
public static final int LOCATION_TYPE_CELL = 6;
public static final int LOCATION_TYPE_AMAP = 7;
public static final int LOCATION_TYPE_OFFLINE = 8;
public static final int LOCATION_TYPE_LAST_LOCATION_CACHE = 9;
public static final int GPS_ACCURACY_GOOD = 1;
public static final int GPS_ACCURACY_BAD = 0;
public static final int GPS_ACCURACY_UNKNOWN = -1;
private String d = "";
private String e = "";
private String f = "";
private String g = "";
private String h = "";
private String i = "";
private String j = "";
private String k = "";
private String l = "";
private String m = "";
private String n = "";
private boolean o = true;
private int p = 0;
private String q = "success";
private String r = "";
private int s = 0;
private double t = 0.0D;
private double u = 0.0D;
private int v = 0;
private String w = "";
private int x = -1;
private boolean y = false;
private String z = "";
private boolean A = false;
protected String a = "";
protected String b = "";
public static final Creator CREATOR = new Creator() {
};
AMapLocationQualityReport c = new AMapLocationQualityReport();
public int getGpsAccuracyStatus() {
return this.x;
}
public void setGpsAccuracyStatus(int var1) {
this.x = var1;
}
public AMapLocation(String var1) {
super(var1);
}
public AMapLocation(Location var1) {
super(var1);
this.t = var1.getLatitude();
this.u = var1.getLongitude();
}
public int getLocationType() {
return this.s;
}
public void setLocationType(int var1) {
this.s = var1;
}
public String getLocationDetail() {
return this.r;
}
public void setLocationDetail(String var1) {
this.r = var1;
}
public int getErrorCode() {
return this.p;
}
public void setErrorCode(int var1) {
if(this.p == 0) {
this.q = dd.b(var1);
this.p = var1;
}
}
public String getErrorInfo() {
StringBuilder var1;
(var1 = new StringBuilder()).append(this.q);
if(this.p != 0) {
var1.append(" 请到http://lbs.amap.com/api/android-location-sdk/guide/utilities/errorcode/查看错误码说明");
var1.append(",错误详细信息:" + this.r);
}
return var1.toString();
}
public void setErrorInfo(String var1) {
this.q = var1;
}
public String getCountry() {
return this.k;
}
public void setCountry(String var1) {
this.k = var1;
}
/** @deprecated */
public String getRoad() {
return this.l;
}
/** @deprecated */
public void setRoad(String var1) {
this.l = var1;
}
public String getAddress() {
return this.i;
}
public void setAddress(String var1) {
this.i = var1;
}
public String getProvince() {
return this.d;
}
public void setProvince(String var1) {
this.d = var1;
}
public String getCity() {
return this.e;
}
public void setCity(String var1) {
this.e = var1;
}
public String getDistrict() {
return this.f;
}
public void setDistrict(String var1) {
this.f = var1;
}
public String getCityCode() {
return this.g;
}
public void setCityCode(String var1) {
this.g = var1;
}
public String getAdCode() {
return this.h;
}
public void setAdCode(String var1) {
this.h = var1;
}
public String getPoiName() {
return this.j;
}
public void setPoiName(String var1) {
this.j = var1;
}
public double getLatitude() {
return this.t;
}
public void setLatitude(double var1) {
this.t = var1;
}
public double getLongitude() {
return this.u;
}
public void setLongitude(double var1) {
this.u = var1;
}
public int getSatellites() {
return this.v;
}
public void setSatellites(int var1) {
this.v = var1;
}
public String getStreet() {
return this.m;
}
public void setStreet(String var1) {
this.m = var1;
}
public String getStreetNum() {
return this.n;
}
public void setNumber(String var1) {
this.n = var1;
}
public void setOffset(boolean var1) {
this.o = var1;
}
public boolean isOffset() {
return this.o;
}
public String getAoiName() {
return this.w;
}
public void setAoiName(String var1) {
this.w = var1;
}
public String getBuildingId() {
return this.a;
}
public void setBuildingId(String var1) {
this.a = var1;
}
public String getFloor() {
return this.b;
}
public boolean isFixLastLocation() {
return this.A;
}
public void setFixLastLocation(boolean var1) {
this.A = var1;
}
public void setFloor(String var1) {
if(!TextUtils.isEmpty(var1)) {
var1 = var1.replace("F", "");
try {
Integer.parseInt(var1);
} catch (Throwable var3) {
var1 = null;
cv.a(var3, "AmapLoc", "setFloor");
}
}
this.b = var1;
}
public boolean isMock() {
return this.y;
}
public void setMock(boolean var1) {
this.y = var1;
}
public String getDescription() {
return this.z;
}
public void setDescription(String var1) {
this.z = var1;
}
public String toString() {
StringBuffer var1 = new StringBuffer();
try {
var1.append("latitude=" + this.t + "#");
var1.append("longitude=" + this.u + "#");
var1.append("province=" + this.d + "#");
var1.append("city=" + this.e + "#");
var1.append("district=" + this.f + "#");
var1.append("cityCode=" + this.g + "#");
var1.append("adCode=" + this.h + "#");
var1.append("address=" + this.i + "#");
var1.append("country=" + this.k + "#");
var1.append("road=" + this.l + "#");
var1.append("poiName=" + this.j + "#");
var1.append("street=" + this.m + "#");
var1.append("streetNum=" + this.n + "#");
var1.append("aoiName=" + this.w + "#");
var1.append("poiid=" + this.a + "#");
var1.append("floor=" + this.b + "#");
var1.append("errorCode=" + this.p + "#");
var1.append("errorInfo=" + this.q + "#");
var1.append("locationDetail=" + this.r + "#");
var1.append("description=" + this.z + "#");
var1.append("locationType=" + this.s);
} catch (Throwable var2) {
;
}
return var1.toString();
}
public String toStr() {
return this.toStr(1);
}
public String toStr(int var1) {
JSONObject var4;
try {
var4 = this.toJson(var1);
} catch (Throwable var3) {
var4 = null;
cv.a(var3, "AMapLocation", "toStr part2");
}
return var4 == null?null:var4.toString();
}
public float getAccuracy() {
return super.getAccuracy();
}
public float getBearing() {
return super.getBearing();
}
public double getAltitude() {
return super.getAltitude();
}
public float getSpeed() {
return super.getSpeed();
}
public String getProvider() {
return super.getProvider();
}
public JSONObject toJson(int var1) {
JSONObject var2;
try {
var2 = new JSONObject();
switch(var1) {
case 1:
try {
var2.put("altitude", this.getAltitude());
var2.put("speed", (double)this.getSpeed());
var2.put("bearing", (double)this.getBearing());
} catch (Throwable var3) {
;
}
var2.put("citycode", this.g);
var2.put("adcode", this.h);
var2.put("country", this.k);
var2.put("province", this.d);
var2.put("city", this.e);
var2.put("district", this.f);
var2.put("road", this.l);
var2.put("street", this.m);
var2.put("number", this.n);
var2.put("poiname", this.j);
var2.put("errorCode", this.p);
var2.put("errorInfo", this.q);
var2.put("locationType", this.s);
var2.put("locationDetail", this.r);
var2.put("aoiname", this.w);
var2.put("address", this.i);
var2.put("poiid", this.a);
var2.put("floor", this.b);
var2.put("description", this.z);
case 2:
var2.put("time", this.getTime());
case 3:
var2.put("provider", this.getProvider());
var2.put("lon", this.getLongitude());
var2.put("lat", this.getLatitude());
var2.put("accuracy", (double)this.getAccuracy());
var2.put("isOffset", this.o);
var2.put("isFixLastLocation", this.A);
}
} catch (Throwable var4) {
var2 = null;
cv.a(var4, "AmapLoc", "toStr");
}
return var2;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel var1, int var2) {
super.writeToParcel(var1, var2);
var1.writeString(this.h);
var1.writeString(this.i);
var1.writeString(this.w);
var1.writeString(this.a);
var1.writeString(this.e);
var1.writeString(this.g);
var1.writeString(this.k);
var1.writeString(this.f);
var1.writeInt(this.p);
var1.writeString(this.q);
var1.writeString(this.b);
var1.writeInt(this.A?1:0);
var1.writeInt(this.o?1:0);
var1.writeDouble(this.t);
var1.writeString(this.r);
var1.writeInt(this.s);
var1.writeDouble(this.u);
var1.writeInt(this.y?1:0);
var1.writeString(this.n);
var1.writeString(this.j);
var1.writeString(this.d);
var1.writeString(this.l);
var1.writeInt(this.v);
var1.writeInt(this.x);
var1.writeString(this.m);
var1.writeString(this.z);
}
public AMapLocation clone() {
try {
super.clone();
} catch (Throwable var3) {
;
}
AMapLocation var1 = new AMapLocation(this);
try {
var1.setAdCode(this.h);
var1.setAddress(this.i);
var1.setAoiName(this.w);
var1.setBuildingId(this.a);
var1.setCity(this.e);
var1.setCityCode(this.g);
var1.setCountry(this.k);
var1.setDistrict(this.f);
var1.setErrorCode(this.p);
var1.setErrorInfo(this.q);
var1.setFloor(this.b);
var1.setFixLastLocation(this.A);
var1.setOffset(this.o);
var1.setLocationDetail(this.r);
var1.setLocationType(this.s);
var1.setMock(this.y);
var1.setNumber(this.n);
var1.setPoiName(this.j);
var1.setProvince(this.d);
var1.setRoad(this.l);
var1.setSatellites(this.v);
var1.setGpsAccuracyStatus(this.x);
var1.setStreet(this.m);
var1.setDescription(this.z);
var1.setExtras(this.getExtras());
if(this.c != null) {
var1.setLocationQualityReport(this.c.clone());
}
} catch (Throwable var2) {
cv.a(var2, "AMapLocation", "clone");
}
return var1;
}
public AMapLocationQualityReport getLocationQualityReport() {
return this.c;
}
public void setLocationQualityReport(AMapLocationQualityReport var1) {
if(var1 != null) {
this.c = var1;
}
}
}
代码量还是挺大的,这里有五百多行的代码量,其中有很多是我们不用关心的,我们只需要看关键性代码:
首先看一下AMapLocation 类中的变量,总共分为三部分:
public static final int LOCATION_SUCCESS = 0;
public static final int ERROR_CODE_INVALID_PARAMETER = 1;
public static final int ERROR_CODE_FAILURE_WIFI_INFO = 2;
public static final int ERROR_CODE_FAILURE_LOCATION_PARAMETER = 3;
public static final int ERROR_CODE_FAILURE_CONNECTION = 4;
public static final int ERROR_CODE_FAILURE_PARSER = 5;
public static final int ERROR_CODE_FAILURE_LOCATION = 6;
public static final int ERROR_CODE_FAILURE_AUTH = 7;
public static final int ERROR_CODE_UNKNOWN = 8;
public static final int ERROR_CODE_FAILURE_INIT = 9;
public static final int ERROR_CODE_SERVICE_FAIL = 10;
public static final int ERROR_CODE_FAILURE_CELL = 11;
public static final int ERROR_CODE_FAILURE_LOCATION_PERMISSION = 12;
public static final int ERROR_CODE_FAILURE_NOWIFIANDAP = 13;
public static final int ERROR_CODE_FAILURE_NOENOUGHSATELLITES = 14;
public static final int ERROR_CODE_FAILURE_SIMULATION_LOCATION = 15;
public static final int ERROR_CODE_AIRPLANEMODE_WIFIOFF = 18;
public static final int ERROR_CODE_NOCGI_WIFIOFF = 19;
这部分全部都是静态整型变量,很明显代表的是一种状态的标志,仔细看一下就会发现,他们名称首位都是“ERROR”开头,也就是说这里他们都是错误码,高德定义了这些错误码来代表相应的定位错误信息,以帮助开发者快速的找到定位失败的原因。具体的错误码对照表,高德的官方文档上有,这里不再多说。
第二部分:
public static final int LOCATION_TYPE_GPS = 1;
public static final int LOCATION_TYPE_SAME_REQ = 2;
/** @deprecated */
public static final int LOCATION_TYPE_FAST = 3;
public static final int LOCATION_TYPE_FIX_CACHE = 4;
public static final int LOCATION_TYPE_WIFI = 5;
public static final int LOCATION_TYPE_CELL = 6;
public static final int LOCATION_TYPE_AMAP = 7;
public static final int LOCATION_TYPE_OFFLINE = 8;
public static final int LOCATION_TYPE_LAST_LOCATION_CACHE = 9;
public static final int GPS_ACCURACY_GOOD = 1;
public static final int GPS_ACCURACY_BAD = 0;
public static final int GPS_ACCURACY_UNKNOWN = -1;
还是一堆的静态整型变量。这部分静态整型变量代表的是定位采用的方式以及各种相关定位设置,具体的详细介绍请参考官方开发文档。
下面是第三部分:
private String d = "";
private String e = "";
private String f = "";
private String g = "";
private String h = "";
private String i = "";
private String j = "";
private String k = "";
private String l = "";
private String m = "";
private String n = "";
private boolean o = true;
private int p = 0;
private String q = "success";
private String r = "";
private int s = 0;
private double t = 0.0D;
private double u = 0.0D;
private int v = 0;
private String w = "";
private int x = -1;
private boolean y = false;
private String z = "";
private boolean A = false;
protected String a = "";
protected String b = "";
这部分就不是静态变量了,说明他们代表的就不是一种标志或者设置,而是真正的变量值,没错这些变量都是用来存放相关定位数据的!估计你此时心里是崩溃的,这些d,e,f,g,h,i,j,k,l,m,,,,,,,,,这些英文字母是什么鬼!完全摸不着头脑有没有!不用英文翻译起码给个注释啊喂!连一个注释都没有!简直是坑爹!
这里说一下,平时我们写代码尽量使用英文翻译,而不是这种英文字母来定义声明,要不然你回头维护代码的时候是非常痛苦的!好,平复下我们的心情还是继续分析代码,我们这里重点关注的是经度和纬度数据,虽然源码中并没有给出相应的注释,不过我们可以猜得到,经度和纬度都是double类型的变量,我们看下谁是double型的,很快就找到下面两个变量:
private double t = 0.0D;
private double u = 0.0D;
变量 t 和变量 u 。就只有他两个是double型的,那么估计经纬度就是他们两个表示的了,只不过不清楚谁是经度谁是纬度。没关系,我们可以往下看代码,重点关注变量 t 和变量 u 出现的地方。很快我们就发现了:
public AMapLocation(Location var1) {
super(var1);
this.t = var1.getLatitude();
this.u = var1.getLongitude();
}
这是AMapLocation类的构造函数,我们可以很清楚的看到,在构造函数里,我们传入一个Location 类的对象,然后分别调用了Location 类的getLatitude()方法和getLongitude()方法为变量 t 和变量 u 赋值!
我们知道,Location 类的getLatitude()方法获取的是纬度,getLongitude()方法获取的是经度,那么这里我们基本就可以确定,变量 t 代表的是纬度,变量 u 代表的是经度,我们可以继续向下看一下 t 和 u 的set和get方法:
public double getLatitude() {
return this.t;
}
public void setLatitude(double var1) {
this.t = var1;
}
public double getLongitude() {
return this.u;
}
public void setLongitude(double var1) {
this.u = var1;
}
对照高德官方开发文档,我们发现猜测是对的。这里说一下,为什么这里还会有set方法呢,我们可以看一下AMapLocation类的继承关系:
public class AMapLocation extends Location implements Parcelable, Cloneable
原来是继承于Location类,AMapLocation类中经纬度的set和get方法实质上是对父类Location类中经纬度set和get方法的重写,这里我们顺便去看一下,Location类中经纬度set和get方法:
/**
* Get the latitude, in degrees.
*
* All locations generated by the {@link LocationManager}
* will have a valid latitude.
*/
public double getLatitude() {
return mLatitude;
}
/**
* Set the latitude, in degrees.
*/
public void setLatitude(double latitude) {
mLatitude = latitude;
}
/**
* Get the longitude, in degrees.
*
*
All locations generated by the {@link LocationManager}
* will have a valid longitude.
*/
public double getLongitude() {
return mLongitude;
}
/**
* Set the longitude, in degrees.
*/
public void setLongitude(double longitude) {
mLongitude = longitude;
}
我们看到,在Location类中,只是进行了简单的赋值返回操作,这里没什么可说的。
好了,现在我们分析源码完毕,并且也知道了关键方法是获取经纬度,那么下面我们就来拦截修改经纬度来改变高德地图App的位置定位吧!
首先你可能会想到,既然需要拦截的关键方法是经纬度 ,那么进行拦截,目标类是AMapLocation 类,目标方法有两个,分别是获取纬度方法getLatitude(),获取经度方法getLongitude()。这里我将会告诉你,这是不对的,因为首先你要知道,AMapLocation类是高德地图SDK中的方法,并不是Android系统所提供的方法,拦截起来难度是很大的,很容易就会出错。
所以在这里,我们不能拦截AMapLocation类,而是拦截Location类。为什么?刚才在我们源码分析中,我们知道,AMapLocation类中的经度和纬度赋值是在构造函数里面,通过调用Location类的获取经度和纬度的方法来赋值;再者Location类是Android系统原生的,拦截起来比较方便和简单。所以这里修改我们的目标类为Location类,目标方法为获取纬度方法getLatitude(),获取经度方法getLongitude()。
下面就开始我们的拦截吧,代码如下:
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
XposedBridge.log("loaded: " + loadPackageParam.packageName);
XposedBridge.log("开始劫持");
if (loadPackageParam.packageName.equals("com.autonavi.minimap")) {
XposedBridge.log("进入");
setHookMethodAndClassGaoDeLo();
setHookMethodAndClassGaoDeLa();
}
private void setHookMethodAndClassGaoDeLo(){//经度
XposedHelpers.findAndHookMethod(Location.class, "getLongitude", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("进入,开始修改经度坐标");
param.setResult(115.193721);
}
});
}
private void setHookMethodAndClassGaoDeLa(){//纬度
XposedHelpers.findAndHookMethod(Location.class, "getLatitude", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
XposedBridge.log("进入,开始修改纬度坐标");
param.setResult(33.655748);
}
});
}
首先我们先做了一个应用包名的判断,发现是高德地图App的应用包就进入开始拦截,高德地图App的应用包名为:com.autonavi.minimap。
下面就是开启了两个钩子方法,分别钩取getLongitude()方法和getLatitude()方法,设置返回结果为我们修改的经纬度,这里我修改的经纬度为我家乡的经纬度。我本人目前是在北京工作。
下面就让我们运行一下程序,打开高德地图App看看是否定位地址修改成功:
这是没有拦截前,显示我现在的地址,位于北京石景山:
开启拦截,再次使用高德地图App定位:
发现定位地址发生了改变,定位显示我处在我的家乡,河南省郸城县
还可以探索一下我们的附近:
搜索附近的美食,发现已经换成了家乡!至此说明,我们确实成功的拦截并修改了高德地图App的定位信息。
好了,文章到此结束,有不明白的地方请留言,需要引用的请标明出处,谢谢!