更新 【2019.05.14】
Version 1.9.5测试版
1.开发环境换成了Android 9,增加一些Android 9的适配
2.修改Android 9中获取位置详情失败的bug
3.删除了获取IMEI的权限
4.修改EMUI9.1的存储权限获取bug(然后我想到如果不给存储权限的话,这个app就直接崩了...)
5.增强了一下稳定性???我也不是很确定,不太好测试,想试一下的可以去下载
6.增加运行日志记录,目录为 你的手机/MockGPS/Log/xxx.log,这个是为了方便bug反馈和调试。有好几个人反应说定位来回跳,但我死活重现不了。所以加上这个运行日志记录,看看能不能找到问题在哪。但是我还没服务器,这个log文件怎么传给我还没想好,先改这些吧有空再说
下载安装包:https://github.com/Hilaver/MockGPS/blob/master/app/release/MockGPS_v1.9.5.190604_alpha.apk
下完能用给个star哇
---------------------------------------------------------------------我是一条分割线------------------------------------------------------------------
更新 【2019.05.05】
Version1.9.4:
GitHub:https://github.com/Hilaver/MockGPS
修改了几个bug,然后添加了一个手动输入经纬度定位的功能。
没闲钱买服务器啊,所以app里没有推送更新的选项。
下载安装包:https://github.com/Hilaver/MockGPS/blob/master/app/release/MockGPS_v1.9.4.190219_beta.apk
下完能用给个star哇
想起了先前遗留的一个问题:国外一些区域的坐标会定位失败,国内以及周边是OK的,不知道有没有人遇到过这样的情况,有点费解呢。
---------------------------------------------------------------------我是一条分割线------------------------------------------------------------------
更新 【2018.12.5】
声明:
请尊重他人劳动成果,本app现阶段免费,不允许向他人出售该软件,否则...
---------------------------------------------------------------------我是一条分割线------------------------------------------------------------------
更新 【2018.11.16】
如果有人在用这个app的话,麻烦在评论里留句话,没什么人的话我就不再更改代码了,因为接下来毕业困难户要开始忙着发论文了...
---------------------------------------------------------------------我是一条分割线------------------------------------------------------------------
需求来自于王大宝在的成都CH公司强制加班且每天上下班打卡,要在公司200m范围内才能签到成功。王大宝多机智(懒)啊,于是立马想到能不能修改手机定位去打卡,问我我也不知道啊,只能试试了。
用了几个晚上的时间各种Google百度,又用了好几个晚上写了一个欺骗定位的Demo,粗略试了一下,能骗过王大宝公司的打卡软件,还能骗过百度地图和高德地图,可能她们的打卡软件就是用了其中一个的SDK吧。但是无法作用于腾讯的系列产品,比如腾讯地图,QQ和微信等,可否有人指点一二?
项目放到GitHub啦:https://github.com/Hilaver/MockGPS
签名安装包也传到上面了
手机定位一共有三种方式:GPS定位、基站定位和WIFI定位。其中GPS定位的精度是最高的,基站定位的误差最大。基站定位和WIFI定位统称为网络定位。GPS定位需要使用Android自带的LocationManager实现,安卓系统下的LocationManager一共有四个位置提供者,分别是NETWORK_PROVIDER【网络定位提供者】、GPS_PROVIDER【GPS定位提供者】、PASSIVE_PROVIDER【被动模式定位提供者】和FUSED_PROVIDER【混合模式提供者,被隐藏了】。我所做的,就是移除原有的网络位置提供者,并添加一个新的网络位置提供者,然后向该提供者不断提供虚假的经纬度信息。你可能要问了,为什么是NETWORK_PROVIDER而不是GPS_PROVIDER,Emmmm,这是因为这两个都尝试过之后发现修改网络位置提供者才有效。。。
有了思路之后代码就比较好写了,只是经常会掉进坑里,往外爬的过程还是有点痛苦的。
把修改GPS定位的模块写成一个Service,在这个Service中开一个线程不断刷新修改后的位置。
现在的效果图
(左边是我的app,右边是百度地图):
1.获取LocationManager
locationManager=(LocationManager)this.getSystemService(Context.LOCATION_SERVICE);
2.移除原有的NETWORK_PROVIDER
//remove network provider
private void rmNetworkProvider(){
try {
String providerStr = LocationManager.NETWORK_PROVIDER;
if (locationManager.isProviderEnabled(providerStr)){
Log.d(TAG, "now remove NetworkProvider");
// locationManager.setTestProviderEnabled(providerStr,true);
locationManager.removeTestProvider(providerStr);
}
}catch (Exception e){
e.printStackTrace();
Log.d(TAG, "rmNetworkProvider error");
}
}
3.添加一个新的NETWORK_PROVIDER
//set new network provider
private void setNewNetworkProvider(){
String providerStr = LocationManager.NETWORK_PROVIDER;
try {
locationManager.addTestProvider(providerStr, false, false,
false, false, false, false,
false, 1, Criteria.ACCURACY_FINE);
Log.d(TAG,"addTestProvider[network] success");
// locationManager.setTestProviderStatus("network", LocationProvider.AVAILABLE, null,
// System.currentTimeMillis());
}catch (SecurityException e){
Log.d(TAG,"setNewNetworkProvider error");
}
if (!locationManager.isProviderEnabled(providerStr)){
Log.d(TAG, "now setTestProviderEnabled[network]");
locationManager.setTestProviderEnabled(providerStr,true);
}
}
4.向这个新添加的provider提供虚假的位置信息
//set network location
private void setNetworkLocation() {
//default location 30.5437233 104.0610342 成都长虹科技大厦
LatLng latLng = new LatLng(Double.valueOf("你的纬度"), Double.valueOf("你的经度"));
String providerStr = LocationManager.NETWORK_PROVIDER;
try {
locationManager.setTestProviderLocation(providerStr, generateLocation(latLng));
} catch (Exception e) {
Log.d(TAG, "setNetworkLocation error");
e.printStackTrace();
}
}
//generate a location
public Location generateLocation(LatLng latLng) {
Location loc = new Location("gps");
loc.setAccuracy(2.0F);
loc.setAltitude(55.0D);
loc.setBearing(1.0F);
Bundle bundle = new Bundle();
bundle.putInt("satellites", 7);
loc.setExtras(bundle);
loc.setLatitude(latLng.latitude);
loc.setLongitude(latLng.longitude);
loc.setTime(System.currentTimeMillis());
if (Build.VERSION.SDK_INT >= 17) {
loc.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
return loc;
}
5.最后就是开一个线程,不断调用setNetworkLocation就好啦,我是这么写的
//thread
handlerThread=new HandlerThread(getUUID(),-2);
handlerThread.start();
handler=new Handler(handlerThread.getLooper()){
public void handleMessage(Message msg){
try {
Thread.sleep(333);
if (!isStop){
setNetworkLocation();
sendEmptyMessage(0);
//broadcast to MainActivity
Intent intent=new Intent();
intent.putExtra("statusCode", RunCode);
intent.setAction("com.example.service.MockGpsService");
sendBroadcast(intent);
}
} catch (InterruptedException e) {
e.printStackTrace();
Log.d(TAG, "setNetworkLocation error");
Thread.currentThread().interrupt();
}
}
};
handler.sendEmptyMessage(0);
1.生成Location
在第4步生成Location的时候,需要进行SDK版本的判断,注意函数setElapsedRealtimeNanos,我在这里crash了好多次。
2.使用百度地图SDK的经纬度问题
GPS定位使用的是国际通用的WGS84坐标系标准,但是在国内,坐标系采用GCJ02标准,是对WGS84标准的一种加密,与原经纬度相比会产生一个非线性偏移,而百度地图定位SDK使用的是自己的标准BD09(国外直接采用了WGS84),是百度在GCJ02标准上的又一次加密。因此这个过程中的坐标转化是必须完成的。至于怎么转换,网上有呀。下面是我找到的代码:
//坐标转换相关
static double pi = 3.14159265358979324;
static double a = 6378245.0;
static double ee = 0.00669342162296594323;
public final static double x_pi = 3.14159265358979324 * 3000.0 / 180.0;
public static double[] bd2wgs(double lon, double lat) {
double[] bd2Gcj = bd09togcj02(lon, lat);
return gcj02towgs84(bd2Gcj[0], bd2Gcj[1]);
}
public static double[] bd09togcj02(double bd_lon, double bd_lat) {
double x = bd_lon - 0.0065;
double y = bd_lat - 0.006;
double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);
double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
double gg_lng = z * Math.cos(theta);
double gg_lat = z * Math.sin(theta);
return new double[] { gg_lng, gg_lat };
}
public static double[] gcj02towgs84(double lng, double lat) {
// if (out_of_china(lng, lat)) {
// return new double[] { lng, lat };
// }
double dlat = transformLat(lng - 105.0, lat - 35.0);
double dlng = transformLon(lng - 105.0, lat - 35.0);
double radlat = lat / 180.0 * pi;
double magic = Math.sin(radlat);
magic = 1 - ee * magic * magic;
double sqrtmagic = Math.sqrt(magic);
dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi);
dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * pi);
double mglat = lat + dlat;
double mglng = lng + dlng;
return new double[] { lng * 2 - mglng, lat * 2 - mglat };
}
private static double transformLat(double lat, double lon) {
double ret = -100.0 + 2.0 * lat + 3.0 * lon + 0.2 * lon * lon + 0.1 * lat * lon + 0.2 * Math.sqrt(Math.abs(lat));
ret += (20.0 * Math.sin(6.0 * lat * pi) + 20.0 * Math.sin(2.0 * lat * pi)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lon * pi) + 40.0 * Math.sin(lon / 3.0 * pi)) * 2.0 / 3.0;
ret += (160.0 * Math.sin(lon / 12.0 * pi) + 320 * Math.sin(lon * pi / 30.0)) * 2.0 / 3.0;
return ret;
}
private static double transformLon(double lat, double lon) {
double ret = 300.0 + lat + 2.0 * lon + 0.1 * lat * lat + 0.1 * lat * lon + 0.1 * Math.sqrt(Math.abs(lat));
ret += (20.0 * Math.sin(6.0 * lat * pi) + 20.0 * Math.sin(2.0 * lat * pi)) * 2.0 / 3.0;
ret += (20.0 * Math.sin(lat * pi) + 40.0 * Math.sin(lat / 3.0 * pi)) * 2.0 / 3.0;
ret += (150.0 * Math.sin(lat / 12.0 * pi) + 300.0 * Math.sin(lat / 30.0 * pi)) * 2.0 / 3.0;
return ret;
}
如果你真的要用这个代码,最难受的不是这个看似投机取巧的欺骗过程,而是之后要实现的进程保活,Android Oreo为了节约电量,会杀掉未经处理的后台进程,具体怎么实现保活就不在这里写了,网上也有很多实现思路,但是很多都失效了。
最后,这种方法是没法修改基站定位的,比如QQ和微信,如果你的打卡软件用的是基站定位,那么这个代码无能为力了,但是提供一个思路:利用Xposed去hook底层的API,这个是有人做过的,但是需要root权限。类似的还有不需要root权限的VirtualXposed。