android平台上开发定位器(SMS+GPS)
在本教程中,我们将创建一个叫做PhoneFinder的应用。本应用将演示如何发送和接收短信。当你的手机丢了或者被偷,你可以使用别人的手机,接收你手机所处位置的GPS坐标,从而找到你的手机,这正是本应用的创意来源。本应用需要一个Activity让用户输入密码,还需要一个IntentReceiver来过滤接收到的短信。??
译者注:本文完全按照原文翻译,如果说明文字中的代码行号和实际显示的代码行号有出入,请按代码执行的功能理解
密码输入
如下所示,我们使用一个简单的对话框来帮助用户输入密码。一旦密码被正确输入,我们将把密码的MD5置入应用包的 SharedPreferences中。Preferences是一个存储少量持久数据的好地方,包中的其他类也可以访问Preferences。之所以存储密码的MD5,是因为即使密码被读取,它也不会泄露密码的明文,除非这密码本身非常弱。 
上图所示的对话框对应的layout文件,main.xml如下所示:
〈?xml version="1.0" encoding="utf-8"?〉?
〈LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"?
??? androidrientation="vertical"?
??? android:layout_width="fill_parent"?
??? android:layout_height="fill_parent"?
??? 〉?
??
??? 〈TextView?
??????? android:layout_width="fill_parent"??
??????? android:layout_height="wrap_content"?
??????? android:text="@string/password_label"??
??????? /〉?
??? 〈EditText id="@+id/password"??
??????????? android:maxLines="1"??
??????????? android:layout_marginTop="2dip"?
??????????? android:layout_width="wrap_content"?
??????????? android:ems="25"?
??????????? android:layout_height="wrap_content"??
??????????? android:autoText="true"?
??????????? android:scrollHorizontally="true"?
??????????? android:password="true" /〉?
??? 〈TextView?
??????? android:layout_width="fill_parent"??
??????? android:layout_height="wrap_content"?
??????? android:text="@string/password_confirm_label"??
??????? /〉?
??? 〈EditText id="@+id/password_confirm"??
??????????? android:maxLines="1"??
??????????? android:layout_marginTop="2dip"?
??????????? android:layout_width="wrap_content"?
??????????? android:ems="25"?
??????????? android:layout_height="wrap_content"??
??????????? android:autoText="true"?
??????????? android:scrollHorizontally="true"?
??????????? android:password="true" /〉?
??
??? 〈Button id="@+id/ok"?
??????? android:layout_width="wrap_content"??
??????? android:layout_height="wrap_content"??
??????? android:layout_gravity="right"?
??????? android:text="@string/button_ok" /〉?
?????
??? 〈TextView? id="@+id/text1"?
??????? android:layout_width="fill_parent"??
??????? android:layout_height="wrap_content"??
??????? /〉?
〈/LinearLayout〉?
??? 如你所见,这是一个相当简单的Layout,2个TextView,2个输入框,1个button,另外还有一个TextView用来显示提示信息。Layout中所涉及到的字符串在strings.xml中定义,以便提供更好的国际化支持。
??? 这个Activity对应的代码也是简单的。它所完成的任务就是确保输入的密码在6个字符以上并且密码的2次输入必须匹配。一旦所有条件满足,我们所要做的就是把用户输入的密码所对应的MD5保存在应用的SharedPreferences里。PhoneFinder Activity的代码如下所示:
public class PhoneFinder extends Activity {?
??
??? public static final String PASSWORD_PREF_KEY = "passwd";?
??
??? private TextView messages;?
??? private EditText pass1;?
??? private EditText pass2;?
??
??? @Override?
??? public void onCreate(Bundle icicle) {?
??????? super.onCreate(icicle);?
??????? setContentView(R.layout.main);?
??
??????? messages = (TextView) findViewById(R.id.text1);?
??????? pass1 = (EditText) findViewById(R.id.password);?
??????? pass2 = (EditText) findViewById(R.id.password_confirm);?
??
??????? Button button = (Button) findViewById(R.id.ok);?
??????? button.setOnClickListener(clickListener);?
??? }?
??
??? private OnClickListener clickListener = new OnClickListener() {?
??
??????? public void onClick(View v) {?
??????????? String p1 = pass1.getText().toString();?
??????????? String p2 = pass2.getText().toString();?
??
??????????? if (p1.equals(p2)) {?
??
??????????????? if (p1.length() 〉= 6 || p2.length() 〉= 6) {?
??
??????????????????? Editor passwdfile = getSharedPreferences(PhoneFinder.PASSWORD_PREF_KEY, 0).edit();?
??????????????????? String md5hash = getMd5Hash(p1);?
??????????????????? passwdfile.putString(PhoneFinder.PASSWORD_PREF_KEY,?
??????????????????????????? md5hash);?
??????????????????? passwdfile.commit();?
??????????????????? messages.setText("assword updated!");?
??
??????????????? } else?
??????????????????? messages.setText("asswords must be at least 6 characters");?
??
??????????? } else {?
??????????????? pass1.setText("");?
??????????????? pass2.setText("");?
??????????????? messages.setText("asswords do not match");?
??????????? }?
??
??????? }?
??
??? };?
}?
??? 在onCreate()方法中,我们初始化了将要在layout中使用的各种View,然后,我们创建了ok按钮的OnClickListener对象。当ok按钮被点击,位于第40行的onClick()方法将会被调用。
??? 在 onClick()方法中,我们确保密码的最少长度要求被满足,而且2个输入框中输入的密码字串一致。如果所有的条件满足,我们在第48行创建一个 SharedPreferences.Editor对象,通过该对象,我们可以编辑该应用的shared preferences。之所以把此类 preferences称为“共享”(shared),因为它是一个应用程序范围内的参数。如果你想把数据的可见性局限在调用的Activity对象内,你可以使用Activity.getPreferences(int),这是Activity级别的数据存储方式。
??? 在第48行,我们调用成员函数getMd5Hash(String),它返回字符串形式的MD5,然后我们把它存储在preferences里面。以下就是getMd5Hash(String)函数:
public static String getMd5Hash(String input) {?
??? try {?
??????? MessageDigest md = MessageDigest.getInstance("MD5");?
??????? byte[] messageDigest = md.digest(input.getBytes());?
??????? BigInteger number = new BigInteger(1,messageDigest);?
??????? String md5 = number.toString(16);?
?????????
??????? while (md5.length() 〈 32)?
??????????? md5 = "0" + md5;?
?????????
??????? return md5;?
??? } catch(NoSuchAlgorithmException e) {?
??????? Log.e("MD5", e.getMessage());?
??????? return null;?
??? }?
}?
??? 我们使用android.security.MessageDigest对象来表达我们想要使用的算法。digest()函数的输入参数为字符串所对应的字节数组,输出参数也是一个字节数组。输出的字节数组可以转化成一个BigInteger对象,进而利用该对象的toString(16)方法转变成16进制的字符串。因为把字节数组转化成一个BigInteger的时候,前端的0会被删掉,所以我们在 MD5字串前面补上足够的0,使它的长度达到32个字符。
处理短消息
??? 现在,用户可以在应用的preferences中保存输入的密码了,接下来,我们将要检查手机接收到的短信,并且对我们感兴趣的短信做出响应。这种短信的格式为:
SMSLOCATE:〈password〉?
??? 因此,如果某条短信以“SMSLOCATE:”开头,并且紧跟‘:’字符后面的密码的MD5和我们先前存储的匹配,我们将回复一条包含手机当前位置信息的短信。为了实现这一目的,我们需要建立一个IntentReceiver,响应类型为 “android.provider.Telephony.SMS_RECEIVED”的Action。该IntentReceiver必须在 AndroidManifest.xml 文件中声明,以下是AndroidManifest.xml 文件:
〈?xml version="1.0" encoding="utf-8"?〉?
〈manifest xmlns:android="http://schemas.android.com/apk/res/android"?
??? package="com.helloandroid.android.phonefinder"〉?
??? 〈uses-permission id="android.permission.RECEIVE_SMS" /〉?
??? 〈application android:icon="@drawable/icon"〉?
??????? 〈activity android:label="@string/app_name"〉?
??????????? 〈intent-filter〉?
??????????????? 〈action android:value="android.intent.action.MAIN" /〉?
??????????????? 〈category android:value="android.intent.category.LAUNCHER" /〉?
??????????? 〈/intent-filter〉?
??????? 〈/activity〉?
?????????
??????? 〈receiver /〉??
??????????? 〈/intent-filter〉??
??????? 〈/receiver〉?????
?????????
??? 〈/application〉?
〈/manifest〉?
??? 在第4行,通过〈uses-permission〉标签,我们请求接收短信的权限。在第 13行,我们指定我们的FinderReceiver作为一个receiver,同时声明了intent-filter,以此来过滤被广播的所有 intent。你可以看到,我们只对“android.provider.Telephony.SMS_RECEIVED”这类action感兴趣。
??? 现在,系统知道了对于这类action,该调用何种receiver。接下来我们来创建名为FinderReceiver的IntentReceiver。
public class FinderReceiver extends IntentReceiver {?
??
??? @Override?
??? public void onReceiveIntent(Context context, Intent intent) {?
??
??????? SharedPreferences passwdfile = context.getSharedPreferences(?
??????????????? PhoneFinder.PASSWORD_PREF_KEY, 0);?
?????????
??????? String correctMd5 = passwdfile.getString(PhoneFinder.PASSWORD_PREF_KEY,?
??????????????? null);?
?????????
??????? if (correctMd5 != null) {?
??
??????????? SmsMessage[] messages = Telephony.Sms.Intents?
??????????????????? .getMessagesFromIntent(intent);?
??
??????????? for (SmsMessage msg : messages) {?
??????????????? if (msg.getMessageBody().contains("SMSLOCATE:")) {?
??????????????????? String[] tokens = msg.getMessageBody().split(":");?
??????????????????? if (tokens.length 〉= 2) {?
??????????????????????? String md5hash = PhoneFinder.getMd5Hash(tokens[1]);?
??
??????????????????????? if (md5hash.equals(correctMd5)) {?
??????????????????????????? String to = msg.getOriginatingAddress();?
??????????????????????????? LocationManager lm =??
??????????????????????????????? (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);?
??????????????????????????? SmsManager sm = SmsManager.getDefault();?
??
??????????????????????????? sm.sendTextMessage(to, null, lm.getCurrentLocation("gps").toString(),??
??????????????????????????????????? null, null, null);?
?????????????????????????????
??????????????????????????? NotificationManager nm =??
??????????????????????????????? (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);?
??????????????????????????? nm.notifyWithText(R.layout.main,??
??????????????????????????????????? context.getText(R.string.notify_text) + " " + msg.getDisplayOriginatingAddress(),
??????????????????????????????????? NotificationManager.LENGTH_LONG, null);?
??????????????????????? }?
??????????????????? }?
??????????????? }?
??????????? }?
??????? }?
??? }?
}?
??? 首先,我们从SharedPreferences取出密码的MD5(18-21行),如果我们取到了,我们就用它来检查我们接收到的所有的短信。
??? 我们使用Telphony.Sms.Intents.getMessageFromInent(intent)来获取SmsMessages数组,我们将遍历该数组,查看每条短信消息体中是否包含“SMSLOCATE:”标记,如果找到符合条件的短消息,我们会获取‘:’字符后的密码,计算其MD5,和手机里存储的MD5进行比较。
??? 如果密码匹配,系统会执行第36行处的代码块。接下来我们将回复该短信,现在我们只需要一个代表短信目的地的字符串,以及代表手机位置信息的字符串。我们创建一个LocationManager,使用其getCurrentLocation("gps").toString()方法来获取位置信息。该信息由GPS位置服务提供者提供。然后,我们使用一个SmsManager对象发送短信。短信发送完毕后,我们还会显示一个通知。
??? 注意:有一个可能更好做法是,在密码输入对话框中加入一个可选项来控制是否显示通知。如果手机被偷,最好把通知隐藏起来,否则小偷会意识到自己被跟踪,从而把手机关掉。
测试
现在,万事俱备。在新版的android SDK中,给模拟器打电话或者发短信都很容易。这些操作都可以通过Eclipse中的模拟器控制面板视图来完成。你可以通过“"Window -> Show View -> Other”,选择android部分的“Emulator Control”项来增加这个视图。 译者注:如果你的android Eclipse插件为ADT0.3.1,请务必升级到ADT0.3.3,升级步骤见http://code.google.com/android/intro/upgrading.html 测试的第一步是激活主Activity,设置密码。例如,我使用“123456”作为密码,接下来,你可以发送一条内容为“SMSLOCATE:123456”的短信,如下图所示: 
??? 当你发送完短信后,你会看到一条类似下图的通知。 
好了,一切OK。我想,这是一个很好的例子。通过它,你会发现在android平台上开发一个有用的应用是多么的容易。我们通过2个基本的对象就完成了一件非常有用的任务。
?
?
注:博客涉及的源码请在千寻资源库:www.qxzyk.com 下载获取,谢谢支持。