本文来自http://blog.csdn.net/hellogv/
前面写了十四篇关于界面的入门文章,大家都看完和跟着练习之后,对于常用的Layout和View都会有一定的了解了,接下来的文章就不再强调介绍界面了,而是针对具体的常见功能而展开。
本文介绍MediaPlayer的使用。MediaPlayer可以播放音频和视频,另外也可以通过VideoView来播放视频,虽然VideoView 比MediaPlayer简单易用,但定制性不如用MediaPlayer,要视情况选择了。MediaPlayer播放音频比较简单,但是要播放视频就需要SurfaceView。SurfaceView比普通的自定义View更有绘图上的优势,它支持完全的OpenGL ES库。
先贴出本文程序运行结果的截图,上面是播放/停止音频,可用SeekBar来调进度,下面是播放/停止视频,也是用SeekBar来调进度: main.xml的源码:
本文程序的源码,有点长:
上次介绍MediaPlayer的时候稍微介绍了SurfaceView,SurfaceView由于可以直接从内存或者DMA等硬件接口取得图像数据,因此是个非常重要的绘图容器,这次我就用两篇文章来介绍SurfaceView的用法。网上介绍SurfaceView的用法有很多,写法也层出不同,例如继承SurfaceView类,或者继承SurfaceHolder.Callback类等,这个可以根据功能实际需要自己选择,我这里就直接在普通的用户界面调用 SurfaceHolder的lockCanvas和unlockCanvasAndPost。
先来看看程序运行的截图:
截图1主要演示了直接把正弦波绘画在SurfaceView上
对比上面的左右两图,右图用.lockCanvas(null),而左图用.lockCanvas(new Rect(oldX, 0, oldX + length,
getWindowManager().getDefaultDisplay().getHeight())),对比一下两个效果,由于左图是按指定Rect绘画,所以效率会比右图的全控件绘画高些,并且在清屏之后 (canvas.drawColor(Color.BLACK))不会留有上次绘画的残留。
接下来贴出main.xml的源码:
接下来贴出程序源码:
注意一下 for (int i = oldX + 1; i < length; i++) {// 绘画正弦波 这句,在.lockCanvas()指定Rect内减少循环画线的次数,可以提高绘图效率。
上一篇简单介绍了SurfaceView的使用,这次就介绍SurfaceView的双缓冲使用。双缓冲是为了防止动画闪烁而实现的一种多线程应用,基于 SurfaceView的双缓冲实现很简单,开一条线程并在其中绘图即可。本文介绍基于SurfaceView的双缓冲实现,以及介绍类似的更高效的实现方法。
本文程序运行截图如下,左边是开单个线程读取并绘图,右边是开两个线程,一个专门读取图片,一个专门绘图:
对比一下,右边动画的帧速明显比左边的快,左右两者都没使用Thread.sleep()。为什么要开两个线程一个读一个画,而不去开两个线程像左边那样都 “边读边画”呢?因为SurfaceView每次绘图都会锁定Canvas,也就是说同一片区域这次没画完下次就不能画,因此要提高双缓冲的效率,就得开一条线程专门画图,开另外一条线程做预处理的工作。
main.xml的源码:
本文程序的源码:
Android有三个基础组件Activity,Service和BroadcastReceiver,他们都是依赖Intent来启动。本文介绍的是Activity的生命周期以及针对Activity的Intent使用。
之前的例子一直都是使用Activity,在一个Layout XML与一个Activity捆绑的情况下可以视为一个Form,多个Layout XML与一个Activity捆绑的话那就是个Application本身了。Intent可以分为显式Intent和隐式Intent:显式 Intent用于启动明确的目标组件(前面所说的三大组件),同一个Application内的多个Activity调用也是显式Intent;隐式 Intent就是调用没有明确的目标组件,可以是系统也可以是第三方程序。隐式Intent一般用于调用系统组件功能,相关例程都是网络上很容易找到的(调用某些系统组件的时候要申请权限)。
Acitivity的运行状况分为:onCreate、onDestroy、onStart、onStop、onRestart、onResume、 onPause,onCreate对应onDestroy,onStart对应onStop,onResume对应onPause。
先贴出本文运行截图:
这个是从Acitivity1转到Activity2的时候,Acitivity1的状态变化,使用了finish()会触发onDestroy()。
这个是从Activity2转到Activity1的时候,Acitivity2的状态变化。从两次Activity的启动可以看出,流程是 onCreate()->onStart()->onResume()三个方法,销毁是 onPause()->onStop()->onDestroy()。另外,要往工程添加第二个Activity,需要到AndroidManifest.xml->Application那里添加Activity2。
main1.xml的源码:
main2.xml的源码:
Activity1的源码:
1. package com.testActivityIntent;
2. import android.app.Activity;
3. import android.content.Intent;
4. import android.content.SharedPreferences;
5. import android.net.Uri;
6. import android.os.Bundle;
7. import android.util.Log;
8. import android.view.View;
9. import android.widget.Button;
10. import android.widget.EditText;
11. public class testActivityIntent extends Activity {
12. /** Called when the activity is first created. */
13. Button btnToInternalActivity;
14. Button btnToExternalActivity;
15. EditText tbBundle;
16. @Override
17. public void onCreate(Bundle savedInstanceState) {
18. super.onCreate(savedInstanceState);
19. Log.e("Activity1", "onCreate");//显示当前状态,onCreate与onDestroy对应
20. setContentView(R.layout.main1);
21.
22. btnToInternalActivity=(Button)this.findViewById(R.id.main1_Button01);
23. btnToExternalActivity=(Button)this.findViewById(R.id.main1_Button02);
24. btnToInternalActivity.setOnClickListener(new ClickEvent());
25. btnToExternalActivity.setOnClickListener(new ClickEvent());
26. tbBundle=(EditText)this.findViewById(R.id.EditText01);
27. }
28. public void onDestroy()
29. {
30. super.onDestroy();
31. Log.e("Activity1", "onDestroy");//显示当前状态,onCreate与onDestroy对应
32. }
33. @Override
34. public void onStart()
35. {
36. super.onStart();
37. Log.e("Activity1", "onStart");//显示当前状态,onStart与onStop对应
38. }
39. @Override
40. public void onStop()
41. {
42. super.onStop();
43. Log.e("Activity1", "onStop");//显示当前状态,onStart与onStop对应
44. }
45. @Override
46. public void onRestart()
47. {
48. super.onRestart();
49. Log.e("Activity1", "onRestart");
50. }
51. @Override
52. public void onResume()
53. {
54. super.onResume();
55. Log.e("Activity1", "onResume");//显示当前状态,onPause与onResume对应
56. SharedPreferences prefs = getPreferences(0); //SharedPreferences 用于存储数据
57. String restoredText = prefs.getString("editText01", null);
58. if (restoredText != null) {
59. this.tbBundle.setText(restoredText);
60. }
61. }
62. @Override
63. public void onPause()
64. {
65. super.onResume();
66. Log.e("Activity1", "onPause");//显示当前状态,onPause与onResume对应
67. //保存文本框的内容,使得重回本Acitivity的时候可以恢复
68. SharedPreferences.Editor editor = getPreferences(0).edit();//SharedPreferences 用于存储数据
69. editor.putString("editText01", this.tbBundle.getText().toString());
70. editor.commit();
71. }
72.
73. class ClickEvent implements View.OnClickListener{
74. @Override
75. public void onClick(View v) {
76. if(v==btnToInternalActivity)
77. {
78. Intent intent = new Intent();
79. intent.setClass(testActivityIntent.this,Activity2.class);
80.
81. //new一个Bundle对象,并将要传递的数据传入
82. Bundle bundle = new Bundle();
83. bundle.putString("Text",tbBundle.getText().toString());
84.
85. //将Bundle对象assign给Intent
86. intent.putExtras(bundle);
87.
88. //调用Activity2
89. startActivity(intent);
90.
91. testActivityIntent.this.finish();//会触发onDestroy();
92. }
93. else if(v==btnToExternalActivity)
94. {
95. //有些外部调用需要开启权限
96. Uri uri = Uri.parse("http://google.com");
97. Intent it = new Intent(Intent.ACTION_VIEW, uri);
98. startActivity(it);
99. }
100.
101. }
102.
103. }
104.
105. }
Activity2的源码:
1. package com.testActivityIntent;
2. import android.app.Activity;
3. import android.content.Intent;
4. import android.os.Bundle;
5. import android.util.Log;
6. import android.view.View;
7. import android.widget.Button;
8. public class Activity2 extends Activity {
9. Button btnBackMain1;
10. public void onCreate(Bundle savedInstanceState)
11. {
12. super.onCreate(savedInstanceState);
13. Log.e("Activity2", "onCreate");//显示当前状态,onCreate与onDestroy对应
14.
15. //加载activity2.xml
16. setContentView(R.layout.main2);
17.
18. //得Intent中的Bundle对象
19. Bundle bunde = this.getIntent().getExtras();
20. //取得Bundle对象中的数据
21. Log.i("In_Text", bunde.getString("Text"));
22. btnBackMain1=(Button)this.findViewById(R.id.main2_Button01);
23. btnBackMain1.setOnClickListener(new ClickEvent());
24. }
25.
26. public void onDestroy()
27. {
28. super.onDestroy();
29. Log.e("Activity2", "onDestroy");//显示当前状态,onCreate与onDestroy对应
30. }
31. @Override
32. public void onStart()
33. {
34. super.onStart();
35. Log.e("Activity2", "onStart");//显示当前状态,onStart与onStop对应
36. }
37. @Override
38. public void onStop()
39. {
40. super.onStop();
41. Log.e("Activity2", "onStop");//显示当前状态,onStart与onStop对应
42. }
43. @Override
44. public void onRestart()
45. {
46. super.onRestart();
47. Log.e("Activity2", "onRestart");
48. }
49. @Override
50. public void onResume()
51. {
52. super.onResume();
53. Log.e("Activity2", "onResume");//显示当前状态,onPause与onResume对应
54. }
55. @Override
56. public void onPause()
57. {
58. super.onResume();
59. Log.e("Activity2", "onPause");//显示当前状态,onPause与onResume对应
60. }
61.
62. class ClickEvent implements View.OnClickListener{
63. @Override
64. public void onClick(View v) {
65. if(v==btnBackMain1)
66. {
67.
68. Intent intent = new Intent();
69. intent.setClass(Activity2.this,testActivityIntent.class);
70.
71. //调用Activity1
72. startActivity(intent);
73.
74. Activity2.this.finish();//会触发onDestroy();
75. }
76.
77. }
78.
79. }
80. }
上次介绍了Activity以及Intent的使用,这次就介绍Service,如果把Activity比喻为前台程序,那么Service就是后台程序,Service的整个生命周期都只会在后台执行。 Service跟Activity一样也由Intent调用。在工程里想要添加一个Service,先新建继承Service的类,然后到 AndroidManifest.xml -> Application ->Application Nodes中的Service标签中添加。
Service要由Activity通过startService 或者 bindService来启动,Intent负责传递参数。先贴出本文程序运行截图:
本文主要讲解Service的调用,以及其生命周期。
上图是startService之后再stopService的Service状态变化。
上图是bindService之后再unbindService的Service状态变化。
startService与bindService都可以启动Service,那么它们之间有什么区别呢?它们两者的区别就是使Service的周期改变。由 startService启动的Service必须要有stopService来结束Service,不调用stopService则会造成 Activity结束了而Service还运行着。bindService启动的Service可以由unbindService来结束,也可以在 Activity结束之后(onDestroy)自动结束。
上图是startService之后再Activity.finish()的Service状态变化,Service还在跑着。
上图是bindService之后再Activity.finish()的Service状态变化,Service最后自动unbindService。
main.xml代码:
testService.java的源码:
MyService.java的源码:
前面分别讨论了Activity和Service,这次就轮到BroastcastReceiver,Broastcast是应用程序间通信的手段。BroastcastReceiver也是跟 Intent紧密相连的,动态/静态注册了BroastcastReceiver之后,使用sendBroadcast把Intent发送之后,系统会自动把符合条件的BroastcastReceiver启动,跟嵌入式系统的中断类似。
本文主要演示了如何静态/动态注册BroastcastReceiver,向系统索取电量信息,以及枚举信息的字段。本文运行截图如下:
上图是发送Intent至内部动态注册的BroadcastReceiver,接收到之后显示消息名称。动态注册BroadcastReceiver用到registerReceiver()。
上图是发送Intent至内部静态注册的BroadcastReceiver,接收到之后显示消息名称。静态注册比动态注册麻烦点,先新建一个类继承BroadcastReceiver,然后到AndroidManifest.xml 添加 第一个name是类名,第二个是action的名称。
上图是枚举Intent消息的字段,这个功能比较适合懒人,把收到的Intent消息的字段全部分解了,再看看哪个需要的,懒得记住。实现这部分的代码如下: main.xml的代码如下: testBroadcastReceiver.java的代码如下: clsReceiver2.java的代码如下:
本文使用SAX来解析XML,在Android里面可以使用SAX和DOM,DOM需要把整个XML文件读入内存再解析,比较消耗内存,而SAX基于事件驱动的处理方式,可以在各节点触发回调函数,不过SAX适合节点结构简单的XML文档,复杂的XML文档在后期的节点深度处理会有点麻烦。
本文要解析的test.xml文件如下:
解析如上XML的结果如下:
使用SAX解析,需要定义SAXParserFactory(使应用程序能够配置和获取基于 SAX 的解析器以解析 XML 文档),SAXParser(从各种输入源解析 XML),XMLReader(使用回调函数读取 XML 文档),其中XMLReader是个关键。XMLReader可以为解析XML定义各种回调函数,“条件符合”的时候触发这些回调函数。
在这段代码里,XMLReader就调用继承DefaultHandler的SAXHandler。DefaultHandler已实现 ContentHandler, DTDHandler, EntityResolver, ErrorHandler等接口,包含常见读取XML的操作,具体请看下面的SAXHandler.java源码。
生成XML的结果如下:
上图是读取各节点之后,使用XmlSerializer重新组合并输出XML字符串。
本文的main.xml代码如下:
SAXHandler.java的源码如下:
testSAX.java的源码如下:
ndroid 包含了常用于嵌入式系统的SQLite,免去了开发者自己移植安装的功夫。SQLite 支持多数 SQL92 标准,很多常用的SQL命令都能在SQLite上面使用,除此之外Android还提供了一系列自定义的方法去简化对SQLite数据库的操作。不过有跨平台需求的程序就建议使用标准的SQL语句,毕竟这样容易在多个平台之间移植。
先贴出本文程序运行的结果:
本文主要讲解了SQLite的基本用法,如:创建数据库,使用SQL命令查询数据表、插入数据,关闭数据库,以及使用GridView实现了一个分页栏(关于GridView的用法),用于把数据分页显示。
分页栏的pagebuttons.xml的源码如下:
main.xml的源码如下
本文程序源码如下:
1. package com.testSQLite;
2.
3. import java.util.ArrayList;
4. import java.util.HashMap;
5. import android.app.Activity;
6. import android.database.Cursor;
7. import android.database.SQLException;
8. import android.database.sqlite.SQLiteDatabase;
9. import android.os.Bundle;
10. import android.util.Log;
11. import android.view.View;
12. import android.widget.AdapterView;
13. import android.widget.AdapterView.OnItemClickListener;
14. import android.widget.Button;
15. import android.widget.EditText;
16. import android.widget.GridView;
17. import android.widget.SimpleAdapter;
18.
19. public class testSQLite extends Activity {
20. /** Called when the activity is first created. */
21. Button btnCreateDB, btnInsert, btnClose;
22. EditText edtSQL;//显示分页数据
23. SQLiteDatabase db;
24. int id;//添加记录时的id累加标记,必须全局
25. static final int PageSize=10;//分页时,每页的数据总数
26. private static final String TABLE_NAME = "stu";
27. private static final String ID = "id";
28. private static final String NAME = "name";
29.
30. SimpleAdapter saPageID;// 分页栏适配器
31. ArrayList<HashMap<String, String>> lstPageID;// 分页栏的数据源,与PageSize和数据总数相关
32.
33. @Override
34. public void onCreate(Bundle savedInstanceState) {
35. super.onCreate(savedInstanceState);
36. setContentView(R.layout.main);
37. btnCreateDB = (Button) this.findViewById(R.id.btnCreateDB);
38. btnCreateDB.setOnClickListener(new ClickEvent());
39.
40. btnInsert = (Button) this.findViewById(R.id.btnInsertRec);
41. btnInsert.setOnClickListener(new ClickEvent());
42.
43. btnClose = (Button) this.findViewById(R.id.btnClose);
44. btnClose.setOnClickListener(new ClickEvent());
45.
46. edtSQL=(EditText)this.findViewById(R.id.EditText01);
47.
48. GridView gridview = (GridView) findViewById(R.id.gridview);//分页栏控件
49. // 生成动态数组,并且转入数据
50. lstPageID = new ArrayList<HashMap<String, String>>();
51.
52. // 生成适配器的ImageItem <====> 动态数组的元素,两者一一对应
53. saPageID = new SimpleAdapter(testSQLite.this, // 没什么解释
54. lstPageID,// 数据来源
55. R.layout.pagebuttons,//XML实现
56. new String[] { "ItemText" },
57. new int[] { R.id.ItemText });
58.
59. // 添加并且显示
60. gridview.setAdapter(saPageID);
61. // 添加消息处理
62. gridview.setOnItemClickListener(new OnItemClickListener(){
63.
64. @Override
65. public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
66. long arg3) {
67. LoadPage(arg2);//根据所选分页读取对应的数据
68. }
69. });
70.
71. }
72.
73.
74. class ClickEvent implements View.OnClickListener {
75.
76. @Override
77. public void onClick(View v) {
78. if (v == btnCreateDB) {
79. CreateDB();
80. } else if (v == btnInsert) {
81. InsertRecord(16);//插入16条记录
82. RefreshPage();
83. }else if (v == btnClose) {
84. db.close();
85. }
86. }
87.
88. }
89.
90.
91. /*
92. * 读取指定ID的分页数据
93. * SQL:Select * From TABLE_NAME Limit 9 Offset 10;
94. * 表示从TABLE_NAME表获取数据,跳过10行,取9行
95. */
96. void LoadPage(int pageID)
97. {
98. String sql= "select * from " + TABLE_NAME +
99. " Limit "+String.valueOf(PageSize)+ " Offset " +String.valueOf(pageID*PageSize);
100. Cursor rec = db.rawQuery(sql, null);
101.
102. setTitle("当前分页的数据总数:"+String.valueOf(rec.getCount()));
103.
104. // 取得字段名称
105. String title = "";
106. int colCount = rec.getColumnCount();
107. for (int i = 0; i < colCount; i++)
108. title = title + rec.getColumnName(i) + " ";
109.
110.
111. // 列举出所有数据
112. String content="";
113. int recCount=rec.getCount();
114. for (int i = 0; i < recCount; i++) {//定位到一条数据
115. rec.moveToPosition(i);
116. for(int ii=0;ii<colCount;ii++)//定位到一条数据中的每个字段
117. {
118. content=content+rec.getString(ii)+" ";
119. }
120. content=content+"\r\n";
121. }
122.
123. edtSQL.setText(title+"\r\n"+content);//显示出来
124. rec.close();
125. }
126.
127. /*
128. * 在内存创建数据库和数据表
129. */
130. void CreateDB() {
131. // 在内存创建数据库
132. db = SQLiteDatabase.create(null);
133. Log.e("DB Path", db.getPath());
134. String amount = String.valueOf(databaseList().length);
135. Log.e("DB amount", amount);
136. // 创建数据表
137. String sql = "CREATE TABLE " + TABLE_NAME + " (" + ID
138. + " text not null, " + NAME + " text not null " + ");";
139. try {
140. db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
141. db.execSQL(sql);
142. } catch (SQLException e) {}
143. }
144.
145. /*
146. * 插入N条数据
147. */
148. void InsertRecord(int n) {
149. int total = id + n;
150. for (; id < total; id++) {
151. String sql = "insert into " + TABLE_NAME + " (" + ID + ", " + NAME
152. + ") values('" + String.valueOf(id) + "', 'test');";
153. try {
154. db.execSQL(sql);
155. } catch (SQLException e) {
156. }
157. }
158. }
159.
160. /*
161. * 插入之后刷新分页
162. */
163. void RefreshPage()
164. {
165. String sql = "select count(*) from " + TABLE_NAME;
166. Cursor rec = db.rawQuery(sql, null);
167. rec.moveToLast();
168. long recSize=rec.getLong(0);//取得总数
169. rec.close();
170. int pageNum=(int)(recSize/PageSize) + 1;//取得分页数
171.
172. lstPageID.clear();
173. for (int i = 0; i < pageNum; i++) {
174. HashMap<String, String> map = new HashMap<String, String>();
175. map.put("ItemText", "No." + String.valueOf(i));
176.
177. lstPageID.add(map);
178. }
179. saPageID.notifyDataSetChanged();
180. }
181. }
上次讲的Android上的SQLite分页读取,只用文本框显示数据而已,这次就讲得更加深入些,实现并封装一个SQL分页表格控件,不仅支持分页还是以表格的形式展示数据。先来看看本文程序运行的动画:
这个SQL分页表格控件主要分为“表格区”和“分页栏”这两部分,这两部分都是基于GridView实现的。网上介绍Android上实现表格的DEMO 一般都用ListView。ListView与GridView对比,ListView最大的优势是格单元的大小可以自定义,可以某单元长某单元短,但是不能自适应数据表的结构;而GridView最大的优势就是自适应数据表的结构,但是格单元统一大小。。。对于数据表结构多变的情况,建议使用 GridView实现表格。
本文实现的SQL分页表格控件有以下特点:
1.自适应数据表结构,但是格单元统一大小;
2.支持分页;
3.“表格区”有按键事件回调处理,“分页栏”有分页切换事件回调处理。
本文程序代码较多,可以到这里下载整个工程的源码:http://www.rayfile.com/files/72e78b68-f2e5-11df-8469-0015c55db73d/
items.xml的代码如下,它是“表格区”和“分页栏”的格单元实现:
main.xml的代码如下:
演示程序testSQLite.java的源码:
分页表格控件GVTable.java的源码:
Android 可以通过MediaRecorder和AudioRecord这两个工具来实现录音,MediaRecorder直接把麦克风的数据存到文件,并且能够直接进行编码(如AMR,MP3等),而AudioRecord则是读取麦克风的音频流。本文使用AudioRecord读取音频流,使用 AudioTrack播放音频流,通过“边读边播放”以及增大音量的方式来实现一个简单的助听器程序。
PS:由于目前的Android模拟器还不支持AudioRecord,因此本程序需要编译之后放到真机运行。
先贴出本文程序运行截图:
PS:程序音量调节只是程序内部调节音量而已,要调到最大音量还需要手动设置系统音量。
使用AudioRecord必须要申请许可,在AndroidManifest.xml里面添加这句:
main.xml的源码如下:
testRecord.java的源码如下:
ClsOscilloscope.java是实现示波器的类库,包含AudioRecord操作线程和SurfaceView绘图线程的实现,两个线程同步操作,代码如下:
testOscilloscope.java是主程序,控制UI和ClsOscilloscope,代码如下:
上次介绍了Android利用麦克风采集并显示模拟信号,这种采集手段适用于无IO控制、单纯读取信号的情况。如果传感器本身需要包含控制电路(例如采集血氧信号需要红外和红外线交替发射),那么传感器本身就需要带一片主控IC,片内采集并输出数字信号了。Android手机如何在不改硬件电路的前提下与这类数字传感器交互呢?可选的通信方式就有USB和蓝牙,两种方式各有好处:USB方式可以给传感器供电,蓝牙方式要自备电源;USB接口标准不一,蓝牙普遍支持SPP协议。本文选择蓝牙方式做介绍,介绍 Android的蓝牙API以及蓝牙客户端的用法。
在Android 2.0,官方终于发布了蓝牙API(2.0以下系统的非官方的蓝牙API可以参考这里:http://code.google.com/p/android-bluetooth/)。Android手机一般以客户端的角色主动连接SPP协议设备(接上蓝牙模块的数字传感器),连接流程是:
1.使用registerReceiver注册BroadcastReceiver来获取蓝牙状态、搜索设备等消息;
2.使用BlueAdatper的搜索;
3.在BroadcastReceiver的onReceive()里取得搜索所得的蓝牙设备信息(如名称,MAC,RSSI);
4.通过设备的MAC地址来建立一个BluetoothDevice对象;
5.由BluetoothDevice衍生出BluetoothSocket,准备SOCKET来读写设备;
6.通过BluetoothSocket的createRfcommSocketToServiceRecord()方法来选择连接的协议/服务,这里用的是SPP(UUID:00001101-0000-1000-8000-00805F9B34FB);
7.Connect之后(如果还没配对则系统自动提示),使用BluetoothSocket的getInputStream()和getOutputStream()来读写蓝牙设备。
先来看看本文程序运行的效果图,所选的SPP协议设备是一款单导联心电采集表:
本文的代码较多,可以到这里下载:http://www.pudn.com/downloads305/sourcecode/comm/android/detail1359043.html
本文程序包含两个Activity(testBlueTooth和WaveDiagram),testBlueTooth是搜索建立蓝牙连接。 BluetoothAdapter、BluetoothDevice和BluetoothSocket的使用很简单,除了前三者提供的功能外,还可以通过给系统发送消息来控制、获取蓝牙信息,例如:
注册BroadcastReceiver:
在BroadcastReceiver的onReceive()枚举所有消息的内容:
在DDMS里面可以看到BluetoothDevice.ACTION_FOUND返回的消息:
程序另外一个Activity~~~WaveDiagram用于读取蓝牙数据并绘制波形图,这里要注意一下JAVA的byte的取值范围是跟C/C++不一样的,Android接收到的byte数据要做"& 0xFF"处理,转为C/C++等值的数据。
上次讲解Android的蓝牙基本用法,这次讲得深入些,探讨下蓝牙方面的隐藏API。用过Android系统设置(Setting)的人都知道蓝牙搜索之后可以建立配对和解除配对,但是这两项功能的函数没有在SDK中给出,那么如何去使用这两项功能呢?本文利用JAVA的反射机制去调用这两项功能对应的函数:createBond和removeBond,具体的发掘和实现步骤如下:
1.使用Git工具下载platform/packages/apps/Settings.git,在Setting源码中查找关于建立配对和解除配对的API,知道这两个API的宿主(BluetoothDevice);
2.使用反射机制对BluetoothDevice枚举其所有方法和常量,看看是否存在:
结果如下:
11-29 09:19:12.012: method name(452): cancelBondProcess
11-29 09:19:12.020: method name(452): cancelPairingUserInput
11-29 09:19:12.020: method name(452): createBond
11-29 09:19:12.020: method name(452): createInsecureRfcommSocket
11-29 09:19:12.027: method name(452): createRfcommSocket
11-29 09:19:12.027: method name(452): createRfcommSocketToServiceRecord
11-29 09:19:12.027: method name(452): createScoSocket
11-29 09:19:12.027: method name(452): describeContents
11-29 09:19:12.035: method name(452): equals
11-29 09:19:12.035: method name(452): fetchUuidsWithSdp
11-29 09:19:12.035: method name(452): getAddress
11-29 09:19:12.035: method name(452): getBluetoothClass
11-29 09:19:12.043: method name(452): getBondState
11-29 09:19:12.043: method name(452): getName
11-29 09:19:12.043: method name(452): getServiceChannel
11-29 09:19:12.043: method name(452): getTrustState
11-29 09:19:12.043: method name(452): getUuids
11-29 09:19:12.043: method name(452): hashCode
11-29 09:19:12.043: method name(452): isBluetoothDock
11-29 09:19:12.043: method name(452): removeBond
11-29 09:19:12.043: method name(452): setPairingConfirmation
11-29 09:19:12.043: method name(452): setPasskey
11-29 09:19:12.043: method name(452): setPin
11-29 09:19:12.043: method name(452): setTrust
11-29 09:19:12.043: method name(452): toString
11-29 09:19:12.043: method name(452): writeToParcel
11-29 09:19:12.043: method name(452): convertPinToBytes
11-29 09:19:12.043: method name(452): getClass
11-29 09:19:12.043: method name(452): notify
11-29 09:19:12.043: method name(452): notifyAll
11-29 09:19:12.043: method name(452): wait
11-29 09:19:12.051: method name(452): wait
11-29 09:19:12.051: method name(452): wait
3.如果枚举发现API存在(SDK却隐藏),则自己实现调用方法:
PS:SDK之所以不给出隐藏的API肯定有其原因,也许是出于安全性或者是后续版本兼容性的考虑,因此不能保证隐藏API能在所有Android平台上很好地运行。。。
本文程序运行效果如下:
main.xml源码如下:
工具类ClsUtils.java源码如下:
主程序testReflect.java的源码如下: