修改Android定位

更新 【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,右边是百度地图):

  修改Android定位_第1张图片

 

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。

你可能感兴趣的:(Java,Android)