好的安卓,广播我的位置!






4.94/5 (29投票s)
本文解释了一个我为自己找到的真实世界解决方案,即定期通知家人我的当前位置。
引言和背景
所以,我在一所(很远的)大学获得了 MCS 课程的录取,我的父母总是强迫我给他们发短信或打电话,告诉他们我的当前位置,我在哪里,当前的状况如何,我是否赶上了公交车等等。这没什么麻烦,麻烦的是我通常会听 Eminem 的歌,所以我会忘记告诉他们我在哪里等等。
为了解决这个难题,我想我应该让我的安卓手机对我自己更有用一点,并通过短信通知我的父母我目前在哪里!本文不仅关注创建那个简单的应用程序,还关注 LocationManager
、LocationListener
、PendingIntent
以及安卓编程中的其他一些概念,你可能需要理解这些概念才能构建其他类似的需要 GPS 或基于网络的定位服务并通知客户端的应用程序。
该应用程序还使用了 SmsManager
,这是一个提供使用客户端网络发送短信功能的 service,费率适用。我希望这篇文章能引起你的兴趣,并可能对你有吸引力。:-)
但这篇文章另有他意,它旨在解释在安卓设备中使用位置 API 的方法。安卓设备充满力量和潜力!位置 API 不仅用于地图和定位朋友的服务,还为您提供用于任何目的的位置 API,因为决定和执行您想用它做什么的权力在您手中。就像我一样,我使用了位置更新,并将它们用于短信中,并通过网络广播给我的朋友和家人。这篇文章只解释了这些事情的一小部分,其余的由您决定。把它想象成乐高积木,玩玩这个帖子吧!:-)
构造与概念
这个应用程序的概念就像一个监听器和一个广播器一样简单,尽管不会直接使用广播,但概念是相似的。此外,一旦完成,我们将能够将 `SmsManager` 插入到我们的应用程序中,以向我们感兴趣的人发送短信。
开始
LocationManager
用于管理所有位置感知应用程序所需的一切。- 一个监听器,可能适用于我们的应用程序,当位置发生任何变化时,可以执行一段代码。这一点又分为两个组件,我稍后将详细讨论它们。
- 一个被触发并通知我们客户的对象。该对象可以是任何东西,在我的例子中,我将使用
SmsManager
向我希望收到这些通知的人发送短信。
通过这种方式,我将能够将位置更新广播给那些我希望收到通知的人。
图 1:我们的需求和解决方案演示。解释了在此演示中发生了什么以及使用了哪些对象。
由于这是一个通用的单元概述,您可以轻松更改实现以满足您自己的需求。例如,您可以更改通知客户端的方式。您可以删除 SmsManager
并实现您自己的 API,将详细信息发送到在线云,传输到 Web 服务,将位置存储在您自己的设备上,或者任何您想要的!这是本文的一个优点,因为我不会将所有内容都硬编码到一个活动中,而是会尝试为您提供多个服务类和函数,这些类和函数可以移植到其他用途、其他功能、其他服务和其他实现中。
阅读本文的其余部分,了解在您的 Android 应用程序中实现位置感知是多么容易,以及实际与客户端共享这些数据或执行您想做的任何事情是多么容易。
理解安卓定位API
首先,我想向您介绍 Android 位置 API 的基础知识。位置 API 自 Android 设备问世以来就已添加,但不是 API 1,而是几个版本之后。位置 API 包括提供程序对象、管理器、地址、地理编码器等等。它们都归类在相似的包下,开发人员可以在其应用程序中使用它们,以充分利用应用程序中的定位服务并开发位置感知应用程序。有没有想过,**导航应用程序是如何工作的**?继续思考,或者继续阅读这篇文章。
Android API 基于 Java 编程语言,因此这些 API 的概念与 Java API 类似。包、对象、管理器,然后是监听器。在 C# 的平行宇宙中,有命名空间、对象、事件和处理程序... 所以,我希望你已经理解了 LocationManager
是什么以及 LocationListener
是什么,不是吗?
LocationManager
是定位 API 中的一个特殊对象,它保存着位置提供者、位置详细信息、检查位置的最小时间跨度以及其他一些信息。LocationListener
另一方面是监听器、处理程序,它在位置更新准备好时被触发。简单来说,对你而言:LocationManager
被请求提供位置变化的更新,并且传递一个 LocationListener
对象,该对象将处理位置的变化。LocationListener
只对在位置更新时执行感兴趣。它们并不总是持有 GPS 的引用 — *为了节省电池,GPS 必须定期关闭,更多内容请参阅“提示”部分* — 但它们将每 1 分钟,或在用户向任何方向移动 100 米后捕获位置更新。通过这种方式,您可以在应用程序中收到用户已从其先前位置移动的通知,并且您可以继续处理您想用新位置做的事情。
现在,重点来了,在接下来的部分中,我将讨论这些对象,然后我将继续进行应用程序的编程部分。我希望,我已经清楚地说明了 Android 位置 API 编程的这些技术方面。
LocationManager
描述
首先是 LocationManager
对象,无论您正在构建什么类型的应用程序,无论您期望如何使用位置,您始终会在应用程序中使用 LocationManager
对象。LocationManager
对象内置在 Android 核心中,您可以将其用作**系统定义的服务**。要使用此服务,您首先需要访问权限,在您的清单文件中输入以下内容:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
LocationManager
为您提供两种类型的位置
- 近似位置,(*精确度约为 200 英尺*),
ACCESS_COARSE_LOCATION
。 - 精确位置(*精确度为 20 英尺*),
ACCESS_FINE_LOCATION
。
大多数情况下,您应该考虑使用 ACCESS_FINE_LOCATION
,如果您的应用程序需要以较低的精度访问位置,则可以考虑 ACCESS_COARSE_LOCATION
类型。除了精度,两者所需的电池电量也存在差异。
要创建此对象的新实例,您不需要对其调用 new 运算符,而是调用 getSystemService
,然后将其转换为 LocationManager
。例如,
LocationManager manager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
这将允许您使用您在应用程序中持有的管理器对象引用。然后,您也可以请求位置更新。
请求位置更新
这是一个有点棘手的概念,因为它也取决于您应用程序的架构和设计。如果您的应用程序不在后台运行,例如在一个持续执行的独立工作线程上,您将不会使用 LocationListener。LocationListener 在您的应用程序关闭后立即被移除。为此,引入了 PendingIntent 以捕获位置更新。
使用的函数类似,但已重载,可以接受其中一个对象。现在,您有责任确定哪个最适合您的需求来捕获位置更新。休眠的应用程序将无法捕获更新,您可能会错过用户从一个位置移动到另一个位置的更新,在这种情况下,PendingIntent 对象会有所帮助。它可以在自己的独立线程上执行,因此您可以允许 LocationManager 代表您使用 PendingIntent 执行操作。
信息:如果您阅读过 Android 文档,您就会知道 PendingIntent
是一种特殊的 Intent
。它是一个不需要您的应用程序处于活动状态即可执行某些操作的 Intent
,您创建一个 Intent
并将其传递给系统,以便系统代表您完成该工作,就像是您自己在做那件事一样!PendingIntent
就像令牌、访问密钥,您将其授予其他应用程序或框架,它们使用它们来模拟您的应用程序。
manager.requestLocationUpdate(PROVIDER, minMinutes, minMeters, handler);
在两种情况下,处理更新的代码都是相同的,现在您应该了解这些变量的含义。
提供者
作为 Java 开发人员,您可能已经明白了这里会有一个“String”*常量*。这些提供程序的内置值是:LocationManager.GPS_PROVIDER
,使用 GPS 卫星提供对定位服务的访问。LocationManager.NETWORK_PROVIDER
,使用网络提供定位服务。与 GPS 卫星相比,提供的位置精度较低。
最小分钟数
您可以控制 LocationManager 更新您的处理程序的频率。将其设置为大于 0 的值,以便在一段时间后收到通知。*该值以分钟为单位*。最小米数
另一个标志,仅当用户从其先前位置移动了几米后才收到通知。如果您想在用户移动 100m 或 1000m(1km)后通知他们,这将很有帮助。处理程序
现在这是一个概念性的点,您需要理解。处理程序只是当更新准备好供您的应用程序处理时触发的代码。
处理程序有两种形式和大小,位置监听器
待定意图
处理程序的详细信息将在下一节中讨论。到目前为止,LocationManager
必须清楚它是什么以及它为我们提供了什么。
LocationListener
描述
首先,我想解释一下 LocationListener
对象以及我们如何在 LocationManager
中使用它来接收位置变化的更新。LocationListener 只是一个接口,它包含您可以在自己的应用程序中用于处理位置更新的处理程序(函数)。就像任何其他接口一样,您实现它并覆盖它拥有的函数!
现在,例如,您创建一个新的 Java 类 SampleListener
,并实现 LocationListener
,您将拥有 4 个额外的函数,允许您管理应用程序中的定位服务。请看下面的代码:
package com.afzaalahmadzeeshan.mylocation; // Package name
// Some required imports
import android.location.Location;
import android.location.LocationListener;
import android.os.Bundle;
// Entire class structure with empty functions.
public class SampleListener implements LocationListener {
@Override
public void onLocationChanged(Location location) {
// Gets executed once location change has been notified to application.
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// Status of location provider has been changed
}
@Override
public void onProviderEnabled(String provider) {
// Provider enabled by user
}
@Override
public void onProviderDisabled(String provider) {
// Opposite of onProviderEnabled(String)
}
}
现在,对这些函数进行一点解释就足以满足本节的目的。在我深入讨论之前,LocationListener
的目的只是从 LocationManager
接收关于用户位置变化的通知,仅此而已!
onLocationChanged(Location)
这是上述可用函数列表中的第一个函数,我们可以用它来接收位置 API 变化的通知。该函数为我们提供了用户当前所在的新位置,新位置作为我们函数的参数传入。
每次应用程序有可用更新时,此函数都会执行。您可以将其用作应用程序业务逻辑的基础,并在此处执行任务,例如重新绘制 UI、更新数据库等。onStatusChanged(String, int, Bundle)
当提供者的状态从可用变为不可用等等时,此函数会执行。该函数会传入有关提供者、状态码和其他可能有助于处理变化的详细信息。提供商
提供者,如前所述,是位置 API 的服务提供者。它可以是GPS_PROVIDER
或其他可能为您提供位置服务的提供者。它帮助您找出哪个提供者改变了状态,以便您可以适当地处理该变化。状态
此参数决定它现在的状态。与provider
结合使用,它将让您找出哪个提供者当前处于活动状态,哪个提供者当前处于关闭状态。它具有以下值,您可以根据这些值确定您的应用程序应该如何继续接收更新,如果它们都不可用,则必须通知用户情况服务中断
暂时不可用
可用
额外内容
如前所述,任何其他可能在此函数中有用的详细信息都将添加到 Android Bundle 中并传递给该函数。
onProviderEnabled(String)
此函数(和后面的函数)由用户交互触发。例如,当用户手动启用提供者时。您可以使用此函数重新激活服务并继续处理位置更新。onProviderDisabled(String)
与前一个类似,当用户交互发生且提供程序被禁用时,此事件也会被触发。使用它来禁用应用程序中的定位服务。
这样,您就可以管理如何获取更新以及发生任何变化时会发生什么。LocationListener
是一种非常简单有效的位置更新处理方式。但是等等,这也有一个缺点。要使用 LocationListener 处理位置更新,您的应用程序需要可见并处于活动状态,一旦您的应用程序被移除,监听器也会被移除,因此您无法从后台(例如服务)捕获位置活动。
换句话说,您的应用程序不再充当位置捕获服务,而是一个“当前位置查看器”之类的应用程序。如果是这种情况,那么太好了,这个监听器对您来说足够了!但是,如果您希望在应用程序未运行或在后台运行时(并且主工作线程可能不可用)获取更新,那么请阅读下一节关于 PendingIntent
的内容。对于后台服务,PendingIntent 完美运行,即使您的应用程序已关闭,也会为您提供更新,因为 PendingIntent 在其自己的线程上执行。
PendingIntent
描述
现在我将指出如何将 PendingIntent 与此管理器一起使用,并在您的应用程序中捕获位置更新。在上一节中,您学习了如何使用监听器,在本节中,您将使用服务,然后接收那些不需要您的应用程序始终在用户面前处于活动状态的后台服务上的更新。
创建服务
第一步是创建一个服务并编写当更新准备好供我们的应用程序处理时需要处理的逻辑。我们需要在应用程序中创建一个 IntentService
。IntentService
允许我们拥有一个函数,我们可以在其中编写每次启动该服务时执行的逻辑代码。通过扩展该对象,您可以获得该对象所拥有的相同功能。将本文与源代码连接起来,我创建了一个服务并将其命名为“BroadcastLocationService
”,它扩展了 IntentService
。
package com.afzaalahmadzeeshan.mylocation; import android.app.IntentService; import android.content.Intent; import android.content.Context; import android.location.Location; public class BroadcastLocationService extends IntentService { public BroadcastLocationService() { super("BroadcastLocationService"); } @Override protected void onHandleIntent(Intent intent) { sendMessage(getApplicationContext(), new LocationService(getApplicationContext()).getLocation()); } private void sendMessage(Context mContext, Location location) { // Code here to broadcast the location } }
在上面我们应用程序的类中,我们有两个函数。一个是继承的,另一个是我们自己创建的自定义函数。现在,考虑一下,当向此服务发送新请求以执行时,它将执行 onHandleIntent(Intent)
函数下的代码。需要处理的 Intent 作为参数传入。
该意图是如何传递的,您将在下一步中学习。如果您对 PendingIntent
没有任何概念,下一节将为您提供一个概念,所以请继续阅读。
创建运行服务的意图
我们可以创建一个意图,在后台执行服务并为我们的应用程序完成工作。请注意,用户不再需要设备屏幕处于活动状态即可使用此方法捕获位置更新。
Intent intent = new Intent(getApplicationContext(), BroadcastLocationService.class);
上面一行代码足以创建当位置更新出现时需要触发的意图。但这还不够,我们现在需要为此意图创建一个 PendingIntent
,并等待管理器在服务和更新可用时向我们提供它们。
要创建新的 PendingIntent
,请编写以下内容:
PendingIntent pIntent = PendingIntent.getService(
getApplicationContext(), // Get the context
0, // Request code
intent, // The intent we created
FLAG_CANCEL_CURRENT // A flag
);
这现在为我们创建了一个 PendingIntent 令牌,我们可以将其传递给其他应用程序、服务或对象,它们可以在我们稍后需要触发时代表我们执行它。
接收更新
现在,又回到同一个话题... 我们现在要将 pIntent
传递给我们的管理器,并在该服务中等待,直到位置更新可用。在这种情况下,相同的代码将起作用,但最后一个参数有所不同。
manager.requestLocationUpdates(GPS_PROVIDER, 10, 1000, pIntent);
例如,在上述代码中,我正在从 GPS_PROVIDER
访问位置更新,每 10 分钟一次,并且仅当用户移动了 1000 米;1 公里。如果是这种情况,则会触发 pIntent,这反过来又会调用 onHandleIntent
函数,并允许我们处理位置并执行我们想做的事情。
一点点 SmsManager
尽管本文不打算专注于硬编码应用程序,但旨在为您提供位置更新广播的概述。SmsManager 只是一个可以使用的 API 示例。您可以创建自己的服务、API 库等来广播位置。前面的部分将保持不变,这只是您希望如何共享位置的方式。
获取 SmsManager
实例
要获取 SmsManager 的实例,请调用以下代码
SmsManager manager = SmsManager.getDefault();
一旦完成,您就可以发送短信或彩信了!这也很容易,只需传递目的地号码、短信内容,您就可以开始了。但为此,您的应用程序需要拥有以下权限:
<uses-permission android:name="android.permission.SEND_SMS" />
这将允许您的应用程序发送消息。
编写应用程序
在写这一部分的时候,我正在思考要写什么,因为主要的东西已经讨论过了。在这一部分,我将向你展示如何将所有东西匹配起来,并创建一个正在运行的 Android 应用程序。第一步是创建一个管理器,并允许用户输入服务更新的详细信息。我们需要允许用户提供这些值,在我的应用程序中,我创建了 2 个 EditText
字段来获取值,或者我为这些标志设置了一些默认值。
图 2:主页面输入字段,用于时间间隔和最小移动米数。
此外,我们希望用户控制服务的运行和关闭时间。这样他就可以管理何时使用资源以及何时不浪费资源。GPS、短信和所有类似的资源也消耗电池,电池也是一种资源,必须明智地考虑。所以,为此我创建了另一个 CheckBox
对象来保存用于激活或停用服务的密钥。
图 3:主活动上禁用的服务界面。
向用户显示有关服务以及应用程序后台运行情况的详细信息也是一个很好的用户体验。例如,我们将根据一些检查向多个联系人发送位置更新。因此,明智的做法是将这些详细信息显示在屏幕上。例如以下内容:
图 4:显示我们服务详细信息的 Android 应用程序。
以上 Android UI 的 XML 代码如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".HomeActivity"> <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/enable_service" android:id="@+id/enable_service" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:checked="false" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="" android:id="@+id/welcome_text" android:layout_below="@+id/enable_service" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_marginTop="10dp" android:textColor="#000000" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/welcome_information_home" android:layout_alignTop="@+id/welcome_text" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:textColor="#000" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="Configuration" android:id="@+id/textView4" android:layout_below="@+id/welcome_text" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_marginTop="50dp" android:textColor="#000" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:text="Time interval" android:id="@+id/textView5" android:layout_below="@+id/textView4" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_marginTop="28dp" android:textColor="#000" /> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:inputType="number" android:ems="10" android:id="@+id/time_interval" android:layout_below="@+id/textView5" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:hint="In minutes; 5-18000" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:text="Minimum distance covered" android:id="@+id/textView6" android:layout_centerVertical="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:textColor="#000" /> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:inputType="number" android:ems="10" android:id="@+id/meters_distance" android:layout_below="@+id/textView6" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:hint="In meters." /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Update" android:id="@+id/update_button" android:layout_below="@+id/meters_distance" android:layout_centerHorizontal="true" /> </RelativeLayout>
上述 Android UI 很有用,因为它已经设置好了 UI 组件,然后我们可以使用相同的 UI,并根据需要从代码后面更改视图。
管理数据源
在 Android 中,您在 SQLite 数据库中管理数据。它们是默认提供的小巧紧凑的数据库,您可以有效地存储数据。
我使用了同样的方法,为各种东西创建了表和数据库,并在类中提供了一些函数,我可以从外部世界执行这些函数!我不想让外部对象直接调用函数并在数据库上执行命令。这就是为什么我将所有内容都保密,并为 CRUD 操作提供了公共函数。
在本节中,您只需要理解两个类,以在应用程序中实现 SQLite 数据库功能。
1. SQLiteOpenHelper
类
此类为我们提供模仿 SQL 引擎可能需要的函数和其他资源。允许我们管理 SQL 数据库以及与这些数据库的连接。为我们提供根据需要创建数据库、升级数据库或删除数据库的函数。
有很多函数,我只想在这里谈论一个函数,`onCreate(SQLiteDatabase)`。这个函数在数据库创建时被调用。你需要处理这个函数并编写 SQL 命令来创建表并在数据库中为你的对象设计模式。这个函数只被调用一次!所以请确保你记住这个模式,因为要改变模式,你需要删除以前的数据库并重新创建它们。
一个应该在 onCreate(SQLiteDatabase)
函数中使用的示例代码如下:
private final static String COMMAND = "CREATE TABLE " + TABLE_NAME +
"(" +
_ID + " INTEGER PRIMARY KEY, " +
COLUMN_ONE + " TEXT," +
COLUMN_TWO + " TEXT" +
")";
@Override
public void onCreate(SQLiteDatabase db) {
// Execute the codes here...
db.execSQL(COMMAND);
}
以上代码一旦执行,将创建一个表并在其中创建三列。您可以执行多个命令在其中创建多列等等。有关 SQL 命令的更多信息,我建议您学习 SQL 语言!
2. BaseColumns
接口
这里没有什么重要的需要理解!它只是向对象添加了另一个成员 _ID
,Cursor
对象可能会期望它。在上面的代码中,_ID
来自这个接口。
public class MyClass extends SQLiteOpenHelper implements BaseColumns {
/*
* The code comes here to manage the SQL databases.
* The functions as discussed also need to be implemented to trigger our own logic each time a database needs to be created.
*/
}
我还使用了相同的方法来创建数据库并存储每个项目的值。它包含在示例中!
捕获位置
如前所述,应用程序的逻辑基于一个后台服务,该服务运行并捕获位置,然后将位置发送给联系人。这就是为什么应用程序不需要为我们应用程序中的每个组件都拥有一个 UI。我们的应用程序包含服务和其他不需要 UI 的后台进程,IntentService、SmsManager 等等,它们不需要 UI 才能工作。构建 UI 不会造成太大的问题,但是为什么要浪费另一个活动,而您可以在基本类中触发函数呢!
我编写了相同的 Java 类,它完成了应用程序的所有工作。它捕获位置,然后触发广播器广播位置。
package com.afzaalahmadzeeshan.mylocation; import android.app.AlertDialog; import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.location.*; import android.provider.Settings; public class LocationService { private Context mContext; private Location mLocation; private LocationManager locationManager; private PendingIntent intent; private static long distance; private static long minutes; public LocationService(Context context) { mContext = context; // Set up to capture the location updates Intent smsIntent = new Intent(mContext, BroadcastLocationService.class); intent = PendingIntent.getService(mContext, 0, smsIntent, 0); } public static long getDistance() { return distance; } public static long getMinutes() { return minutes; } public void cancelUpdates() { if(locationManager != null) { locationManager.removeUpdates(intent); } } public Location getLocation() { locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); // Check if the tracking is enabled. boolean isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); boolean isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); if (!isGPSEnabled && !isNetworkEnabled) { // Prompt to get the settings enabled by the user. showSettingsDialog(); } else { // Either one is enabled // 10 * 60 * 1000 = 10 minutes // 1000 = 1 km // this = listener if (isGPSEnabled) { // Get the location from GPS try { locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, getMinutes() * 1000 * 60, getDistance(), intent); if(mLocation == null) { mLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); } } catch (SecurityException e) { showSettingsDialog(); } } else { // Get the location from GPS try { // Set up to capture the location updates Intent smsIntent = new Intent(mContext, BroadcastLocationService.class); PendingIntent intent = PendingIntent.getService(mContext, 0, smsIntent, 0); locationManager.requestLocationUpdates( LocationManager.NETWORK_PROVIDER, getMinutes() * 1000 * 60, getDistance(), intent); if(mLocation == null) { mLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); } } catch (SecurityException e) { showSettingsDialog(); } } } return mLocation; } public void showSettingsDialog() { new AlertDialog.Builder(mContext) .setTitle("Enable GPS") .setMessage("Enable GPS in your settings for receiving active location details.") .setNegativeButton("Cancel", null) .setPositiveButton("Settings", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); mContext.startActivity(intent); } }) .create().show(); } }
此代码在后台运行,我们的主活动使用这里可用的函数来执行操作并获取有关位置管理器和位置 API 的详细信息。
广播位置
最后一步是实际广播位置,我为此使用了 SmsManager!以下代码可以做到这一点。
package com.afzaalahmadzeeshan.mylocation;
import android.content.Context;
import android.telephony.SmsManager;
import java.util.ArrayList;
public class SmsService {
public static boolean sendMessage(Context context, String location) {
boolean result = false;
try {
SmsManager manager = SmsManager.getDefault();
String message;
// Get the string
message = "[AUTOMATIC MESSAGE]\n" + "I am currently at " + location + " (approximately; accuracy within 100 meters).";
ArrayList<Number> numbers = new ContentManager(context).getNumbers();
if(numbers != null && numbers.size() > 0) {
for (Number number : numbers) {
String telNumber = number.getNumber();
manager.sendTextMessage(telNumber, null, message, null, null);
}
result = true;
}
} catch (Exception ignored) {
}
return result;
}
}
上述代码为我们完成了任务。如您所见,它获取我们希望通知的联系人列表,然后循环以根据位置向他们每个人发送消息。所以这为我们完成了任务!
额外:处理错误和良好实践
现在,我可能会分享一些关于错误和糟糕应用程序代码的个人经验,以便您在为客户编写应用程序时,不会重复同样的问题,并且设计可以实现到最佳水平。
1. 表格未找到
您的应用程序中可能出现的第一个错误是“table not found”(表未找到)。这是一个合法的错误,原因是您最初在创建数据库时可能想要创建的表(table)没有被创建,现在它无法被创建,除非您删除以前的数据库并重新执行 onCreate
函数。
要解决此问题,在开发环境中,您可以删除数据然后重新执行应用程序。但请记住始终在数据库的 onCreate
函数中创建表并定义模式。
2. 供应商不可用
由于我们的应用程序依赖于位置提供程序,我们需要确保它们在我们的应用程序开始捕获位置并接收有关用户位置其他变化的通知之前可用。您可以使用管理器并获取定位服务和提供程序。
例如,以下代码确定 GPS 的提供程序是否已启用:
boolean isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
您可以以类似的方式获取其他提供程序。由于这是一个布尔对象,您可以在条件中使用它并适当地工作。您还可以创建一个逻辑来显示对话框并在用户需要触发提供程序时打开设置。
if(!isGPSEnabled && !isNetworkEnabled) {
showSettingsDialog();
}
这将触发另一个功能,提示用户在设置中激活提供商。该功能具有以下结构:
public void showSettingsDialog() { new AlertDialog.Builder(mContext) .setTitle("Enable GPS") .setMessage("Enable GPS in your settings for receiving active location details.") .setNegativeButton("Cancel", null) .setPositiveButton("Settings", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); mContext.startActivity(intent); } }) .create().show(); }
这将提示用户激活定位服务。为了这篇文章,我可以模拟这个场景并向您展示它是如何工作的。例如,我们有一个激活定位提供程序并从中获取位置的应用程序。
图 5:Android 设备显示设置,位置默认启用。
我们可以禁用定位功能,看看我们的应用程序将如何表现。
图 6:定位现已关闭。
现在,我们可以触发应用程序中的捕获功能,看看会发生什么。首先,请考虑我们有一个条件来检查提供程序是否已启用。如果未启用,则该函数会显示一个警报框,否则,如果不是这种情况,则可能会引发异常,因为提供程序不可用。
当我们点击启用服务时,我们的位置管理器会告诉我们提供商未启用,因此我们的应用程序会提示用户启用服务。否则,取消操作。
图 7:Android 警报对话框,提示他启用位置。
用户可以在其设置中启用位置,因为它们已被禁用。
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
上面的意图默认会将用户带到位置设置,所以这与其他应用程序直接将您带到设置是同一回事。
图 8:位置已关闭。用户可以将其打开以启用提供商。
然后可以看到 GPS 提示位置提供商正在工作,您的应用程序可以为用户捕获位置。
图 9:系统托盘中显示的 GPS 标志。
所以,这解释了您应该如何考虑检查应用程序的路径,并确保您编写的有效应用程序能够为用户处理大部分事情。
3. 资源管理
上面显示的应用程序使用短信资源、定位资源和电池。为此,由于我们只是想通知用户,您必须考虑延长时程以节省电池。每次 GPS 引擎开启时,它都会开始消耗电池电量,因此您可能需要确保您的应用程序不会浪费电量。
- 最短时间
至少设置为 10 分钟。除非您在屏幕上显示 GPS,否则您不需要持续更新。 - 最小距离
100 米根本不重要,所以,这可以设置为最小行驶距离。
这将允许我们的应用程序休眠一段时间,然后一段时间后访问位置。由于我们的应用程序能够在后台工作,我们不需要担心用户是自己触发请求还是应用程序将自行管理。
短信管理器会消耗资源并向用户收费,因此请确保您编写的文本非常简洁,但要小。
在我写的应用程序中,有以下代码:
if(m > 99 && min > 4 && min < 18001) {
/* m is the variable for meters
* min is the variable for minutes
*
* The above condition checks if minimum minutes are less than 18000, that is enough!
* also checks whether meters providers are greater than 100 or not.
* This ensures that user receives updates for location changes and battery isn't
* drained without need.
*/
new ContentManager(getBaseContext()).setupSettings(getBaseContext(), m, min);
}
该代码工作并检查配置的有效性。
请勿发送批量消息!几个月前我写了一个应用程序,它使用循环以数组的形式发送消息。这阻塞了整个网络,我当时需要立即收到的消息延迟了 5 到 9 小时才收到,而且很多时候我自己的消息都没有发送出去,因为网络上存在批量消息。尝试使用发送和交付报告来向网络发送下一条消息。
应用程序示例
当我使用这个应用程序时,我收到了如下短信。我使用我自己的号码作为联系人,因为我想看看它会怎么做,它在第一次运行时做得很好,遇到了一些问题,但我不认为它们是学习过程中的问题。:-)
图 10:我在使用我们刚刚学会创建的服务时收到的短信。
所以,通过这种方式,您可以看到位置 API 可以非常有用了!
兴趣点
该应用程序是为个人使用而开发的,但文章并不反映个人应用程序,而是解释了 API 和库。该应用程序解释了以下内容的使用:
位置管理器
位置监听器
待定意图
短信管理器
SQLiteOpenHelper
基本列
- 还有更多...
本文不强迫您遵循某个框架或方法,您可以自由地以自己的方式编写和实现。本文只是一份资源,您可以利用它从头开始构建自己的应用程序!下载源代码并开始使用吧。:-)