热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android如何实现模拟定位

这篇文章主要介绍了Android如何实现模拟定位,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

在导航测试场景中经常需要定位模拟和路线回放,记录下通过LocationManager.setTestProviderLocation()方法实现模拟地位,如果要测试的应用不支持TestProviderLocation模拟位置输入,可以考虑从HAL层入手,hook系统默认的GPS实现。

一、Android模拟权限开启配置

在Android6.0以下的版本中,需要在设置中勾选模拟定位的开关,在6.0以上就改成了选择模拟定位的应用,对应的开启配置方式也不一样,相同的是在AndroidManifest.xml都需要配置以下两个权限:


1)Android 6.0以下开启模拟定位开关

Settings.Secure.putInt(getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 1);

通过这种方式去开启模拟定位需要在AndroidManifest.xml中配置以下系统权限,应用还需要经过系统签名,对于非系统应用不能通过这种实现方式。

android:sharedUserId="android.uid.system"

涉及到这种操作可以通过adb shell命令的方式配置来绕过系统权限配置:

adb shell settings put secure mock_location 1

2)Android 6.0以上代码配置选择模拟定位的应用

在6.0以上的Android版本就需要设置指定包名的mock_location权限为allow。

try {
    String mockLocatiOnPkgName= getPackageName();
    PackageManager mPackageManager = getPackageManager();
    final ApplicationInfo ai = mPackageManager.getApplicationInfo(
            mockLocationPkgName, PackageManager.MATCH_DISABLED_COMPONENTS);
    AppOpsManager mAppsOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
    mAppsOpsManager.setMode(AppOpsManager.OPSTR_MOCK_LOCATION, ai.uid,
            mockLocationPkgName, AppOpsManager.MODE_ALLOWED);
} catch (PackageManager.NameNotFoundException e) {
    /* ignore */
}

同时在AndroidManifest.xml中还需要配置如下权限,可惜这种方式也是需要经过系统签名和源码一起编译,只有系统层级的应用才可以使用。

为了在设置界面模拟定位选项中显示模拟定位的应用,需要配置ACCESS_MOCK_LOCATION权限。

以上配置也可以通过adb shell命令去实现, 参数用自己应用的包名替换。

adb shell appops set  android:mock_location allow

关闭模拟定位权限,用如下命令:

adb shell appops set  android:mock_location deny

要查询有哪些应用开启了模拟定位权限,用如下命令:

adb shell appops query-op android:mock_location allow

执行后会输出应用的包名列表参数。

二、Android模拟定位实现

1)模拟定位开关检查

首先是代码中先判断模拟定位权限是否开启,6.0以上的只能通过添加定位监听是否有异常来判断。

boolean mockPermission = false;
if (Build.VERSION.SDK_INT <= 22) {//6.0以下
    mockPermission = Settings.Secure.getInt(getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) == 1;
} else {
    try {
        LocationManager locatiOnManager= (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            mockPermission = false;            
            return;
        }
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0, new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
            }
            @Override
            public void onStatusChanged(String s, int i, Bundle bundle) {
            }
            @Override
            public void onProviderEnabled(String s) {
            }
            @Override
            public void onProviderDisabled(String s) {
            }
        });
        mockPermission = true;
    } catch (SecurityException e) {
        mockPermission = false;
    }
}

可以看下添加定位监听的源码实现,以Android 7.0的源码实现做参考,LocationManager调用接口之后,最终是调用到LocationManagerService,在requestLocationUpdates 方法或 getLastLocation 方法中都有checkPackageName方法的调用

private void checkPackageName(String packageName) {
    if (packageName == null) {
        throw new SecurityException("invalid package name: " + packageName);
    }
    int uid = Binder.getCallingUid();
    String[] packages = mPackageManager.getPackagesForUid(uid);
    if (packages == null) {
        throw new SecurityException("invalid UID " + uid);
    }
    for (String pkg : packages) {
        if (packageName.equals(pkg)) return;
    }
    throw new SecurityException("invalid package name: " + packageName);
}

如果调用requestLocationUpdates方法的应用没有模拟定位的权限,就会报SecurityException异常,另外requestLocationUpdates需要在主线程中调用,如果在子线程中调用,还需要传一个looper参数,不然在实例化ListenerTransport的时候会报错。

看下ListenerTransport的构造函数,如果在子线程中添加监听,又没有传Loop,初始化mListenerHandler的时候就会报异常。

ListenerTransport(LocationListener listener, Looper looper) {
    mListener = listener;
    if (looper == null) {
        mListenerHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                _handleMessage(msg);
            }
        };
    } else {
        mListenerHandler = new Handler(looper) {
            @Override
            public void handleMessage(Message msg) {
                _handleMessage(msg);
            }
        };
    }
}

接着添加对应的Provider,设置开关状态为true,配置状态为AVAILABLE。

LocationProvider provider = locationManager.getProvider(LocationManager.GPS_PROVIDER);
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 {
    locationManager.addTestProvider(LocationManager.GPS_PROVIDER, true, true, false, false, true, true,
        true, Criteria.POWER_LOW, Criteria.ACCURACY_FINE);
}
locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);
locationManager.setTestProviderStatus(LocationManager.GPS_PROVIDER, LocationProvider.AVAILABLE, null, System.currentTimeMillis());

2)setTestProviderLocation调用

调用代码示例如下,经纬度、车速、定位准确性、方位、海拔参数根据实际需求设置。

