期末,Android本学期学习总结
本人大二,这学期基本上大部分的课余时间都用来学编程了,其中学的最多的就是安卓了。有段时间几乎每天晚上,每个周末,每次没课的时候都在学安卓。。不过期末了,,要复习考试了,哎。。不然又挂了。。在这里总结一下这半年所学。不多。也不够详细。其中或有诸多讹误,大牛勿喷,望不吝赐教。
九月份
Xml与UI设计
布局是UI设计的基本组成单位。这里简单介绍下三种布局。布局layout,在这里可以称为parent,因为所有的部件必须存在于其之上。我喜欢把这个parent叫做母体。下面说母体就是指的具体的layout。这里我不会讲每一个组件,比如Button,TextView,EditText等等,这些东西你在每一本android的书上都有介绍,这里就不赘述了,主要讲一些共通的东西。
值得一提的是,在xml文件中的一个小技巧。在每个xml文件开头都会有这么一句话,比如:
<LinearLayoutxmlns:android=http://schemas.android.com/apk/res/android
只要将上面的android换成其他的字母或单词,下面你就不用一遍一遍的写android:。。。了
<LinearLayoutxmlns:a="http://schemas.android.com/apk/res/android"
a:layout_width="fill_parent"
a:layout_height="fill_parent"
a:orientation="vertical">
改成字母a后,会不会很方便
1.线性布局
线性布局是目前,我使用最多的布局,也是初学者最容易上手的布局。
<LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="9dp" android:background="#60000000" > </LinearLayout>
所有的布局内的view组件都要写在两个LinearLayout标签之间。介绍几个常用参数。
layout_width,layout_height用来描述部件的长和宽。其值可为warp_content,或者match-parent/fill-parent。顾名思义,前者就是指的适配内容。后者就是与母体一样宽或高。fill_parent是最初版本里出现的,后来在android2.2之后出现了match_parent。貌似是叫法更加贴切了,目前也是鼓励使用这个,但是最新的sdk依旧是支持fil_parent的使用,这是为了旧设备上的一个兼容性。
orientation是指的布局上的组件的排布方向。horizontal是水平排布。vertical是垂直排布。
background就是背景喽。可以是颜色也可以是图片。颜色用#开头,后面就是通常的RGB颜色,用六位十六进制数字表示。000000为黑色。ffffff是白色。但是#后面是八位,因为后六位是rgb颜色,前两位是透明度。ff是不透明,00全透明。
pading是布局上的view组件的四周距离他的母体layout的长度。单位是dp。安卓布局里面有好多长度单位。px不鼓励使用了。sp是用来描述字体大小的。
再比如在这个布局之中添加两个图片按钮
<ImageButton android:id="@+id/btn_blue" android:layout_width="0dp" android:layout_weight="1" android:layout_height="150dp" android:background="@drawable/blue"/> <ImageButton android:id="@+id/btn_green" android:layout_width="0dp" android:layout_weight="1" android:layout_height="150dp" android:background="@drawable/green"/>
id属性是这个组件的标识。接下来会讲到。
还有一个常用的参数是layout_weight。表示的是所占比例。
上面就是说两个按钮的比例是1:1。在水平布局下,如果使用比例的话,layout_width属性就失效了,设置为0.同理,垂直布局时,layout_height应设置为0.
2.相对布局
相对布局的产生是为了解决日趋严重的安卓设备碎片化问题,由于android的开放,不同的厂商生产了许许多多屏幕分辨率不同的产品,这对于一款应用的测试带来了很大的难度。相对布局使得这一现象得以缓解,注意,不是解决。
相对布局的使用,开始的时候是十分令人头疼的。但当你慢慢熟练之后,你会发现它的便利之处。在这里,根植于相对布局之上的部件,通过与母体layout或其他组件的相对位置来描述。如果你熟悉html的话,会很快上手。。当然我现在还不熟悉html=_=#
属性 |
描述 |
layout_alignParentTop, layout_alignParentBottom, layout_alignParentRight, layout_alignParentLeft |
view与它的RelativeLayout容器对齐。上、下、左或者右对齐。 取值为boolean类型 |
layout_centerHorizontal, layout_centerVertical, layout_centerInParent |
设置居中,水平居中,垂直居中,在母体布局中居中 |
layout_alignTop, layout_alignBottom, layout_alignRight, layout_alignLeft |
同样是对齐,不过是和其他view组件对齐。后面的参数是相对的其他view组件的id |
layout_alignBaseline |
设置当前view组件的所有边和指定的组件对齐。参数是指定组件的id |
layout_above, layout_below, layout_leftOf, layout_rightOf |
顾名思义,在其他view组件的上下左右。参数同样是指定组件的id |
3.表格布局
这个是比较简单的布局,如果你希望你的设计比较简单,并且规整的话,就可以使用这个布局。直接上代码。
Look:
<TableLayout a:layout_width="match_parent" a:layout_height="wrap_content" a:layout_marginTop="80dp"> <TableRow a:id="@+id/tableRow1" a:layout_width="wrap_content" a:layout_height="wrap_content"> <ImageButton a:id="@+id/btn_blue" a:layout_width="0dp" a:layout_weight="1" a:layout_height="150dp" a:background="@drawable/blue"/> <ImageButton a:id="@+id/btn_green" a:layout_width="0dp" a:layout_weight="1" a:layout_height="150dp" a:background="@drawable/green"/> </TableRow> <TableRow a:id="@+id/tableRow2" a:layout_width="wrap_content" a:layout_height="wrap_content"> <ImageButton a:id="@+id/btn_red" a:layout_width="0dp" a:layout_weight="1" a:layout_height="150dp" a:background="@drawable/red"/> <ImageButton a:id="@+id/btn_yellow" a:layout_width="0dp" a:layout_weight="1" a:layout_height="150dp" a:background="@drawable/yellow"/> </TableRow> </TableLayout>
TableLayout包含子组件,TableRow,用来指定每一行的布局。将每一行的view组件置于TableRow即可。
效果:
4.id
id就是你可视化布局里面的资源的索引,类似于windows中的句柄。可以将理解为特殊的“指针”。由于界面逻辑与业务逻辑的分离,id成了打通两者的桥梁。当然在android中也是可以用java来写布局的。但是我不喜欢,这样耦合度太高,修改起来太麻烦。而使用xml布局时,界面逻辑与业务逻辑只通过id进行联系,那么以后你修改布局中某个组件的位置,背景颜色等等之类的属性的时候,Activity通常是不用做任何修改的。不过需要说明的时,在编译的时候xml时候先会转换成java,再转换成字节码文件,所以xml布局在效率上应该会差一点点。。
其实这个id,你可以在R文件中可以看到,就是一个长整型。
Activity入门
新建出工程以后,会自动生成activity的类,及其代码框架。一般来说,一个activity对应一个布局的xml文件,我一般将类名和布局名写成相同的或相似的,便于以后寻找修改。我们要做的是在这个类的开始部分(也可以是其他部分),声明出xml文件中出现的部件。一般不用初始化,但涉及其他变量最好应该初始化。
然后必做的一件事情就是找到这个组件。连接视图与控制的桥梁——id。findViewById。
比如在Activity的类中,有一个红色按钮。
btnRed = (ImageButton)findViewById(R.id.btn_red);//R.id.btn_red就是你布局中指定id的按钮了。注意前面的强制类型转化哦,这个方法返回的是View对象。
手势与事件
当然这只是让你找到了这个部件,你还要为他添加事件。比如一个按钮,添加一个【点击】事件。SetOnClickListenter(),直译就是设置点击事件监听器。参数是你要实现的点击事件接口,比如OnClickListener。每一种监听器的set方法都会有与之对应的监听器接口。注意,有时候你导入包的时候会出问题,比如dialog和view下面都有这个OnClickListener的接口。所以如果出现麻烦,请写成View.OnClickListener。你可以以内部类的形式实现此接口。也可以显示实现这个接口,然后再实例化你的类作为参数。
class MyClickListener implements OnClickListener{ @Override public void onClick(View arg0) {//onClick是要实现的方法 // TODO Auto-generated method stub } }
此时,设置监听可以:
btnRed.setOnClickListener(new MyClickListener());
基本上,每种手势事件与监听器有一一对应的关系,不过Click和Touch是比较像的手势,但却不同。。。下面给出android4.3的支持的七种手势。
注意,android4.4支持的手势发生了变化,变成了八种手势。具体请看这里。http://developer.android.com/design/patterns/gestures.html
Intent
Intent有时候翻译为“意图”,确实它代表着你要跳转的页面的一个意图。。一个冲动。。Android之中的activity是堆在一个类似“栈”的容器中。一层一层,如果你finish掉当前的activity,那么他下面的activity就会显示出来。也可以通过当前的activity,向新的activity的跳转,从而使新的activity叠加到原先的activity上面,而这一实现便是通过Intent。我喜欢把Intent看成一艘船,两个activity是此岸和彼岸。
Intent myIntent = new Intent(); myIntent.setClass(A.this,B.class); A.this.startActivity(myIntent); A.this.finish();
此时的A.this便是指的当前的activity,B.class是你要跳转的activity。。
finish方法会从activity的栈中移除当前的activity,也就是说你从新的activity中按返回键的时候是无法返回上一个activity的。如果要返回就不需要写finish方法。
不过,请原谅我没告诉你更简洁的构造方法
Intent myIntent = new Intent(A.this,B.class);
如果在两个activity之间传递数据怎么办?Intent也可以实现。 那就让这艘船装点货吧。
Bunlde
putExtra方法,这个方法的参数有两个值,name,value,这是一个映射。name是一个字符串,value可以是任何类型。
比如。myIntent.putExtra("mode", "admin");这个就可以传递一个映射。
如果要传递大量的数据这样就不好了
putExtras方法:IntentputExtras(Bundle extras)。它的参数是一个Bundle类型。Bundle封装映射。
Bundle bundle = new Bundle(); //创建Bundle对象 bundle.putString("mode", "admin"); //装入数据 intent.putExtras(bundle);
putExtras方法还有一个重载方法。
IntentputExtras(Intent src)。参数就是另外一个封装好的Intent对象。
然后在新的activity中要获得数据,就是在onCreate方法中
Intent myintent = getIntent(); Bundle bundle = myintent.getExtras(); String mode = bundle.getString("mode");
十月份
SQLite数据库
SQLite数据库是一个开源数据库。它内部只支持NULL,INTEGER,REAL,TEXT,BLOB这五种数据类型。但它也可以接受varchar,char等类型。只不过会在执行的时候转化为上述类型。所以你会发现,SQLite中在定义字段的时候你写varchar(10)和varchar其实没有什么不同。
在学习android数据库的时候,最好下载一个SQLite Expert这个可视化软件。你从模拟器中导出的表通过这个软件得以方便查看。原来貌似是收费软件但是现在有了person版,个人版即免费版。
要引起注意的是,由于android原生的模拟的慢的令人无法忍受,我相信很多人和我一样在使用Genymotion模拟器。确实快很多。但是问题是这个模拟器中产生的表,是无法通过eclipse查看并导出的。开始的时候怎么都找不到模拟器中的表,这个问题曾经折磨我很长时间,一段怀疑自己的代码,甚至怀疑开始怀疑人生。后来才发现是Genymotion并不能查看表。所以如果你要导出表的话,最好还是使用原生的模拟器。毕竟他才是亲儿子。
SQLiteDBHelper
用于操纵数据库,所有具体的增删查改操作,都应该在继承此类的子类中实现。不要臃肿的堆在ui类中。
新建一个继承SQLiteOpenHelper的类的时候,会自动生成两个重写的方法:onCreate和onUpgrade。在app运行的时候,若调用该类,如果没有相应的表,则会默认调用onCreate方法去创建一个表,所以你必须要补充完整此方法。这也是一个典型的回调机制的运用。而onUpgrade一般可不做任何处理。
SQL语句的执行
比如一个数据库member,表名为contacts的表。我习惯在类的开头将所有的字段定义成字符串常量。
public class MemberHelper extends SQLiteOpenHelper { public static final String DB_NAME = "member"; public static final String TABLE_NAME = "contacts"; public static final String ID="_id"; public static final String NAME="name"; public static final String PHONE="phone"; public static final String NO="no"; public static final String QQ="qq"; public static final String BANJI="banji"; public static final String STU_NO="stu_no"; public static final String PWD="pwd"; //省略
(数据库的字段名都是字符串,不要搞混,比如_id字段名是字符串类型,但存储的数据的类型定义的是整型)
与jdbc不同,在执行SQL语句时,可以直接由数据库的对象来执行,而不需要jdbc那样,搞一个Statement对象出来用于专门执行sql语句。
Android使用的数据库为SQLite数据库,对应的类为SQLiteDataBase。
比如一个对象SQLiteDataBase db,可以这样执行sql。
db.execSQL("create table"+TABLE_NAME+" (" //调用execSQL方法创建表 + ID+ " integer primary key," + NO + " varchar," + NAME+ " varchar," + STU_NO+ " varchar," + PHONE+ " varchar," + QQ+ " varchar," + BANJI+ " varchar," + PWD+ " varchar)");
【用于onCreate方法中创建表】
而有些时候对于表的操作时包含变量的,比如插入操作。在MemberHelper方法中增加一个增加联系人的方法。
public void add(SQLiteDatabase db,String no,String name,String stu_no,String phone,String qq,String banji,String pwd){ db.execSQL("insert into contacts(no,name,stu_no,phone,qq,banji ,pwd)values(?,?,?,?,?,?,?)", new String[]{no,name,stu_no,phone,qq,banji,pwd}); }
在方法中的sql语句处使用?来代替后面的变量。?就是占位符,类似于C语言中的%d吧,呵呵。Sql语句后面,所有的参数都要放进一个String类型的数组中。注意,如果插入数据中有数整型数据,要转换为字符串。比如整型数据id。Id+””就变成了字符串。
Cursor游标
Cursor的概念类似于jdbc中的ResultSet结果集,就是说当你执行查询语句的时候,可能返回的结果不只一个,此时返回的是所有结果的集合(确切的说是指向这个集合的第一条记录)。
Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null);
通过游标获得某个字段的内容就是具体类型的get方法,比如如果字段是String,那么就是getString(),括号里的参数是字段也就是列的下标(index),从0开始。但是这样容易记混顺序从而引发问题,所以可以通过一个提供的方法获得这个index。
String no = cursor.getString(cursor.getColumnIndex(NO));
getColumnIndex方法的参数是字段名,为String类型,注意,上例中的NO是前面定义的静态字符串变量。
如果要遍历查询结果中的每条记录,可以使用循环语句,不过请注意,while与do while的不同,这个小差别,在遍历的时候有时会非常重要。
if(cursor.moveToFirst()){ do{ String number = cursor.getString(cursor.getColumnIndex(NO)); if(no.equals(number)) return cursor.getInt(cursor.getColumnIndex(ID)); }while(cursor.moveToNext()); }
如果直接用while的话,第一条记录是不会执行循环体的,从而产生问题。这是小细节,但也层让我百般调试,细节决定成败。
listVIew与数据库
ListVIew是我们可以经常看到的组件(比如你的电话薄),它用来在屏幕上显示一条一条的数据(Item),是一个非常强大的view组件。ListVIew充分利用了MVC的设计思想。我喜欢简称他为LV。
说几句“废话”。问:如果你要显示的Item条目有成千上万那么ListView在加载的时候会不会很耗时呢?答:不会。因为ListVIew不会全部加载所有的Item条目,而是只加载一部分,比一屏幕的要显示的条目要多些。反正能铺满一屏幕,然后当你滚动屏幕,每次有新的Item要进入屏幕的时候,就会有一个旧的Item被垃圾回收器销毁掉,从而节约内存。
要充分利用数据库就要和ListVIew相联系,利用ListView来显示数据库中的数据。当你定义好了ListView对象,下面要显示数据就要设置适配器,setAdapter( );这个方法的参数是各种适配器类型的对象。比如BaseAdapter,SimpleAdapter。这些都是抽象类,实现起来总有麻烦。这里说一个简单的适配器类SimpleCursorAdapter。看到游标Cursor了吧。要实现显示数据库数据简单了不少。
SimpleCursorAdapter(Context context,intlayout,Cursor c,String[] from,int[] to,int flags)
它有六个参数。Contextcontext是你的Activity对象,传入this就行了。int layout是你定义的一个item的布局,就是ListVIew中每一条item的xml布局。这里传入的就是他的id喽。Cursor c是你定义好了的一个游标对象,它必须是返回了你数据库查询结果的游标哦。接下来的String[] from和 int[] to有一个一一对应的关系,也就是形成映射。int[] to是你item布局里要显示的子组件的id,比如多个TextView。而String[] from中就是你要显示的数据库中的字段名了。至于int flags呢,官方的API的说法是定义默认的适配器行为。具体什么行为我也没研究,默认就是0就行了。
adapter = new SimpleCursorAdapter(this, R.layout.listitem,cursor, newString[]{"no","name","stu_no"}, newint[]{R.id.txNO,R.id.txName,R.id.txStu}, 0); lv.setAdapter(adapter);
//其中adapter是SimpleCursorAdapter类型,lv是LiseVIew类型。
然后呢,你可以给你的ListVIew对象设置事件监听器了。常用的比如setOnItemClickListener、setOnItemLongClickListener方法。顾名思义,(=_=#,好多顾名思义)就是点击某一item条目的事件,和长按某一item条目的事件。这两个实现起来基本一致,以前者为例。要实现的OnItemClickListener 接口中的为实现方法为
public void onItemClick(AdapterView<?> arg0,View arg1, int position,long id)
position就是你点击的是第几个条目,从0算起。这个位置也是该条记录在数据库中的位置。所以当你想实现点击item显示该条记录的详情的时候。就需要通过游标或许相应的_id(int类型)。
cursor.moveToPosition(position); _id=cursor.getInt(0);
然后在跳转的新的activity(也就是详情页面),显示该条记录的全部信息。只需要需要用Intent把这个_id传过去。再去通过查询_id对应的记录,然后获取各个字段的数据就行了。
网络编程
恼人的socket与线程
关于socket通信的实现,这里不具体介绍了,是java网络编程中的。android中和java中基本一致的。就是客户端类Socket要指定,ip和port端口。服务器端的ServerSocket指定端口就行了。注意客户端socket指定的端口要与此一致。port的范围在1025到65535之间。
在android4.0以后,不允许在UI线程里,进行可能会产生阻塞的操作了,比如网络通信。所以你在主线程里进行socket连接,在android4.0以后会崩溃。当初刚学的时候,书上还写得是老版本的写法,网上好多也是,然后就是一路的崩溃。。
其实,解决方法不难,只不过当初自己对于线程还没深的认知。线程大家都知道了,实现以来有两种方法,一是继承Thread类,二是实现Runnable接口。当你的线程的对象开始执行start方法的时候,默认的会调用你自己写的run方法。具体实现途径好多了。比如可以
private Runnable mRunnable = new Runnable() { @Override public void run() { // TODO Auto-generated method stub while (true) { try { socket = new Socket(IP, PORT); //shenglve }; } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } };
然后在主线程的onCreate方法中
mThread = new Thread(mRunnable); mThread.start();
mThread是activity的类中声明的私有成员,
private Thread mThread=null;
十一月份
继续网络编程
Handler与UI更新
前面已经说了,在UI线程里不能进行耗时的操作,所以网络通信放在一个子线程中,但如果子线程涉及到UI更新,Android主线程是线程不安全的,也就是说,更新UI只能在主线程中更新,子线程中操作是危险的。Handler就出现了来解决这个问题,由于Handler运行在主线程中(UI线程中),它与子线程可以通过Message对象来传递数据,Handler把这些消息放入主线程队列中,配合主线程进行更新UI。Bundle封装了数据,然后传给Message来搬运数据,好比Bundle传给Intent来搬运数据一样。
private Handler myHandler = new Handler(){ @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); Bundle b = msg.getData(); char flag = b.getChar("color"); switch(flag) { case 'R':img.setBackgroundResource(R.drawable.red);break; case 'G':img.setBackgroundResource(R.drawable.green);break; case 'B':img.setBackgroundResource(R.drawable.blue);break; case 'Y':img.setBackgroundResource(R.drawable.yellow);break; } } };
这个例子是通过网络传送过来的不同字符,来改变img当前的图片。有get就有set,上面只是获取Handler中的数据,那么如何放置数据呢?其实只不过是逆过程罢了。
Message msg = new Message(); Bundle b = new Bundle(); b.putChar("color", flag); msg.setData(b); myHandler.sendMessage(msg);注意这些操作都要放在子线程中哦。
说一点IO
说到网络编程不能不提IO,android在java的基础上并无变化。IO是一个比较复杂的东西,并且我自己也不敢说会IO,简单说一下,IO分为两类,字符流、字节流。记忆起来就是后缀是Stream的都是字节流,比如InputStream和OutputStream,而Reader和Writer是字符流的输入输出。IO类是一个庞大的体系,要分析的话就要记好后缀。就像汉语里,牛奶和奶牛的区别,一个偏正短语后面字(词)的才是中心语,很好懂吧。那InputStreamReader()可以理解了,它是一个Reader类型,但它的构造方法的参数是InputStream类型。它的作用是将字节流转化为字符流。
从Socket获取的IO是字节流。比如一个Socket对象socket。在建立了连接后,可以通过socket.getInputStream()、socket.getOutputStream()来获取IO流。然后都需要对获得的IO流进行加工。比如转化,将字节流转化为字符流,利用InputStreamReader()。或者包装,将字节流包装成过滤流(继承自FilterInputSteam/FilterOutputSteam抽象类)——DataInputStream(DataOutputStream)、BufferedInputStream(BufferedOutputStream)。前者比较常用因为它提供了针对基本数据类型的读/写方法。比如readInt()、readChar()、readByte()、readDouble()等等,需要提一下针对字符串的方法是readUTF而不是readString。这些read方法都是针对输入流DataInputStream的,同理,在DataOutputStream类中有相应的write方法。另外还有一个单独的read方法——int read(byte[]),就是将流中的字节存储到字节数组b中。返回值int是调用一次方法,所存储的字节的数目,如果到了末尾,没有存储字节就返回-1。重载方法int read(byte[] b,int off, int len)。就是可以指定存储位置,off是要存入字节数组b中的起始位置,len就是存储的长度了,也就是说你可以不从b[0]开始存入数据。
回过头再谈一下字节流字符流的转化。
InputStream input = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(input); BufferedReader buffer = new BufferedReader(isr); String inputLine=null; while((inputLine=buffer.readLine())!=null){......}
先转化成字符流,再包装成字符流的缓冲流,目的是实现一行一行的读取数据,提高效率。所谓一行一行就是当字符流中出现\r或\n字符时,就认作行结束了,然后返回该行字符串。注意是\r和\n不会被返回的。
蓝牙通信小解
原来有个误区就是所谓的网络编程只是TCP,UDP那种互联网的通信编程才叫网络编程。其实蓝牙,Wifi等等也都在网络编程的范畴里。学蓝牙是很淡疼的,能找到的资料不是很多,原因大概是蓝牙是涉及硬件的,所以有些在模拟器上学习android编程的人,就束手无策,其次,一定要有至少两个蓝牙设备才能测试,也这也抬高了学习蓝牙的门槛。从搜集到的断简残篇里我学到了一点东西。
同样是通信,同样是socket。不过是BluetoothServerSocket和BluetoothSocket。其实关键的不同之处就是在于如何连接两个蓝牙的过程。在连接之后的操作和一般的socket编程无异。
还要介绍两个类BluetoothAdapter和BluetoothDevice。前者代表本地的蓝牙适配器,后者代表要与本地连接的其他的蓝牙设备。通过BluetoothAdapter静态方法getDefaultAdapter()可以获得本地蓝牙适配器的对象。
private BluetoothAdapter myAdapter = BluetoothAdapter.getDefaultAdapter();
同样是C/S模式的通信我们先看客户端:
首先要获得远程蓝牙设备的对象需要本地适配器对象执行getRemoteDevice方法。
public BluetoothDevice getRemoteDevice (String address)
参数是远程蓝牙设备的地址,不是本地蓝牙的地址。就好像一般socket编程中要指定的ip一样。地址就是MAC地址,也就是物理地址,包括互联网上的每一台电脑的网卡都有一个物理地址。蓝牙也有MAC地址。这个地址在哪里找呢。首先打开手机的蓝牙,然后设置其可见。接着依次打开手机里的设置——关于手机——状态信息就能看到了。
你获得了BluetoothDevice对象,接着要获得BluetoothSocket对象。执行下面的方法
public BluetoothSocket createRfcommSocketToServiceRecord (UUID uuid)
方法中的有个单词RFCOMM,就是蓝牙通信的协议。参数UUID叫做通用唯一识别码,这里简单说就是代表着你的蓝牙通信要进行哪种服务,不同的服务有不同的UUID识别码。具体看这位同学的博客吧——传送门。一般的通信就用这个吧00001101-0000-1000-8000-00805F9B34FB。
socket = device.createRfcommSocketToServiceRecord(UUID.fromString(" 00001101-0000-1000-8000-00805F9B34FB"));
UUID的静态方法fromString就是从字符串创建UUID对象喽。
再看看服务端:
服务端的BluetoothServerSocket对象必然要开启对于客户端连接请求的监听。使用listenUsingRfcommWithServiceRecord方法。
public BluetoothServerSocket listenUsingRfcommWithServiceRecord (String name, UUID uuid)
参数字符串name是服务器名,随便起个名字就好了。UUID就不再解释了。
再说一下,打开蓝牙
蓝牙适配器类有isEnable方法来判断本地蓝牙是否已经打开。有两种方法。第一种是直接调用适配器类的enable方法打开。第二种调用系统API,弹出一个小对话框,让你选择是否打开。
//第一种打开方法: boolean result = mBluetoothAdapter.enable(); //第二种打开方法,调用系统API去打开蓝牙 if (!mBluetoothAdapter.isEnabled()){ Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(intent,1); }
看一下startActivityForResult方法。
public void startActivityForResult (Intent intent, int requestCode)
Intent intent 就是前面定义的Intent对象,一个系统定义好的打开蓝牙请求对话框的意图。
int requestCode是请求返回码,大于等于0即可。这个返回码会在onActivityResult()中得到处理。
(onActivityResult()是Activity类中的方法)
蓝牙的东西太多了。这里只是很小很小的一部分,只是当做给大家学习蓝牙的一个开胃小菜。好多东西都没有讲到,大家自己去探索吧。