[Android端源码下载][WCF源码下载]
前面一篇重点介绍了服务端的代码,接下来说明下Android客户端的代码,先上张图,客户端的功能组合一目了然。


1) 准备:
android里对于应用的权限控制有着严格的限制,因此根据不同的使用目的,需要在 AndroidManifest.xml 里添加用户权限(uses-permission)。在这个应用里使用了Internet访问,读取短信,发送短信,查询联系人4个主要的功能,所以提前添加下面4个权限:
<uses-permission android:name="android.permission.INTERNET"></uses-permission> <uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission> <uses-permission android:name="android.permission.SEND_SMS"></uses-permission> <uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
2) Android Http交互

Http交互当然首选org.apache.http下强大的HttpClient 类,这里先包装下封装了基本的调用方法 —— doGet 和 doPost
其中 doPost 传入 Xml/Json 字符串,按照指定的 ContentType 发出请求。
package android.study.TestApp5_SmsApp; import java.net.HttpURLConnection; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.params.HttpClientParams; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import android.util.Log; public class WebClient { private String encode; private HttpClient httpClient; private HttpParams httpParams; private int timeout; private int bufferSize; private static final String TAG = "WebClient"; public WebClient() { timeout = 20 * 1000; bufferSize = 8192; encode = "UTF-8"; httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, timeout); HttpConnectionParams.setSoTimeout(httpParams, timeout); HttpConnectionParams.setSocketBufferSize(httpParams, bufferSize); HttpClientParams.setRedirecting(httpParams, true); httpClient = new DefaultHttpClient(httpParams); } public String doGet(String url) throws Exception { return doGet(url, null); } public String doPost(String url) throws Exception { return doPost(url, null); } public String doGet(String url, Map<String, String> params) throws Exception { // 添加QueryString String paramStr = ""; if (params != null) { Iterator<Entry<String, String>> iter = params.entrySet().iterator(); while(iter.hasNext()) { Entry<String, String> entry = (Entry<String, String>)iter.next(); paramStr += "&" + entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), encode); } if (paramStr.length() > 0) paramStr.replaceFirst("&", "?"); url += paramStr; } // 创建HttpGet对象 HttpGet get = new HttpGet(url); try { String strResp = ""; // 发起请求 Log.v(TAG, "doGet:" + url); HttpResponse resp = httpClient.execute(get); if (resp.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) strResp = EntityUtils.toString(resp.getEntity()); else // 如果返回的StatusCode不是OK则抛异常 throw new Exception("Error Response:" + resp.getStatusLine().toString()); return strResp; } finally { get.abort(); } } public String doPost(String url, Map<String, String> params) throws Exception { // POST参数组装 List<NameValuePair> data = new ArrayList<NameValuePair>(); if (params != null) { Iterator<Entry<String, String>> iter = params.entrySet().iterator(); while(iter.hasNext()) { Entry<String, String> entry = (Entry<String, String>)iter.next(); data.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); } } HttpPost post = new HttpPost(url); try { // 添加请求参数到请求对象 if (params != null) post.setEntity(new UrlEncodedFormEntity(data, HTTP.UTF_8)); // 发起请求 Log.v(TAG, "doPost:" + url); HttpResponse resp = httpClient.execute(post); String strResp = ""; if (resp.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) strResp = EntityUtils.toString(resp.getEntity()); else // 如果返回的StatusCode不是OK则抛异常 throw new Exception("Error Response:" + resp.getStatusLine().toString()); return strResp; } finally { post.abort(); } } /** * @param url - 需要访问的address * @param data - Request的内容字符串 * @param contentType - Request的ContentType * @return Response的字符串 * @throws Exception */ public String doPost(String url, String data, String contentType) throws Exception { HttpPost post = new HttpPost(url); try { // 添加请求参数到请求对象 StringEntity se = new StringEntity(data, HTTP.UTF_8); se.setContentType(contentType); post.setEntity(se); // 发起请求 Log.v(TAG, "doPost:" + url); HttpResponse resp = httpClient.execute(post); String strResp = ""; if (resp.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) strResp = EntityUtils.toString(resp.getEntity()); else // 如果返回的StatusCode不是OK则抛异常 throw new Exception("Error Response:" + resp.getStatusLine().toString()); return strResp; } finally { post.abort(); } } }

3) Android Timer 的使用:(下面的代码并非实际顺序,整体代码看最后贴出来的代码)
在这个小程序里,启动后点击Button启动Timer,然后再次点击则关闭Timer(Timer.cancel)。
android timer 需要设定它的schedule,下面代码中 schedule传入的是一个 TimerTask 匿名类,它重写了run()方法。
相当于.net里的 Tick 事件处理方法。
【功能说明】在Timer中调用 doGet 方法,从WCF服务端取要发送的短信,利用SmsManager发送短信并记录LOG呈现在UI上。
// 启动Timer mTimer = new Timer(); mTimer.schedule(new TimerTask() { @Override public void run() { // 发送 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String tickInfo = df.format(new Date()).toString(); SimpleDateFormat dfForLog = new SimpleDateFormat("HH:mm:ss"); String runtimeInfo = "(" + dfForLog.format(new Date()).toString() + ")"; Message msg = new Message(); msg.what = 0; try { // 获取要发送的短信 WebClient client = new WebClient(); String result = client.doGet(mServerBaseUrl + GET_DATA); if (result.length() > 0) { JSONObject json = new JSONObject(result); String phone = json.getString("Phone"); String content = json.getString("Content"); sendSms(phone, content); msg.what = 1; runtimeInfo += "[S][OK]" + phone + ":" + content; } } catch (Exception e) { e.printStackTrace(); msg.what = 1; runtimeInfo += "[S][ERR]" + e.getMessage(); } Bundle bundle = new Bundle(); bundle.putString("tickInfo", tickInfo); bundle.putString("runtimeInfo", runtimeInfo); msg.setData(bundle); mHandler.sendMessage(msg); } }, 1000, 10000); // 延迟1000ms启动,每10000ms运行一次
和.net一样,在Timer(线程)里不能直接对UI进行操作,需要向Handler 发消息,让 Handler 通过重写handleMessage() 方法实现对UI的更新。Timer里new一个Message,再放入一个Bundle对象,Bundle就像个容器放什么都可以。而Message.what(int型)可以利用来做标志位。
// 定义Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); //handler处理消息 mTxtRuntimeInfo.setText(msg.getData().getString("tickInfo")); if(msg.what > 0){ //在handler里更改UI组件 String runtimeInfo = msg.getData().getString("runtimeInfo"); if (runtimeInfo.length() > 0) { if (mLogQueue.size() > 10) mLogQueue.poll(); String strLog = msg.getData().getString("runtimeInfo"); mLogQueue.offer(strLog); mTxtLog.setText(""); for(int i = mLogQueue.size()-1; i>0; i--) { mTxtLog.append(mLogQueue.get(i)); mTxtLog.append("/n---------/n"); } } //timer.cancel(); } } };
4) Android 的短信拦截
android中当系统收到短信会对所有应用发出广播,通过注册BroadcastReceiver的实例再重写onReceive方法就可以收听到收到的短信。(Tip:利用java中内部类可以访问外部类实例成员的特性(C#中不能),可以简化不少编码量,下面的代码中直接调用了mHandler类成员)
【功能说明】当收到一条短信则调用 doPost 方法将短信发送给WCF服务端,并记录LOG呈现在UI上。

// 实现一个 BroadcastReceiver,用于接收指定的 Broadcast // 调用 SetData 传输到 WCF 端 public class SmsReceiver extends BroadcastReceiver { private static final String ACTION_NAME = "android.provider.Telephony.SMS_RECEIVED"; @Override public void onReceive(Context context, Intent intent) { SimpleDateFormat dfForLog = new SimpleDateFormat("HH:mm:ss"); String runtimeInfo = "(" + dfForLog.format(new Date()).toString() + ")"; Message msg = new Message(); msg.what = 0; try { if (intent.getAction().equals(ACTION_NAME)) { Bundle bundle = intent.getExtras(); if (bundle != null) { Object[] pdus = (Object[]) bundle.get("pdus"); SmsMessage[] sms = new SmsMessage[pdus.length]; for (int i = 0; i < pdus.length; i++) { sms[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); } // 获取短信内容 String phone = ""; String content = ""; String name = ""; for (SmsMessage currMsg : sms) { phone = currMsg.getDisplayOriginatingAddress(); content = currMsg.getDisplayMessageBody(); } phone = phone.replace("+86", ""); // 查询联系人 Cursor curr = managedQuery(People.CONTENT_URI, new String[] { People.NAME }, People.NUMBER + " like ?", new String[] { "%" + phone }, People.NAME ); if (curr.moveToFirst()) { name = curr.getString(0); } // 将数据传到WCF端 WebClient client = new WebClient(); JSONObject json = new JSONObject(); json.put("Phone", phone); json.put("Content", content); json.put("Name", name); String result = client.doPost(mServerBaseUrl + SET_DATA, json.toString(), "application/json"); msg.what = 1; runtimeInfo += "[G][OK]" + phone + "(" + name + "):" + content; } } } catch (Exception e) { e.printStackTrace(); msg.what = 1; runtimeInfo += "[G][Err]" + e.getMessage(); } Bundle bundle1 = new Bundle(); bundle1.putString("runtimeInfo", runtimeInfo); msg.setData(bundle1); mHandler.sendMessage(msg); }
5) Android 中的Json序列化和反序列化
在上面两段代码中(Timer和Receiver),可以看到Json的序列化和反序列化的代码:
5-1) 序列化成json字符串:

JSONObject json = new JSONObject(); json.put("Phone", phone); json.put("Content", content); json.put("Name", name); String strJson = json.toString();

5-2) 反序列化成字典:

JSONObject json = new JSONObject(result); String phone = json.getString("Phone"); String content = json.getString("Content");


6) 联系人查询
这里使用的是 Activity.managedQuery() 方法,网上的说明很多了这里不一一累述。
// 查询联系人 Cursor curr = managedQuery(People.CONTENT_URI, new String[] { People.NAME }, People.NUMBER + " like ?", new String[] { "%" + phone }, People.NAME ); if (curr.moveToFirst()) { name = curr.getString(0); }

吼吼,客户端也介绍完了。再分享一些测试心得,希望能帮助到和我一样的新手们:)
7) android测试经验分享
(1) android 模拟器每次启动都很耗时,但实际不用每次都启动。修改代码保存后断开和模拟器的连接,修改后再Debug启动就可以了。
(2) 短信/电话拨打测试使用 telnet(windows7需要先在windows组件中安装telnet组件)。然后就可以按照如下进行短信测试了。
cmd > telnet localhost 5554
sms send <PhoneNo> <content>


(3) 利用 LogCat 帮助你快速发现bug。log4j,log4net大家都很常用了,在android下提供了更方便更容易查看的工具LogCat。工具类——android.util.Log(详细看这里:图解LogCat的用法)
(4) 发布测试,有时候我们常常要发布到真机上看看真实的效果,可以在真机上安装一个FTPServer,这样release之后直接copy到机器上,安装就很方便了。另外自己准备一个key文件,每次发布都用一个key可以保证覆盖安装。