Location loc = new Location(LocationManager.GPS_PROVIDER);
loc.setLongitude(24.522301);
loc.setLatitude(118.115756);
loc.setSpeed(60);
loc.setAccuracy(Criteria.ACCURACY_HIGH);
loc.setBearing(0);
loc.setAltitude(0);
loc.setTime(System.currentTimeMillis());
if (Build.VERSION.SDK_INT >= 17) {
    loc.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER,loc);

来看下为什么需要设置elapsedRealtimeNanos和time参数。在SDK版本17以下,Location(Android 4.1.1)是没有setElapsedRealtimeNanos这个方法的,在SDK版本17开始,Location(Android 4.2)加了这个方法,在调用setTestProviderLocation设置定位信息的时候,Android SDK版本17以上会做定位参数是否完整的校验,17以下的版本自动做补足,17开始的版本直接抛异常。

public void setTestProviderLocation(String provider, Location loc) {
    if (!loc.isComplete()) {
        IllegalArgumentException e = new IllegalArgumentException(
            "Incomplete location object, missing timestamp or accuracy&#63; " + loc);
        if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN) {
            // just log on old platform (for backwards compatibility)
            Log.w(TAG, e);
            loc.makeComplete();
        } else {
            // really throw it!
            throw e;
        }
    }
    try {
        mService.setTestProviderLocation(provider, loc, mContext.getOpPackageName());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

接着看下Location类中makeComplete和isComplete做的逻辑处理,isComplete里面有provider,Accuracy,mTime和mElapsedRealtimeNanos的判断。

@SystemApi
public void makeComplete() {
    if (mProvider == null) mProvider = "&#63;";
    if (!hasAccuracy()) {
        mFieldsMask |= HAS_ACCURACY_MASK;
        mAccuracy = 100.0f;
    }
    if (mTime == 0) mTime = System.currentTimeMillis();
    if (mElapsedRealtimeNanos == 0) mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
}
@SystemApi
public boolean isComplete() {
    if (mProvider == null) return false;
    if (!hasAccuracy()) return false;
    if (mTime == 0) return false;
    if (mElapsedRealtimeNanos == 0) return false;
    return true;
}

以上是就是单个点的模拟定位实现,如果要实现路线回放模拟,只要在后台请求到路线定位点数组数据之后,每隔1秒刷新调用setTestProviderLocation接口就可以了。

在模拟定位操作完毕之后,需要移除模拟定位对象,避免定位信息还是使用模拟定位接口的参数,如果没有移除下一次使用的时候又调用添加同名的Provider也会抛异常。

locationManager.removeTestProvider(LocationManager.GPS_PROVIDER);

以上就是Android如何实现模拟定位的详细内容,更多关于Android 模拟定位的资料请关注其它相关文章!


推荐阅读
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • RecyclerView初步学习(一)
    RecyclerView初步学习(一)ReCyclerView提供了一种插件式的编程模式,除了提供ViewHolder缓存模式,还可以自定义动画,分割符,布局样式,相比于传统的ListVi ... [详细]
  • Android 九宫格布局详解及实现:人人网应用示例
    本文深入探讨了人人网Android应用中独特的九宫格布局设计,解析其背后的GridView实现原理,并提供详细的代码示例。这种布局方式不仅美观大方,而且在现代Android应用中较为少见,值得开发者借鉴。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 解决微信电脑版无法刷朋友圈问题:使用安卓远程投屏方案
    在工作期间想要浏览微信和朋友圈却不太方便?虽然微信电脑版目前不支持直接刷朋友圈,但通过远程投屏技术,可以轻松实现在电脑上操作安卓设备的功能。 ... [详细]
  • 本文详细介绍了 Java 中 org.apache.xmlbeans.SchemaType 类的 getBaseEnumType() 方法,提供了多个代码示例,并解释了其在不同场景下的使用方法。 ... [详细]
  • 本文深入探讨了 com.example.android.sunshine.data.TestUtilities 中 validateThenCloseCursor() 方法的使用方法及其代码示例,旨在帮助开发者更好地理解和应用该方法。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 解决JAX-WS动态客户端工厂弃用问题并迁移到XFire
    在处理Java项目中的JAR包冲突时,我们遇到了JaxWsDynamicClientFactory被弃用的问题,并成功将其迁移到org.codehaus.xfire.client。本文详细介绍了这一过程及解决方案。 ... [详细]
  • 本文总结了2018年的关键成就,包括职业变动、购车、考取驾照等重要事件,并分享了读书、工作、家庭和朋友方面的感悟。同时,展望2019年,制定了健康、软实力提升和技术学习的具体目标。 ... [详细]
  • 资源推荐 | TensorFlow官方中文教程助力英语非母语者学习
    来源:机器之心。本文详细介绍了TensorFlow官方提供的中文版教程和指南,帮助开发者更好地理解和应用这一强大的开源机器学习平台。 ... [详细]
  • 在计算机技术的学习道路上,51CTO学院以其专业性和专注度给我留下了深刻印象。从2012年接触计算机到2014年开始系统学习网络技术和安全领域,51CTO学院始终是我信赖的学习平台。 ... [详细]
  • 本文介绍如何使用 Sortable.js 库实现元素的拖拽和位置交换功能。Sortable.js 是一个轻量级、无依赖的 JavaScript 库,支持拖拽排序、动画效果和多种插件扩展。通过简单的配置和事件处理,可以轻松实现复杂的功能。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
author-avatar
我是风一样的我们
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有