好了,过了这么久总算是开始写android端的博文了,每次一回家就各种忙+各种病……带病写文章难免会有疏忽,还请大家见谅……
android程序员的成分和以前的手机程序员成分不同,以前做手机开发的基本全是做嵌入式的,所以大家也就习惯性的用C/C++来做开发,用个QT就算高级的了,做游戏的好像好多是从电脑游戏转型过来的,不过曾经的j2me路线也是走的人们非常纠结。自从iphone出来android出来以后很多原先的手机程序员就懵了,写程序用java,想不学面向对象用C偷懒做不成了。布局硬是被从代码中给揪出来了,而且xml的布局方式也无法直接在布局中插入“动态代码”。然而令人奇怪的是在iphone开放SDK之后,在android放出1.0版本之后,手机程序员的数量暴增,新增的程序员在短短几天之内就可以搞定简单的手机程序了,相反的,学嵌入式的哥哥们发现上手并不是想象中的那么容易……从我个人的经验来看,这主要是因为做嵌入式的没有做过网站罢了。android系统在设计时有意的把很多服务器端程序遇到的问题在android中做了处理(可能跟google本身就是做网络的有关系吧),比如考虑到程序的跨平台特性选用JAVA作为开发语言(而且基本上所有的软件出身的都是java的老用户),充分的发扬了java“一处编写,处处运行”的优势。比如说网络编程一直在倡导的“代码与布局分开”,在如今的服务器端编程中已经被作为了一项基本准则,在android中也被完美的实现(虽然google考虑到会有很多人用着不习惯而提出“随个人喜好可以选用这两种布局方式”)。再比如做网站为了克服硬盘读取速度慢的问题使用SQL数据库来高效的提取数据,android也内置了SQLlite来读取短信等数据并供用户程序读写自己的数据(尽管做嵌入式的会笑话做网站的不知道flash是当做内存来用的……)。所以说android系统做软件的上手很容易,硬件出身的可以稍微看一点网站架设的相关书籍,对入门android非常有用。
从这里开始就没有什么硬件的东西了,如果对java不熟的话想继续做下去最好还是多看一些java的书,不然的话大片的内部类,各种匪夷的迭代器看着会很困惑的。
大家经常会从CSDN或者别的什么地方直接下载别人的工程文件,经常有同学下载了以后无法再自己的手机上运行,需要提醒一下刚入门的朋友,android的版本升级并没有像承诺的那样在低版本的api上写得代码在高本版上可以直接兼容,android的sdk在1.5和2.0、2.2处做了很大的改动,很多东西都变了,所以大家下载完工程之后要对着自己的固件版本对程序修改并重新编译,具体怎么做在看过android的入门书籍后就大概知道了(同时也提醒大家在买入门书籍的时候尽可能选自己要用到的api版本的,有些很古老的书写的是非常不错可以拿来参考但是上面的例子8成是不能用的,同时千万不要买那些现成的代码拼起来没有理论的书,事实证明,得不偿失)。对纯新手有三个地方需要注意,android程序的AndroidManifest.xml非常重要,每一个程序的硬件功能都要加上对应的权限(如使用internet或加速度传感器之类),不然是无法启动程序的,同时在properties中改过api版本号后记得把AndroidManifest.xml中的版本号也改了(下面红字),不然也是没法用的,最后就是千万不要自己修改R文件,这个系统自动生成的,如果你写对了布局layout文件的话代码中显示的R文件的错误就没有了。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.lynx.SmackCar"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
接下来的代码是一个简单的蓝牙串口通信的例子,来源是一个叫“血色残阳”的大虾的百度空间: 点击打开链接
这个里面的蓝牙代码不知是从哪里搞来的,但却是我找了很久的唯一一段可以在2.x系统上使用的蓝牙串口代码。
解释一下这段代码,代码中
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); //这条是蓝牙串口通用的UUID,不要更改
private static String address = "00:11:00:18:05:45"; // <==要连接的蓝牙设备MAC地址
是用来配置与你自己的蓝牙串口连接的,上面的UUID就使用这个数不要改,因为蓝牙的串口都是用的这个UUID(我也是后来才知道的),address改成你买到的蓝牙模块的mac地址,这个可以在你用电脑测试蓝牙串口的时候看到。
android的程序中会有很多
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
这样的代码,这是输出调试信息的,在多线程的程序中用断点的方式调试经常会出现匪夷所思的情况,就像单片机外部中断间隔很短又在中断中设断点差不多,所以大家都是用输出日志来观察程序执行的流程并除错的(在虚拟机中或手机连接电脑时在eclipse的DDMS中查看运行日志)。Log.e输出的是错误日志,Log.d输出的是DEBUG日志,详细的说明在eclipse中手动键入代码的时候都可以看得到。很多人为了方便在程序成型后不再显示debug日志经常会这样写:
private static final boolean DebugFlag = true;
if(DebugFlag)
{
Log.d("This is a log");
}
程序启动需要事先进行过配对(只要有过配对记录就行了),先打开单片机连着的蓝牙模块,再打开手机上的蓝牙功能再运行程序即可。
再次感谢原作者。
BlueToothTestActivity.java
package android.lynx.BlueToothTest;
import android.app.Activity;
import android.os.Bundle;
import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class BlueToothTestActivity extends Activity {
private static final String TAG = "BluetoothTest";
private static final boolean D = true;
private BluetoothAdapter mBluetoothAdapter = null;
private BluetoothSocket btSocket = null;
private OutputStream outStream = null;
Button mButtonF;
Button mButtonB;
Button mButtonL;
Button mButtonR;
Button mButtonS;
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); //这条是蓝牙串口通用的UUID,不要更改
private static String address = "00:11:00:18:05:45"; // <==要连接的蓝牙设备MAC地址
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//前进
mButtonF=(Button)findViewById(R.id.btnF);
mButtonF.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
String message;
byte[] msgBuffer;
int action = event.getAction();
switch(action)
{
case MotionEvent.ACTION_DOWN:
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
}
message = "CMF";
msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
}
break;
case MotionEvent.ACTION_UP:
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
}
message = "CS";
msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
}
break;
}
return false;
}
});
//后退
mButtonB=(Button)findViewById(R.id.btnB);
mButtonB.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
String message;
byte[] msgBuffer;
int action = event.getAction();
switch(action)
{
case MotionEvent.ACTION_DOWN:
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
}
message = "CMB";
msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
}
break;
case MotionEvent.ACTION_UP:
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
}
message = "CS";
msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
}
break;
}
return false;
}
});
//左转
mButtonL=(Button)findViewById(R.id.btnL);
mButtonL.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
String message;
byte[] msgBuffer;
int action = event.getAction();
switch(action)
{
case MotionEvent.ACTION_DOWN:
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
}
message = "CTL";
msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
}
break;
case MotionEvent.ACTION_UP:
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
}
message = "CTL";
msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
}
break;
}
return false;
}
});
//右转
mButtonR=(Button)findViewById(R.id.btnR);
mButtonR.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
String message;
byte[] msgBuffer;
int action = event.getAction();
switch(action)
{
case MotionEvent.ACTION_DOWN:
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
}
message = "CTR";
msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
}
break;
case MotionEvent.ACTION_UP:
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
}
message = "CTR";
msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
}
break;
}
return false;
}
});
//停止
mButtonS=(Button)findViewById(R.id.btnS);
mButtonS.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN)
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
}
String message = "CR";
byte[] msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
}
return false;
}
});
if(D)
Log.e(TAG, "+++ ON CREATE +++");
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter == null)
{
Toast.makeText(this, "Bluetooth is not available.", Toast.LENGTH_LONG).show();
finish();
return;
}
if(!mBluetoothAdapter.isEnabled())
{
Toast.makeText(this, "Please enable your Bluetooth and re-run this program.", Toast.LENGTH_LONG).show();
finish();
return;
}
if(D)
Log.e(TAG, "+++ DONE IN ON CREATE, GOT LOCAL BT ADAPTER +++");
}
@Override
public void onStart()
{
super.onStart();
if(D)
Log.e(TAG, "++ ON START ++");
}
@Override
public void onResume()
{
super.onResume();
if (D)
{
Log.e(TAG, "+ ON RESUME +");
Log.e(TAG, "+ ABOUT TO ATTEMPT CLIENT CONNECT +");
}
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Socket creation failed.", e);
}
mBluetoothAdapter.cancelDiscovery();
try {
btSocket.connect();
Log.e(TAG, "ON RESUME: BT connection established, data transfer link open.");
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
Log .e(TAG,"ON RESUME: Unable to close socket during connection failure", e2);
}
}
// Create a data stream so we can talk to server.
if (D)
Log.e(TAG, "+ ABOUT TO SAY SOMETHING TO SERVER +");
/* try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Output stream creation failed.", e);
}
String message = "1";
byte[] msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.e(TAG, "ON RESUME: Exception during write.", e);
}
*/
}
@Override
public void onPause()
{
super.onPause();
if (D)
Log.e(TAG, "- ON PAUSE -");
if (outStream != null)
{
try {
outStream.flush();
} catch (IOException e) {
Log.e(TAG, "ON PAUSE: Couldn't flush output stream.", e);
}
}
try {
btSocket.close();
} catch (IOException e2) {
Log.e(TAG, "ON PAUSE: Unable to close socket.", e2);
}
}
@Override
public void onStop()
{
super.onStop();
if (D)Log.e(TAG, "-- ON STOP --");
}
@Override
public void onDestroy()
{
super.onDestroy();
if (D) Log.e(TAG, "--- ON DESTROY ---");
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:weightSum="1">
<RelativeLayout android:layout_width="fill_parent" android:id="@+id/relativeLayout1" android:layout_height="wrap_content">
<RelativeLayout android:id="@+id/relativeLayout4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true"></RelativeLayout>
<Button android:id="@+id/btnF" android:layout_width="200dp" android:text="Forward" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true"></Button>
</RelativeLayout>
<RelativeLayout android:layout_width="fill_parent" android:id="@+id/relativeLayout2" android:layout_height="30dp" android:layout_weight="0.06">
<RelativeLayout android:id="@+id/relativeLayout5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true"></RelativeLayout>
<Button android:id="@+id/btnS" android:layout_width="100dp" android:text="Middle" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true"></Button>
<Button android:id="@+id/btnR" android:layout_width="100dp" android:text="Right" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_alignParentRight="true"></Button>
<Button android:id="@+id/btnL" android:layout_width="100dp" android:text="Left" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_alignParentLeft="true"></Button>
</RelativeLayout>
<RelativeLayout android:layout_width="fill_parent" android:id="@+id/relativeLayout3" android:layout_height="46dp" android:layout_weight="0.01">
<RelativeLayout android:id="@+id/relativeLayout6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_centerHorizontal="true"></RelativeLayout>
<Button android:id="@+id/btnB" android:layout_width="200dp" android:text="Back" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true"></Button>
</RelativeLayout>
</LinearLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.lynx.BlueToothTest"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<uses-permission android:name="android.permission.BLUETOOTH"></uses-permission>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"></uses-permission>
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".BlueToothTestActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
想下工程文件的朋友移步 http://download.csdn.net/detail/lynx2/4012584
写这篇文章的时候刚好发现原作大虾推出了新版本,好像可以在程序中搜索设备并自定发送的字符串了,不想跟着这个博客写下面程序的可以直接用那个了,不错不错 http://hi.baidu.com/liuhuiviking/blog/item/392b715133158d928d5430df.html
不过为了让代码更清晰,我后面的程序还是沿用这个简单的蓝牙连接代码,此处再强调一下,这个代码是用在2.x以上sdk的蓝牙库的,在后续的程序中这个库中的方法在2.3的手机上运行的相当完美,但是2.1(我用的是倒霉的升级到2.1的G3)的这个库BUG不断,整的好多在别人手机上轻松运行的代码在我这里困难重重,后面的文章中我会不定期的大骂2.1,诸位看客看到后理解万岁…………
下一篇节外生个枝,讲如何在触屏上做一个摇杆来控制小车,之后回归正题,我们要把小车的蓝牙服务写在一个service中,让手机上的其他程序可以松耦合的调用这个小车的控制。我们的目标是:让不同类型的控制程序(视频、GPS或者方向键控制随意更换)可以控制不同类型的小车(底层接口不同,比如有无多级控制有无电量显示)。