关于Android(安卓)Studio使用开发者允许模拟位置欺骗gps
本文固定连接,转载请留言
关于Android Studio使用开发者允许模拟位置欺骗GPS
引言:五一刚过,疫情稍微稳定了一些。仍然没有回老家给先祖拜年,博文叩拜先祖(因为疫情,过年没有回家)。结果一翻开手机朋友圈,各种刷位置和旅游的。说实话,这群人也就赶上了好时候,哆哆嗦嗦的就跑出去浪。浪就浪,生怕其他人不知道你浪。于是,突发奇想,想自己做个android的app(读艾坡),显摆不能靠运费,得靠实力。于是就有了这篇博文,关于怎么做这个事情的过程。
一、准备工作
准备肯定是安装android的开发工具,诸如java、eclipse/android studio、android sdk之类的。这个网上有。不介绍了。
因为我是做Unity开发的,做了Unity开发八九年了,所以有一点点android的基础,什么java之类的开发环境应该算是早就有了。不过之前一直是选择的eclipse作为Unity辅助开发的工具。但是现在eclipse好像没有那么火了。于是用了android studio。
二、应该具备什么功能?
应该具备什么功能这个话题很宽。一开始就是像试试能不能随便输入个地址(经纬度)改成功就行。这个后来发现太容易了。
于是,觉得应该有个跟地图有关的功能,比如,点击地图位置,直接定位到目标位置。这个搞了几天。因为不是很熟悉。
最终决定,使用高德地图SDK,打开地图后点击位置,自动模拟位置。
三、一个字——干
说干就干,首先是高德SDK相关资料。地址如下:
高德地图Android SDK地址
按照高德地图SDK的要求,配置好相关要求以及权限(具体看连接地址)这里简短介绍下AndroidManifest.xml,如下xml:
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.cf.gps"> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission..ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/> <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" tools:ignore="MockLocation"/> <!-- <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" tools:ignore="MockLocation"/>--> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <meta-data android:name="com.amap.api.v2.apikey" android:value="这里是申请的高德sdk key"/> <service android:name="com.amap.api.location.APSService" ></service> </application></manifest>
涉及到的具体权限我就不解释了。
申请key的过程SDK里也有。如果发现你填的key不对,在使用sdk的时候会报错。对应会有打印,打印内容里会给出你应该填的正确的key,你留意logcat,反正一开始我就是这么干的。我真机智。
注意,这个manifest是debug的,如图:
同时,在build.gradle中添加对应的引用库,注意,这个地方我引用了一个permissionhelp的库,这个是国外人写的,用来申请权限的。
如图:
注意是app这个module,不是project。内容如下:
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'com.master.android:permissionhelper:2.0' //implementation 'com.amap.api:3dmap:latest.integration' implementation 'com.amap.api:map2d:6.0.0' implementation 'com.amap.api:search:7.3.0' implementation 'com.amap.api:location:4.9.0' implementation 'com.google.android.material:material:1.1.0' testImplementation 'junit:junit:4.13' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'}
界面设计如下:
当然,后续你会发现,到我写这个博文的时候这个界面为什么这么丑。
MainActivity如下:
package com.cf.gps;import android.Manifest;import android.content.Context;import android.content.Intent;import android.content.pm.PackageManager;import android.location.Location;import android.location.LocationListener;import android.location.LocationManager;import android.os.Bundle;import android.text.Editable;import android.text.TextWatcher;import android.util.Log;import android.view.Gravity;import android.view.View;import android.widget.Button;import android.widget.ListView;import android.widget.TextView;import android.widget.Toast;import androidx.annotation.NonNull;import androidx.appcompat.app.AppCompatActivity;import androidx.core.app.ActivityCompat;import com.amap.api.location.AMapLocation;import com.amap.api.location.AMapLocationClient;import com.amap.api.location.AMapLocationClientOption;import com.amap.api.location.AMapLocationListener;import com.amap.api.services.core.PoiItem;import com.amap.api.services.poisearch.PoiResult;import com.amap.api.services.poisearch.PoiSearch;import com.master.permissionhelper.PermissionHelper;import java.util.List;public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private static final String GPS_LOCATION_NAME = android.location.LocationManager.GPS_PROVIDER; private LocationManager locationManager; //打开高德地图 private Button btn_openGD; //改变经纬度 private Button btnChangeLocation; //打开权限 private Button btn_openP; //高德获得的纬度 private TextView gd_wdTextView; //高德获得的经度 private TextView gd_jdTextView; //本地获得的纬度 private TextView self_wdTextView; //本地获得的经度 private TextView self_jdTextView; //权限检测类 private PermissionHelper mPermissionHelper; //声明AMapLocationClient类对象 public AMapLocationClient mLocationClient = null; //声明AMapLocationClientOption对象 public AMapLocationClientOption mLocationOption = null; private AMapLocationClientOption option; private Bundle mSavedInstanceState; //搜索 private ListView mSearchListView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSavedInstanceState=savedInstanceState; setContentView(R.layout.activity_main); initViews(); initData(); initMap(); } /** * 方法描述:初始化View组件信息及相关点击事件 */ private void initViews() { //打开高德地图 btn_openGD=(Button)findViewById(R.id.btn_openGD); btn_openGD.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ShowToast("btn_openGD onClick begin"); Intent intent = new Intent(MainActivity.this, GDMapActivity.class); startActivity(intent); } }); //改变经纬度按钮 btnChangeLocation = (Button) findViewById(R.id.btn_changeLocation); btnChangeLocation.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ShowToast("btn_changeLocation"); } }); btn_openP=(Button)findViewById(R.id.btn_openP); btn_openP.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ShowToast("btn_openP"); initProvider(); } }); //高德纬度 gd_wdTextView=(TextView)findViewById(R.id.gd_wdTextView); gd_wdTextView.setText(""); //高德经度 gd_jdTextView=(TextView)findViewById(R.id.gd_jdTextView); gd_jdTextView.setText(""); //高德经度 self_wdTextView=(TextView)findViewById(R.id.self_wdTextView); self_wdTextView.setText(""); //高德经度 self_jdTextView=(TextView)findViewById(R.id.self_jdTextView); self_jdTextView.setText(""); //搜索里面的关键字 final TextView searchTextView=(TextView)findViewById(R.id.searchTextView); searchTextView.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { //内容发生了变化,对应的ListView搜索内容也应该改变 DoSearch(s.toString()); } @Override public void afterTextChanged(Editable s) { } }); //搜索按钮 Button searchBtn=(Button)findViewById(R.id.searchBtn); searchBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { DoSearch(searchTextView.getText().toString()); } }); } private void DoSearch(String keyword) { //执行搜索的内容。 if(!AMapUtil.IsEmptyOrNullString(keyword)) {// PoiSearch poiSearch =new PoiSearch(keyword,"");//最后一个字符空字符串,空字符串代表全国在全国范围内进行搜索// poiSearch.setOnPoiSearchListener(new PoiSearch.OnPoiSearchListener() {// @Override// public void onPoiSearched(PoiResult poiResult, int i) {//// }//// @Override// public void onPoiItemSearched(PoiItem poiItem, int i) {//// }// });// poiSearch.searchPOIAsyn(); } else { ShowToast("请输入需要搜索内容"); } } /** * 方法描述:初始化定位相关数据 */ private void initData() { //获取定位服务 locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); //位置提供器,也就是实际上来定位的对象,这里选择的是GPS定位 String locationProvider=null; //获取手机中开启的位置提供器 List<String> providers=locationManager.getProviders(true); if(providers.contains(locationManager.GPS_PROVIDER)) { locationProvider=locationManager.GPS_PROVIDER;//GPS定位 }else if(providers.contains(locationManager.NETWORK_PROVIDER)) { locationProvider=locationManager.NETWORK_PROVIDER;//网络定位 } if(locationProvider!=null) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return ; } Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider); if(lastKnownLocation!=null)refreshSelfLocation(lastKnownLocation); locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, new LocationListener() { @Override public void onLocationChanged(Location location) { refreshSelfLocation(location); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } }); } } private void initMap() { //初始化定位 mLocationClient = new AMapLocationClient(this.getApplicationContext()); mLocationOption=getDefaultOption(); //给定位客户端对象设置定位参数 mLocationClient.setLocationOption(mLocationOption); //设置定位回调监听 mLocationClient.setLocationListener(new AMapLocationListener() { @Override public void onLocationChanged(AMapLocation aMapLocation) { if (null != aMapLocation) { if(aMapLocation.getErrorCode() == 0) { refreshGDLocation(aMapLocation); } } } }); //启动定位 mLocationClient.startLocation(); } private void initProvider() { //初始化权限获取类 mPermissionHelper = new PermissionHelper(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, 100); //请求权限 mPermissionHelper.request(new PermissionHelper.PermissionCallback() { @Override public void onPermissionGranted() { Log.d("mPermissionHelper", "onPermissionGranted() called"); ShowToast("onPermissionGranted"); } @Override public void onIndividualPermissionGranted(String[] grantedPermission) { Log.d("mPermissionHelper", "onIndividualPermissionGranted() called with: grantedPermission "); ShowToast("onIndividualPermissionGranted"); } @Override public void onPermissionDenied() { Log.d("mPermissionHelper", "onPermissionDenied() called"); ShowToast("onPermissionDenied"); } @Override public void onPermissionDeniedBySystem() { Log.d("mPermissionHelper", "onPermissionDeniedBySystem() called"); ShowToast("onPermissionDeniedBySystem"); } }); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (mPermissionHelper != null) { mPermissionHelper.onRequestPermissionsResult(requestCode, permissions, grantResults); } } private void refreshSelfLocation(Location location) { //获取纬度 Double latitude=location.getLatitude(); //获取经度 Double longitude = location.getLongitude(); Log.e("refreshSelfLocation", String.valueOf(latitude)); Log.e("refreshSelfLocation", String.valueOf(longitude)); self_wdTextView.setText(String.valueOf(latitude)); self_jdTextView.setText(String.valueOf(longitude)); } private void refreshGDLocation(Location location) { //获取纬度 Double latitude=location.getLatitude(); //获取经度 Double longitude = location.getLongitude(); Log.e("refreshGDLocation", String.valueOf(latitude)); Log.e("refreshGDLocation", String.valueOf(longitude)); gd_wdTextView.setText(String.valueOf(latitude)); gd_jdTextView.setText(String.valueOf(longitude)); } private void ShowToast(String content) { Toast t=Toast.makeText(MainActivity.this,content,Toast.LENGTH_SHORT); t.setGravity(Gravity.CENTER,0,0); t.show(); Log.d(TAG,content); } /** * 默认的定位参数 * @since 2.8.0 * @author hongming.wang * */ private AMapLocationClientOption getDefaultOption(){ AMapLocationClientOption mOption = new AMapLocationClientOption(); mOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);//可选,设置定位模式,可选的模式有高精度、仅设备、仅网络。默认为高精度模式 mOption.setGpsFirst(true);//可选,设置是否gps优先,只在高精度模式下有效。默认关闭 mOption.setHttpTimeOut(30000);//可选,设置网络请求超时时间。默认为30秒。在仅设备模式下无效 mOption.setInterval(2000);//可选,设置定位间隔。默认为2秒 mOption.setNeedAddress(true);//可选,设置是否返回逆地理地址信息。默认是true mOption.setOnceLocation(false);//可选,设置是否单次定位。默认是false mOption.setOnceLocationLatest(false);//可选,设置是否等待wifi刷新,默认为false.如果设置为true,会自动变为单次定位,持续定位时不要使用 AMapLocationClientOption.setLocationProtocol(AMapLocationClientOption.AMapLocationProtocol.HTTP);//可选, 设置网络请求的协议。可选HTTP或者HTTPS。默认为HTTP mOption.setSensorEnable(false);//可选,设置是否使用传感器。默认是false mOption.setWifiScan(true); //可选,设置是否开启wifi扫描。默认为true,如果设置为false会同时停止主动刷新,停止以后完全依赖于系统刷新,定位位置可能存在误差 mOption.setLocationCacheEnable(true); //可选,设置是否使用缓存定位,默认为true mOption.setGeoLanguage(AMapLocationClientOption.GeoLanguage.DEFAULT);//可选,设置逆地理信息的语言,默认值为默认语言(根据所在地区选择语言) return mOption; }}
其实没有太多的功能,首先是初始化界面相关。一个是初始化本地定位功能。一个是高德地图相关。
内容不做过多的赘述。我注释写那么多,还需要解释你太懒了。
在初始化view,也就是initViews方法的时候,按钮打开高德地图其实是打开了 一个新的activity,如图:
activity的xml如下:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.amap.api.maps2d.MapView android:id="@+id/map" android:layout_width="match_parent" android:layout_height="match_parent"></com.amap.api.maps2d.MapView> <ScrollView android:layout_width="match_parent" android:layout_height="60dp" android:layout_alignParentBottom="true" android:background="#D999"> <TextView android:id="@+id/tv_result" android:layout_width="match_parent" android:layout_height="wrap_content" /> </ScrollView> </RelativeLayout></LinearLayout>
这里其实后面的ScrollView里面的内容不重要了。可以去掉。重要的是代码:
<com.amap.api.maps2d.MapView android:id="@+id/map" android:layout_width="match_parent" android:layout_height="match_parent"></com.amap.api.maps2d.MapView>
这个其实是固定模式,相当于使用高德地图固定组件了。
生成类的时候顺带生成的另外一个xml文件 content.gdmap.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" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".GDMapActivity" tools:showIn="@layout/activity_gdmap"></androidx.constraintlayout.widget.ConstraintLayout>
整个目录结构如下:
GDMapActivity代码如下:
package com.cf.gps;import android.Manifest;import android.annotation.TargetApi;import android.content.Context;import android.location.Criteria;import android.location.Location;import android.location.LocationManager;import android.location.LocationProvider;import android.os.Build;import android.os.Bundle;import android.os.Handler;import android.provider.Settings;import android.util.Log;import android.view.Gravity;import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import com.amap.api.maps2d.AMap;import com.amap.api.maps2d.AMapOptions;import com.amap.api.maps2d.CameraUpdateFactory;import com.amap.api.maps2d.LocationSource;import com.amap.api.maps2d.MapView;import com.amap.api.maps2d.UiSettings;import com.amap.api.maps2d.model.BitmapDescriptorFactory;import com.amap.api.maps2d.model.LatLng;import com.amap.api.maps2d.model.Marker;import com.amap.api.maps2d.model.MarkerOptions;import com.amap.api.maps2d.model.MyLocationStyle;import com.master.permissionhelper.PermissionHelper;import org.jetbrains.annotations.NotNull;import java.util.ArrayList;import java.util.List;public class GDMapActivity extends AppCompatActivity{ //地图对象 private MapView mMapView = null; //高德里面的地图对象对应的属性 private AMap aMap=null; //蓝点样式 private MyLocationStyle myLocationStyle; //定义一个UiSettings对象 private UiSettings mUiSettings; //Marker的参数 private MarkerOptions markerOptions; //Marker对象 private Marker mClickMarker; //模拟地址管理类 private MockLocationManager mockLocationManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_gdmap); //获取地图控件引用 mMapView = (MapView) findViewById(R.id.map); //在activity执行onCreate时执行mMapView.onCreate(savedInstanceState),创建地图 mMapView.onCreate(savedInstanceState); //初始化地图控制器对象 if (aMap == null) { aMap = mMapView.getMap(); } //设置地图方式 InitMyLocationStyle(); //地图UISetting InitUISettings(); //地图的一些设置 InitAMap(); //模拟位置管理器 InitMockLocationManager(); //地图标记 InitMarker(); } //设置定位Style private void InitMyLocationStyle() { //初始化定位蓝点样式类myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);//连续定位、且将视角移动到地图中心点,定位点依照设备方向旋转,并且会跟随设备移动。(1秒1次定位)如果不设置myLocationType,默认也会执行此种模式。 myLocationStyle = new MyLocationStyle(); //设置连续定位模式下的定位间隔,只在连续定位模式下生效,单次定位模式下不会生效。单位为毫秒。 myLocationStyle.interval(500); //连续定位、蓝点不会移动到地图中心点,并且蓝点会跟随设备移动。 myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_FOLLOW_NO_CENTER); //设置是否显示定位小蓝点,用于满足只想使用定位,不想使用定位小蓝点的场景,设置false以后图面上不再有定位蓝点的概念,但是会持续回调位置信息。 myLocationStyle.showMyLocation(true); } private void InitUISettings() { //实例化UiSettings类对象 mUiSettings = aMap.getUiSettings(); //是否允许显示缩放按钮 mUiSettings.setZoomControlsEnabled(true); //展示地图方向 mUiSettings.setCompassEnabled(true); //显示默认的定位按钮 mUiSettings.setMyLocationButtonEnabled(true); //显示比例尺 mUiSettings.setScaleControlsEnabled(true); //显示高德logo,这个玩意不能隐藏只能挪位置,而且是固定的。 mUiSettings.setLogoPosition(AMapOptions.LOGO_POSITION_BOTTOM_LEFT); //所有手势 mUiSettings.setAllGesturesEnabled (true); } private void InitAMap() { //普通地图模式 aMap.setMapType(AMap.MAP_TYPE_NORMAL); // aMap.moveCamera(CameraUpdateFactory.zoomBy(6)); //设置定位蓝点的Style aMap.setMyLocationStyle(myLocationStyle); // 设置为true表示启动显示定位蓝点,false表示隐藏定位蓝点并不进行定位,默认是false。 aMap.setMyLocationEnabled(true); //位置变更 aMap.setOnMyLocationChangeListener(new AMap.OnMyLocationChangeListener() { @Override public void onMyLocationChange(Location location) { Log.d("onMyLocationChange:",location.getLatitude()+":"+location.getLongitude()); } }); //通过aMap对象设置定位数据源的监听 aMap.setLocationSource(new LocationSource() { @Override public void activate(OnLocationChangedListener onLocationChangedListener) { Log.d(GDMapActivity.class.getSimpleName(),"OnLocationChangedListener"); } @Override public void deactivate() { Log.d(GDMapActivity.class.getSimpleName(),"deactivate"); } }); } private void InitMarker() { //定义一个标记参数 markerOptions=new MarkerOptions(); //设置Marker可拖动 markerOptions.draggable(true); //点击地图 aMap.setOnMapClickListener(new AMap.OnMapClickListener() { @Override public void onMapClick(LatLng latLng) { //标记参数的位置 markerOptions.position(latLng); //如果点击标记还没有创建,新建一个。 if(mClickMarker==null) { markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)); mClickMarker=aMap.addMarker(markerOptions); } else { //否则更改位置,也就是说只有一个标记 mClickMarker.setPosition(latLng); } Log.d(GDMapActivity.class.getSimpleName(),"setOnMapClickListener "+latLng.toString()); //设置点击位置为模拟gps位置。 setMockLocation(latLng); //提示 ShowToast(latLng.toString()); } }); } private void InitMockLocationManager() { mockLocationManager=new MockLocationManager(); mockLocationManager.initService(getApplicationContext()); } //点击位置即是想要更改成的位置。 private void setMockLocation(@NotNull LatLng latLng) { if(mockLocationManager.getUseMockPosition(getApplicationContext())) { mockLocationManager.setLocationData(latLng.latitude+0.002715f,latLng.longitude-0.0051f);//这里有做修正,暂时不明白为什么会有差距。 startMockLocation(); mockLocationManager.startThread(); } ShowToast("setMockLocation"+latLng.toString()); } public void stopMockLocation() { mockLocationManager.bRun = false; mockLocationManager.stopMockLocation(); } public void startMockLocation() { mockLocationManager.bRun = true; } private void ShowToast(String content) { Toast t=Toast.makeText(GDMapActivity.this,content,Toast.LENGTH_SHORT); t.setGravity(Gravity.CENTER,0,0); t.show(); Log.d(GDMapActivity.class.getSimpleName(),content); } @Override protected void onDestroy() { super.onDestroy(); //在activity执行onDestroy时执行mMapView.onDestroy(),销毁地图 if(mMapView!=null)mMapView.onDestroy(); if(mockLocationManager!=null)mockLocationManager.stopMockLocation(); Log.d(GDMapActivity.class.getSimpleName(),"onDestroy"); }//// @Override// protected void onResume() {// super.onResume();// //在activity执行onResume时执行mMapView.onResume (),重新绘制加载地图// if(mMapView!=null)mMapView.onResume();// }//// @Override// protected void onPause() {// super.onPause();// //在activity执行onPause时执行mMapView.onPause (),暂停地图的绘制// if(mMapView!=null)mMapView.onPause();// }//// @Override// protected void onSaveInstanceState(Bundle outState) {// super.onSaveInstanceState(outState);// //在activity执行onSaveInstanceState时执行mMapView.onSaveInstanceState (outState),保存地图当前的状态// if(mMapView!=null)mMapView.onSaveInstanceState(outState);// }}
代码其实也没有什么好说的,注释写了很容易明白。看看就明白。这里我用了一个MockLocationManager的文件,
其内容是开启一个线程,在满足条件的情况下利用开发者模拟定位的方式设置gps坐标。
/** * 模拟位置线程 */ private class RunnableMockLocation implements Runnable { @Override public void run() { while (true) { try { Thread.sleep(1000); if (hasAddTestProvider == false) { continue; } if (bRun == false) { stopMockLocation(); continue; } try { // 模拟位置(addTestProvider成功的前提下) for (String providerStr : mockProviders) { Location mockLocation = new Location(providerStr); mockLocation.setLatitude(latitude); // 维度(度) mockLocation.setLongitude(longitude); // 经度(度) mockLocation.setAccuracy(0.1f); // 精度(米) mockLocation.setTime(new Date().getTime()); // 本地时间 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { mockLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); } locationManager.setTestProviderLocation(providerStr, mockLocation); } } catch (Exception e) { // 防止用户在软件运行过程中关闭模拟位置或选择其他应用 stopMockLocation(); } } catch (InterruptedException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } } } public void startThread() { if(mThread==null) { mThread=new Thread(new RunnableMockLocation()); mThread.start(); } }
当然,在模拟定位之前,你需要知道是否可以模拟位置,也就是模拟位置是否启用 ,代码如下:
/** * 模拟位置是否启用 * 若启用,则addTestProvider */ public boolean getUseMockPosition(Context context) { // Android 6.0以下,通过Setting.Secure.ALLOW_MOCK_LOCATION判断 // Android 6.0及以上,需要【选择模拟位置信息应用】,未找到方法,因此通过addTestProvider是否可用判断 boolean canMockPosition = (Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0) || Build.VERSION.SDK_INT > 22; if (canMockPosition && hasAddTestProvider == false) { try { for (String providerStr : mockProviders) { LocationProvider provider = locationManager.getProvider(providerStr); if (provider != null) { locationManager.addTestProvider( provider.getName() , provider.requiresNetwork() , provider.requiresSatellite() , provider.requiresCell() , provider.hasMonetaryCost() , provider.supportsAltitude() , provider.supportsSpeed() , provider.supportsBearing() , provider.getPowerRequirement() , provider.getAccuracy()); } else { if (providerStr.equals(LocationManager.GPS_PROVIDER)) { locationManager.addTestProvider( providerStr , true, true, false, false, true, true, true , Criteria.POWER_HIGH, Criteria.ACCURACY_FINE); } else if (providerStr.equals(LocationManager.NETWORK_PROVIDER)) { locationManager.addTestProvider( providerStr , true, false, true, false, false, false, false , Criteria.POWER_LOW, Criteria.ACCURACY_FINE); } else { locationManager.addTestProvider( providerStr , false, false, false, false, true, true, true , Criteria.POWER_LOW, Criteria.ACCURACY_FINE); } } locationManager.setTestProviderEnabled(providerStr, true); locationManager.setTestProviderStatus(providerStr, LocationProvider.AVAILABLE, null, System.currentTimeMillis()); } hasAddTestProvider = true; // 模拟位置可用 canMockPosition = true; } catch (SecurityException e) { canMockPosition = false; } } if (canMockPosition == false) { stopMockLocation(); } return canMockPosition; }
还记得在GDMapActivity里有这么一个方法,就是模拟点击地图的方法,如下代码:
private void InitMarker() { //定义一个标记参数 markerOptions=new MarkerOptions(); //设置Marker可拖动 markerOptions.draggable(true); //点击地图 aMap.setOnMapClickListener(new AMap.OnMapClickListener() { @Override public void onMapClick(LatLng latLng) { //标记参数的位置 markerOptions.position(latLng); //如果点击标记还没有创建,新建一个。 if(mClickMarker==null) { markerOptions.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)); mClickMarker=aMap.addMarker(markerOptions); } else { //否则更改位置,也就是说只有一个标记 mClickMarker.setPosition(latLng); } Log.d(GDMapActivity.class.getSimpleName(),"setOnMapClickListener "+latLng.toString()); //设置点击位置为模拟gps位置。 setMockLocation(latLng); //提示 ShowToast(latLng.toString()); } }); }
这里有个setMockLocation方法,他把点击地图所得到的坐标传入这个方法,这个方法是这样写的:
//点击位置即是想要更改成的位置。 private void setMockLocation(@NotNull LatLng latLng) { if(mockLocationManager.getUseMockPosition(getApplicationContext())) { mockLocationManager.setLocationData(latLng.latitude+0.002715f,latLng.longitude-0.0051f);//这里有做修正,暂时不明白为什么会有差距。 startMockLocation(); mockLocationManager.startThread(); } ShowToast("setMockLocation"+latLng.toString()); }
特别注意的是,这个地方模拟点击的坐标精确度好像没有高德自己定位的精确度搞。所以这里我按照多次统计之后的结果做了位置修正。分别是经度0.002715和-0.0051。否则会发生偏移。
这样就对应起来了。打开地图,点击点图某一点,然后把这个点做经纬度修复并传递到MockLocationManager中,并让位置在另外一个线程中一直设置位置。
特别注意的是,如果不一直设置位置,会出现我刚开始的,明明位置设置好了,可是切换到后台其他应用。诸如钉钉之类的,会发现GPS位置并没有改变。
大概过程就是这样。如果不懂,可以留言。
四、问题
这个方式有两个问题:
**第一个问题:**可以对钉钉实现修改位置,但是必须不是最新版本,2020年之前的版本才行,具体哪个版本,我没有去实验,应该是说2020年2月份之前的版本才行,因为钉钉之后的版本加入了开发者模式不能打开的设定。搜了一下,说是可以回退到之前的版本。回退可能得想办法,据说钉钉禁止回退。如果你还原出厂设置可以回退安装之前的版本。
**第二个问题:**微信没法实现,我查了下,微信利用了基站定位,网上有对应的基站定位欺骗方式。这个如果第一个问题我结局了,我会来实现这个过程。
五、抛砖引玉
因为有两个问题,尤其是第一个问题,大大打击了我的自信心。我一度没法继续看这个代码了。折腾了好久,结果发现人家轻轻松松几句代码把钉钉这条路给封了。绝望。有点想找个地方释放下压力。希望是个柳腰弯月眉,突然就浪起来了。感觉男人的脑袋里面除了大便就是屎。
划重点:如果大家有解决第一个问题的方法(也就是非开发修改定位模式欺骗GPS方式),麻烦留言告诉我。
好了,总想找个地方去匍匐一下,翻云覆雨一下。
想啥?我就是想去搓个澡!!!你们这群思想龌龊的家伙。
更多相关文章
- android定时定位 - 利用百度定位API来实现定时定位功能
- 实现Android的不同精度的定位(基于网络和GPS)
- Android高德地图使用之地点关键词的输入提示-InputTips
- Google Map Api 在android 中的使用(转载)
- 应用开发笔记 2011年10月28日 Android(安卓)使用动画效果后的控
- Android(安卓)Studio初使用之百度地图初使用(一)--配置
- 解决android程序中oncreate方法中调用百度地图MKSearchListener
- Android(安卓)studio 百度地图开发(9)只显示网格不显示地图或者只
- Android使用Google Map服务 - 根据GPS信息在地图上定位