惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
J
Java Code Geeks
L
LangChain Blog
V
V2EX
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
博客园 - Franky
Microsoft Azure Blog
Microsoft Azure Blog
Jina AI
Jina AI
Blog — PlanetScale
Blog — PlanetScale
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The Cloudflare Blog
博客园 - 司徒正美
B
Blog
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
MyScale Blog
MyScale Blog
有赞技术团队
有赞技术团队
Hugging Face - Blog
Hugging Face - Blog

博客园 - dodo-yufan

Win server 2012 +IIS8.0下安装SSL证书 无法打开运行空间池,服务器管理器winrm插件可能已损坏或丢失 php: zend server 安装及相关配置 android: 后台执行的定时任务 android: 使用 IntentService android: 使用前台服务 android: 服务的生命周期 android: 活动和服务进行通信 android: 服务的基本用法 android: 使用 AsyncTask android: 多线程编程基础 传递给数据库 'master' 中的日志扫描操作的日志扫描号无效 android: 播放视频 android: 播放音频 android: 从相册中选择照片 android: 调用摄像头拍照 android: 将程序运行到手机上 android: 使用通知 android: open failed: EACCES (Permission denied)
android: 接收和发送短信
dodo-yufan · 2016-03-31 · via 博客园 - dodo-yufan

8.2    接收和发送短信

收发短信应该是每个手机最基本的功能之一了,即使是许多年前的老手机也都会具备这 项功能,而 Android 作为出色的智能手机操作系统,自然也少不了在这方面的支持。每个 Android 手机都会内置一个短信应用程序,使用它就可以轻松地完成收发短信的操作,如 图 8.4 所示。

图   8.4

不过作为一名开发者,仅仅满足于此显然是不够的。你要知道,Android 还提供了一系 列的 API,使得我们甚至可以在自己的应用程序里接收和发送短信。也就是说,只要你有足 够的信心,完全可以自己实现一个短信应用来替换掉 Android 系统自带的短信应用。那么下 面我们就来看一看,如何才能在自己的应用程序里接收和发送短信。

8.2.1    接收短信

其实接收短信主要是利用了我们在第 5 章学习过的广播机制。当手机接收到一条短信的 时候,系统会发出一条值为 android.provider.Telephony.SMS_RECEIVED 的广播,这条广播里 携带着与短信相关的所有数据。每个应用程序都可以在广播接收器里对它进行监听,收到广 播时再从中解析出短信的内容即可。

让我们通过一个具体的例子来实践一下吧,新建一个 SMSTest 项目,首先修改 activity_

main.xml 中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"

android:orientation="vertical" >

<LinearLayout android:layout_width="match_parent"

android:layout_height="50dp" >

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:padding="10dp" android:text="From:" />

<TextView android:id="@+id/sender" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" />

</LinearLayout>

<LinearLayout android:layout_width="match_parent" android:layout_height="50dp" >

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:padding="10dp" android:text="Content:" />

<TextView android:id="@+id/content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" />

</LinearLayout>

</LinearLayout>

这个布局文件里,我们在根元素下面放置了两个 LinearLayout,用于显示两行数据。第 一个 LinearLayout 中有两个 TextView,用于显示短信的发送方。第二个 LinearLayout 中也有 两个 TextView,用于显示短信的内容。

接着修改 MainActivity 中的代码,在 onCreate()方法中获取到两个 TextView 的实例,如下所示:

public class MainActivity extends Activity {

private TextView sender;

private TextView content;

@Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);

sender = (TextView) findViewById(R.id.sender);

content = (TextView) findViewById(R.id.content);

}

}

然后我们需要创建一个广播接收器来接收系统发出的短信广播。在 MainActivity 中新建 MessageReceiver 内部类继承自 BroadcastReceiver,并在 onReceive()方法中编写获取短信数 据的逻辑,代码如下所示:

public class MainActivity extends Activity {

……

class MessageReceiver extends BroadcastReceiver {

@Override

public void onReceive(Context context, Intent intent) { Bundle bundle = intent.getExtras();

Object[] pdus = (Object[]) bundle.get("pdus"); // 提取短信消息

SmsMessage[] messages = new SmsMessage[pdus.length];

for (int i = 0; i < messages.length; i++) {

messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);

送方号码

}

String address = messages[0].getOriginatingAddress(); // 获取发

String fullMessage = "";

for (SmsMessage message : messages) {

fullMessage += message.getMessageBody(); // 获取短信内容

} sender.setText(address); content.setText(fullMessage);

}

}

}

