關於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方式),麻煩留言告訴我。


好了,總想找個地方去匍匐一下,翻雲覆雨一下。

想啥?我就是想去搓個澡!!!你們這羣思想齷齪的傢伙。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章