最后贴上完整代码:
package android.study.TestApp5_SmsApp; import java.text.SimpleDateFormat; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import org.json.JSONObject; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.provider.Contacts.People; import android.telephony.gsm.SmsManager; import android.telephony.gsm.SmsMessage; import android.text.method.ScrollingMovementMethod; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class TestApp5_SmsApp extends Activity { private static final String BASE_URL = "http://%s/RestWcfSMSSvc"; private static final String SET_DATA = "/setdata"; private static final String GET_DATA = "/getdata"; private Handler mHandler = null; private TextView mTxtRuntimeInfo; private TextView mTxtLog; private EditText mTxtServerIP; private Button mBtnStart; private Timer mTimer; private SmsReceiver receiver; private String mServerBaseUrl; private LinkedList<String> mLogQueue; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 控件初始化 mTxtRuntimeInfo = (TextView)this.findViewById(R.id.txtRuntimeInfo); mBtnStart = (Button) this.findViewById(R.id.btnStart); mTxtServerIP = (EditText)this.findViewById(R.id.txtServerIP); mTxtLog = (TextView)this.findViewById(R.id.txtLog); mServerBaseUrl = String.format(BASE_URL, mTxtServerIP.getText()); mTxtLog.setMovementMethod(ScrollingMovementMethod.getInstance()); mLogQueue = new LinkedList<String>(); // 实例化自定义的 SmsReceiver receiver = new SmsReceiver(); // 定义Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); //handler处理消息 mTxtRuntimeInfo.setText(msg.getData().getString("tickInfo")); if(msg.what > 0){ //在handler里更改UI组件 String runtimeInfo = msg.getData().getString("runtimeInfo"); if (runtimeInfo.length() > 0) { if (mLogQueue.size() > 10) mLogQueue.poll(); String strLog = msg.getData().getString("runtimeInfo"); mLogQueue.offer(strLog); mTxtLog.setText(""); for(int i = mLogQueue.size()-1; i>0; i--) { mTxtLog.append(mLogQueue.get(i)); mTxtLog.append("/n---------/n"); } } } } }; mBtnStart.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { if (mBtnStart.getText().equals("Start")) { start(); } else { stop(); } } }); } @Override protected void onDestroy() { super.onDestroy(); mTimer.cancel(); } private void start() { try { mBtnStart.setText("Stop"); mTxtLog.setText(""); // 启动Timer mTimer = new Timer(); mTimer.schedule(new TimerTask() { @Override public void run() { // 发送 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String tickInfo = df.format(new Date()).toString(); SimpleDateFormat dfForLog = new SimpleDateFormat("HH:mm:ss"); String runtimeInfo = "(" + dfForLog.format(new Date()).toString() + ")"; Message msg = new Message(); msg.what = 0; try { // 获取要发送的短信 WebClient client = new WebClient(); String result = client.doGet(mServerBaseUrl + GET_DATA); if (result.length() > 0) { JSONObject json = new JSONObject(result); String phone = json.getString("Phone"); String content = json.getString("Content"); sendSms(phone, content); msg.what = 1; runtimeInfo += "[S][OK]" + phone + ":" + content; } } catch (Exception e) { e.printStackTrace(); msg.what = 1; runtimeInfo += "[S][ERR]" + e.getMessage(); } Bundle bundle = new Bundle(); bundle.putString("tickInfo", tickInfo); bundle.putString("runtimeInfo", runtimeInfo); msg.setData(bundle); mHandler.sendMessage(msg); } }, 1000, 10000); // 延迟1000ms启动,每10000ms运行一次 IntentFilter filter = new IntentFilter(); // 为 BroadcastReceiver 指定 action ,使之用于接收同 action 的广播 filter.addAction("android.provider.Telephony.SMS_RECEIVED"); // 注册 this.registerReceiver(receiver, filter); } catch (Exception e) { e.printStackTrace(); disp(e.getMessage()); } } private void stop() { mBtnStart.setText("Start"); if (mTimer != null) { mTimer.cancel(); } if (receiver != null) { // 取消注册 this.unregisterReceiver(receiver); } } private void disp(String msg) { Toast.makeText(this.getApplicationContext(), msg, Toast.LENGTH_LONG).show(); } private boolean sendSms(String phone, String content) { SmsManager sms = SmsManager.getDefault(); List<String> texts = sms.divideMessage(content); //逐条发送短信 for(String text:texts) { sms.sendTextMessage(phone, null, text, null, null); } return true; } // 实现一个 BroadcastReceiver,用于接收指定的 Broadcast // 调用 SetData 传输到 WCF 端 public class SmsReceiver extends BroadcastReceiver { private static final String ACTION_NAME = "android.provider.Telephony.SMS_RECEIVED"; @Override public void onReceive(Context context, Intent intent) { SimpleDateFormat dfForLog = new SimpleDateFormat("HH:mm:ss"); String runtimeInfo = "(" + dfForLog.format(new Date()).toString() + ")"; Message msg = new Message(); msg.what = 0; try { if (intent.getAction().equals(ACTION_NAME)) { Bundle bundle = intent.getExtras(); if (bundle != null) { Object[] pdus = (Object[]) bundle.get("pdus"); SmsMessage[] sms = new SmsMessage[pdus.length]; for (int i = 0; i < pdus.length; i++) { sms[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); } // 获取短信内容 String phone = ""; String content = ""; String name = ""; for (SmsMessage currMsg : sms) { phone = currMsg.getDisplayOriginatingAddress(); content = currMsg.getDisplayMessageBody(); } phone = phone.replace("+86", ""); // 查询联系人 Cursor curr = managedQuery(People.CONTENT_URI, new String[] { People.NAME }, People.NUMBER + " like ?", new String[] { "%" + phone }, People.NAME ); if (curr.moveToFirst()) { name = curr.getString(0); } // 将数据传到WCF端 WebClient client = new WebClient(); JSONObject json = new JSONObject(); json.put("Phone", phone); json.put("Content", content); json.put("Name", name); String result = client.doPost(mServerBaseUrl + SET_DATA, json.toString(), "application/json"); msg.what = 1; runtimeInfo += "[G][OK]" + phone + "(" + name + "):" + content; } } } catch (Exception e) { e.printStackTrace(); msg.what = 1; runtimeInfo += "[G][Err]" + e.getMessage(); } Bundle bundle1 = new Bundle(); bundle1.putString("runtimeInfo", runtimeInfo); msg.setData(bundle1); mHandler.sendMessage(msg); } } }

更多相关文章

  1. Android(安卓)Activity生命周期具体内容概述
  2. android framework 启动流程
  3. 安卓虚拟机启动后报错: 类似 SDK Manager] Error: Error parsing
  4. Android应用程序进程启动过程的源代码分析(1)
  5. 【android内核分析-启动】Android(安卓)启动过程详解
  6. Android使用Java的第三方jar注意事项
  7. Android(安卓)学习笔记——利用JNI技术在Android中调用、调试C++
  8. Android(安卓)API之Typeface代码演示
  9. 在android的webview中实现websocket

随机推荐

  1. c语言while循环例子
  2. c语言中除号用什么表示
  3. net framework 4.0安装未成功原因是hresu
  4. C 语言中 strlen 是什么意思?
  5. c语言如何定义字符串数组
  6. c语言统计字符串中各个字符的个数
  7. c语言实现向有序数组中插入一个数并保持
  8. c++中main函数的返回值类型是什么
  9. c++是什么意思
  10. c语言中的“?:”是什么运算符