可以看到,首先我们从 Intent 参数中取出了一个 Bundle 对象,然后使用 pdu 密钥来提取 一个 SMS pdus 数组,其中每一个 pdu 都表示一条短信消息。接着使用 SmsMessage 的 createFromPdu() 方法将每一个 pdu 字节数组转换为 SmsMessage 对象,调用这个对象的 getOriginatingAddress()方法就可以获取到短信的发送方号码,调用 getMessageBody()方法就 可以获取到短信的内容,然后将每一个 SmsMessage 对象中的短信内容拼接起来,就组成了 一条完整的短信。最后将获取到的发送方号码和短信内容显示在 TextView 上。

完成了 MessageReceiver 之后,我们还需要对它进行注册才能让它接收到短信广播,代 码如下所示:

 public class MainActivity extends Activity {

private TextView sender;

private TextView content;

private IntentFilter receiveFilter;

private MessageReceiver messageReceiver;

@Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);

sender = (TextView) findViewById(R.id.sender); content = (TextView) findViewById(R.id.content); receiveFilter = new IntentFilter();

receiveFilter.addAction("android.provider.Telephony.SMS_RECEIVED");

messageReceiver = new MessageReceiver();

registerReceiver(messageReceiver, receiveFilter);

}

@Override

protected void onDestroy() {

super.onDestroy();

unregisterReceiver(messageReceiver);

}

……

}

这些代码你应该都已经非常熟悉了,使用的就是动态注册广播的技术。在 onCreate()方 法中对 MessageReceiver 进行注册,在 onDestroy()方法中再对它取消注册。

代码到这里就已经完成得差不多了,不过最后我们还需要给程序声明一个接收短信的权 限才行,修改 AndroidManifest.xml 中的代码,如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.smstest"

android:versionCode="1" android:versionName="1.0" >

<uses-permission android:name="android.permission.RECEIVE_SMS" />

……

</manifest>

现在可以来运行一下程序了,界面如图 8.5 所示。

图   8.5

当有短信到来时,短信的发送方和内容就会显示在界面上。不过话说回来,我们使用的是模拟器,模拟器上怎么可能会收得到短信呢?不用担心,DDMS 提供了非常充分的模拟环

境,使得我们不需要支付真正的短信费用也可以模拟收发短信的场景。将 Eclipse 切换到 DDMS 视图下,然后点击 Emulator Control 切换卡,在这里就可以向模拟器发送短信了,如 图 8.6 所示。

 

图   8.6

可以看到,我们指定发送方的号码是 556677,并填写了一段短信内容,然后点击 Send按钮,这样短信就发送成功了。接着我们立马查看一下 SMSTest 这个程序,结果如图 8.7 所示。

 

图   8.7

可以看到,短信的发送方号码和短信内容都显示到界面上了,说明接收短信的功能成功 实现了。

8.2.2    拦截短信

仔细观察图 8.7,你会发现在系统状态栏出现了一个通知图标,这个通知图标是由 Android 自带的短信程序产生的。也就是说当短信到来时,不仅我们的程序会接收到这条短信,系统 的短信程序同样也会收到。同样一条短信被重复接收两遍就会造成比较差的用户体验,那么 有没有什么办法可以屏蔽系统短信程序的接收功能呢?

在前面 5.3.2 节学习有序广播的时候我们就已经知道,有序广播的传递是可以截断的, 而系统发出的短信广播正是一条有序广播,因此这里我们的答案是肯定的。修改 MainActivity 中的代码,如下所示:

public class MainActivity extends Activity {

……

@Override

protected void onCreate(Bundle savedInstanceState) {

……

receiveFilter = new IntentFilter(); receiveFilter.addAction("android.provider.Telephony.SMS_RECEIVED"); receiveFilter.setPriority(100);

messageReceiver = new MessageReceiver();

registerReceiver(messageReceiver, receiveFilter);

}

……

class MessageReceiver extends BroadcastReceiver {

@Override

public void onReceive(Context context, Intent intent) {

……

abortBroadcast();

}

}

}

可以看到,关键性的步骤只有两步。一是提高 MessageReceiver 的优先级,让它能够先 于系统短信程序接收到短信广播。二是在 onReceive()方法中调用 abortBroadcast()方法,中止掉广播的继续传递。

现在重新运行程序,再向模拟器发送一条短信,这时只有我们自己的程序才能收到这条 短信了。按下 Back 键将程序关闭后,系统的短信程序又会重新拥有接收短信的功能。

注意这个功能一定要慎用,随意拦截短信有可能会造成重要数据的丢失,所以你在拦截 之前一定要先想清楚这种功能是不是你想要的。

8.2.3    发送短信

下面我们继续对 SMSTest 项目进行扩展,给它加上发送短信的功能。那么还是先来编写 一下布局文件吧,修改 activity_main.xml 中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"

android:orientation="vertical" >

……

<LinearLayout android:layout_width="match_parent" android:layout_height="50dp" >

<TextView

android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:padding="10dp"

android:text="To:" />

<EditText android:id="@+id/to" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" />

</LinearLayout>

<LinearLayout android:layout_width="match_parent" android:layout_height="50dp" >

<EditText

android:id="@+id/msg_input" android:layout_width="0dp"

android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_weight="1" />

<Button android:id="@+id/send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:text="Send" />

</LinearLayout>

</LinearLayout>

这里我们又新增了两个 LinearLayout,分别处于第三和第四行的位置。第三行中放置了 一个 EditText,用于输入接收方的手机号码。第四行中放置了一个 EditText 和一个 Button, 分别用于输入短信内容和发送短信。

然后修改 MainActivity 中的代码,在里面加入发送短信的处理逻辑,代码如下所示:

public class MainActivity extends Activity {

……

private EditText to; private EditText msgInput; private Button send;

@Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);

……

to = (EditText) findViewById(R.id.to);

msgInput = (EditText) findViewById(R.id.msg_input); send = (Button) findViewById(R.id.send); send.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

SmsManager smsManager = SmsManager.getDefault();

smsManager.sendTextMessage(to.getText().toString(), null,

}

});

}

……

}

msgInput.getText().toString(), null, null);

可以看到,首先我们获取到了布局文件中新增控件的实例,然后在 Send 按钮的点击事 件里面处理了发送短信的具体逻辑。当 Send 按钮被点击时,会先调用 SmsManager 的 getDefault()方法获取到 SmsManager 的实例,然后再调用它的 sendTextMessage()方法就可以 去发送短信了。sendTextMessage()方法接收五个参数,其中第一个参数用于指定接收人的手 机号码,第三个参数用于指定短信的内容,其他的几个参数我们暂时用不到,直接传入 null 就可以了。

接下来也许你已经猜到了,发送短信也是需要声明权限的,因此修改 AndroidManifest.xml中的代码,如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.smstest"

android:versionCode="1"

android:versionName="1.0" >

<uses-permission android:name="android.permission.RECEIVE_SMS" />

<uses-permission android:name="android.permission. SEND_SMS" />

……

</manifest>

现在重新运行程序之后,SMSTest 就拥有了发送短信的能力。不过点击 Send 按钮虽然 可以将短信发送出去,但是我们并不知道到底发送成功了没有,这个时候就可以利用 sendTextMessage()方法的第四个参数来对短信的发送状态进行监控。修改 MainActivity 中的 代码,如下所示:

public class MainActivity extends Activity {

……

private IntentFilter sendFilter;

private SendStatusReceiver sendStatusReceiver;

@Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);

……

sendFilter = new IntentFilter(); sendFilter.addAction("SENT_SMS_ACTION"); sendStatusReceiver = new SendStatusReceiver(); registerReceiver(sendStatusReceiver, sendFilter); send.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

SmsManager smsManager = SmsManager.getDefault(); Intent sentIntent = new Intent("SENT_SMS_ACTION"); PendingIntent pi = PendingIntent.getBroadcast

(MainActivity.this, 0, sentIntent, 0);

smsManager.sendTextMessage(to.getText().toString(), null, msgInput.getText().toString(), pi, null);

}

});

}

@Override

protected void onDestroy() { super.onDestroy(); unregisterReceiver(messageReceiver);

unregisterReceiver(sendStatusReceiver);

}

……

class SendStatusReceiver extends BroadcastReceiver {

@Override

public void onReceive(Context context, Intent intent) {

if (getResultCode() == RESULT_OK) {

// 短信发送成功

Toast.makeText(context, "Send succeeded", Toast.LENGTH_LONG).show();

} else {

// 短信发送失败

Toast.makeText(context, "Send failed", Toast.LENGTH_LONG).show();

}

}

}

}

可以看到,在 Send 按钮的点击事件里面我们调用了 PendingIntent 的 getBroadcast()方法 获取到了一个 PendingIntent 对象,并将它作为第四个参数传递到 sendTextMessage()方法中。 然后又注册了一个新的广播接收器 SendStatusReceiver,这个广播接收器就是专门用于监听 短信发送状态的,当 getResultCode()的值等于 RESULT_OK 就会提示发送成功,否则提示发 送失败。

现在重新运行一下程序,在文本输入框里输入接收方的手机号码以及短信内容,然后点 击 Send 按钮,结果如图 8.8 所示。

 

图   8.8

注意,这里虽然提示发送成功了,但实际上使用模拟器来发送短信对方是不可能收得到 的,只有把这个项目运行在手机上,才能真正地实现发送短信的功能。

另外,根据国际标准,每条短信的长度不得超过 160 个字符,如果想要发送超出这个长 度的短信,则需要将这条短信分割成多条短信来发送,使用 SmsManager 的 sendMultipart- TextMessage()方法就可以实现上述功能。它的用法和 sendTextMessage()方法也基本类似,感 兴趣的话你可以自己研究一下,这里就不再展开讲解了。