这个程序要用到摇杆了,可想而知,我们需要多级的控制才行,不然的话拨来拨去只有四个动作就没意思了。这个程序与前面程序最大的区别是加入了一个守护进程来实时的监控摇杆的位置并传送控制指令,同时加入了检测蓝牙连接是否断开并自动重连的功能。

这里我可要被2.1的sdk气死了,2.1的蓝牙库没有提供检测连接断开的功能,socket一直是连着的,断了它也不会给什么反馈,我就直接用单片机给个反馈,结果这个程序在2.3的机器上跑的相当好,唯独我这个倒霉的2.1各种出错,模拟机没法用蓝牙调试就只能用真机,一遍一遍的打包apk连接上传运行然后在真机上多的想吐的DEBUG信息中在溢出前找到自己的DEBUG然后分析在重新编译…………到后来连哭的力气都没有,于是一狠心就给他屏蔽掉了,反正是用在稳定电源的车模上的,而且手机也不会远离车,一般不会断开…………下面的代码是有重连功能的,如果你有和我一样的问题就找到“if(!CarToContral.isBonded())”改成if(false)吧。

运行程序,可以用方向键控制,在打开守护进程后可以用摇杆来控制。

检测连接是用类似电脑上ping的方法实现的,手机发送一个“A”再由单片机接到后再传回去,如果手机接到了这个反馈则说明连接正常,若接不到则说明连接断开。为了防止在这个过程中有其他反馈命令混杂其中,检测的时候迭代整个缓冲区来寻找反馈信息。同时要注意这里有个sleep是必须的,我们用的串口的速度是9600bit/s传送一个字节是需要时间的,更何况要传送两回并且单片机还有反应一下……

关于那个“守护进程”,由于我的访问是时序的,所以没有考虑死锁之类的问题,如果改的话遇到随机访问还是加上锁安全一些。

 /*** 创建用于定时发送位置状态的守护进程*/public void ThreadCreate(){if(Guarder != null)  //确保只有一个守护进程可以被创建return;Guarder = new Thread(this);ThreadFlag = true;Guarder.start();}

这段代码是为了防止生成多个线程用的,线程只能有这个View类来创建销毁并且只记录一个,这样就不会出现因为前一个线程未消毁导致两个线程抢资源的问题了。

好了,废话少说,上代码了

AndroidBluetoothCarActivity.java

package android.lynx.BluetoothCar;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.Toast;
import android.widget.ToggleButton;
import android.widget.CompoundButton.OnCheckedChangeListener;public class AndroidBluetoothCarActivity extends Activity implements CarPositionSetable{private static final String TAG = "BluetoothTest";private static final boolean D = true;private BluetoothAdapter mBluetoothAdapter = null;private BluetoothDevice device = null;private BluetoothSocket btSocket = null;private OutputStream outStream = null;Button mButtonF;Button mButtonB;Button mButtonL;Button mButtonR;Button mButtonS;ToggleButton JoyStickSwitcher;VirtualJoyStickView JoyStick;private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");  //这条是蓝牙串口通用的UUID,不要更改private static String address = "00:19:5D:EE:2E:A5"; // <==要连接的蓝牙设备MAC地址/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);//前进mButtonF=(Button)findViewById(R.id.btnF);mButtonF.setOnTouchListener(new Button.OnTouchListener(){@Overridepublic 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(){@Overridepublic 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(){@Overridepublic 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(){@Overridepublic 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(){@Overridepublic 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;}});JoyStick = (VirtualJoyStickView)findViewById(R.id.JoyStick);JoyStick.setPositionSetable(this);JoyStickSwitcher=(ToggleButton)findViewById(R.id.JoyStickSwitcher);JoyStickSwitcher.setOnCheckedChangeListener(new OnCheckedChangeListener(){@Overridepublic void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {if(isChecked){mButtonF.setEnabled(false);mButtonB.setEnabled(false);mButtonS.setEnabled(false);mButtonL.setEnabled(false);mButtonR.setEnabled(false);JoyStick.ThreadCreate();   }else{mButtonF.setEnabled(true);mButtonB.setEnabled(true);mButtonS.setEnabled(true);mButtonL.setEnabled(true);mButtonR.setEnabled(true);JoyStick.ThreadDestory(); }}});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 +++");}@Overridepublic void onStart() {super.onStart();if(D) Log.e(TAG, "++ ON START ++");}@Overridepublic void onResume() {super.onResume();if (D) {Log.e(TAG, "+ ON RESUME +");Log.e(TAG, "+ ABOUT TO ATTEMPT CLIENT CONNECT +");}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);}*/}@Overridepublic 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);}}@Overridepublic void onStop(){super.onStop();if (D)Log.e(TAG, "-- ON STOP --");}@Overridepublic void onDestroy() {super.onDestroy();if (D) Log.e(TAG, "--- ON DESTROY ---");}@Overridepublic void setPositionMove(double movePosition) {String message;byte[] msgBuffer;try {outStream = btSocket.getOutputStream();} catch (IOException e) {Log.e(TAG, "ON RESUME: Output stream creation failed.", e);}if(movePosition > 1 || movePosition < -1)message = "CM4";else if(movePosition > 0.9)message = "CM7";else if(movePosition > 0.7)message = "CM6";else if(movePosition > 0.3)message = "CM5";else if(movePosition < -0.9)message = "CM1";else if(movePosition < -0.7)message = "CM2";else if(movePosition < -0.3)message = "CM3";elsemessage = "CM4";msgBuffer = message.getBytes();try {outStream.write(msgBuffer);} catch (IOException e) {Log.e(TAG, "ON RESUME: Exception during write.", e);}}@Overridepublic void setPositionTurn(double turnPosition) {String message;byte[] msgBuffer;try {outStream = btSocket.getOutputStream();} catch (IOException e) {Log.e(TAG, "ON RESUME: Output stream creation failed.", e);}if(turnPosition > 1 || turnPosition < -1)message = "CT4";else if(turnPosition > 0.9)message = "CT7";else if(turnPosition > 0.7)message = "CT6";else if(turnPosition > 0.3)message = "CT5";else if(turnPosition < -0.9)message = "CT1";else if(turnPosition < -0.7)message = "CT2";else if(turnPosition < -0.3)message = "CT3";elsemessage = "CT4";msgBuffer = message.getBytes();try {outStream.write(msgBuffer);} catch (IOException e) {Log.e(TAG, "ON RESUME: Exception during write.", e);}}@Overridepublic void Connect() {if (D) {Log.e(TAG, "+ ABOUT TO ATTEMPT CLIENT CONNECT +");}device = mBluetoothAdapter.getRemoteDevice(address);try {btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);} catch (IOException e) {Log.e(TAG, "ON CONNECT: 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);}}}@Overridepublic boolean isBonded() {InputStream inStream = null;try {outStream = btSocket.getOutputStream();inStream = btSocket.getInputStream();} catch (IOException e) {Log.e(TAG, "ON RESUME: Output stream creation failed.", e);}String message = "A";byte[] msgBuffer = message.getBytes();byte[] msgBuffer2 =new byte[20];try {outStream.write(msgBuffer);           } catch (IOException e) {Log.e(TAG, "ON RESUME: Exception during write.", e);}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}try {inStream.read(msgBuffer2);} catch (IOException e) {e.printStackTrace();}for(byte ThisByte : msgBuffer2){if(ThisByte == (byte)'A')return true;}return false;/*if(device.getBondState() == BluetoothDevice.BOND_NONE || device == null){return false;}else{return true;}*/ }
}

CarPositionSetable.java

/*** */
package android.lynx.BluetoothCar;/*** @author lynx@ynu 2011/10/15**/
public interface CarPositionSetable {/*** 设置车轮转向的位置,-1为左最大,1为右最大,0为正中* @param turnPosition -1~1*/public void setPositionTurn(double turnPosition);/*** 设置速度等级,-1为后退最大,1为前进最大,0为静止* @param movePosition -1~1*/public void setPositionMove(double movePosition);/*** Get the bond state* @return True if bonded*/public boolean isBonded();/*** Connect to the Setter*/public void Connect();
}

StickPositionGetable.java

/*** */
package android.lynx.BluetoothCar;/*** @author lynx@ynu  2011/10/15**/
public interface StickPositionGetable {/*** Get the X position percent of the max distance.* @return The position X from -1 to 1 in double*/public double getX();/*** Get the Y position percent of the max distance.* @return The position Y from -1 to 1 in double*/public double getY();}

VirtualJoyStickView.java

/*** */
package android.lynx.BluetoothCar;import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.MotionEvent;public class VirtualJoyStickView extends View implements StickPositionGetable, Runnable{private Paint paint;//固定摇杆背景圆形的X,Y坐标以及半径private int RockerCircleX = 170;private int RockerCircleY = 110;private int RockerCircleR = 80;//摇杆的初始X,Y坐标及半径private float SmallRockerInitCircleX =170;private float SmallRockerInitCircleY = 110;private float SmallRockerInitCircleR = 30;//摇杆的X,Y坐标以及摇杆的半径private float SmallRockerCircleX = SmallRockerInitCircleX;private float SmallRockerCircleY = SmallRockerInitCircleY;private float SmallRockerCircleR = SmallRockerInitCircleR;//守护进程用fieldprivate Thread Guarder = null;private boolean ThreadFlag = false;private CarPositionSetable CarToContral = null;public VirtualJoyStickView(Context context) {super(context);init();}public VirtualJoyStickView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}public VirtualJoyStickView(Context context, AttributeSet attrs) {super(context, attrs);init();}public void init(){this.setKeepScreenOn(true);paint = new Paint();paint.setAntiAlias(true);setFocusable(true);setFocusableInTouchMode(true);}/**** 得到两点之间的弧度*/public double getRad(float px1, float py1, float px2, float py2) {//得到两点X的距离float x = px2 - px1;//得到两点Y的距离float y = py1 - py2;//算出斜边长float xie = (float) Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));//得到这个角度的余弦值(通过三角函数中的定理 :邻边/斜边=角度余弦值)float cosAngle = x / xie;//通过反余弦定理获取到其角度的弧度float rad = (float) Math.acos(cosAngle);//注意:当触屏的位置Y坐标<摇杆的Y坐标我们要取反值-0~-180if (py2 < py1) {rad = -rad;}return rad;}@Overridepublic boolean onTouchEvent(MotionEvent event) {if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {// 当触屏区域不在活动范围内if (Math.sqrt(Math.pow((RockerCircleX - (int) event.getX()), 2) + Math.pow((RockerCircleY - (int) event.getY()), 2)) >= RockerCircleR) {//得到摇杆与触屏点所形成的角度double tempRad = getRad(RockerCircleX, RockerCircleY, event.getX(), event.getY());//保证内部小圆运动的长度限制getXY(RockerCircleX, RockerCircleY, RockerCircleR, tempRad);} else {//如果小球中心点小于活动区域则随着用户触屏点移动即可SmallRockerCircleX = (int) event.getX();SmallRockerCircleY = (int) event.getY();}} else if (event.getAction() == MotionEvent.ACTION_UP) {//当释放按键时摇杆要恢复摇杆的位置为初始位置SmallRockerCircleX = SmallRockerInitCircleX;SmallRockerCircleY = SmallRockerInitCircleY;}this.invalidate();return true;}/*** * @param R*            圆周运动的旋转点* @param centerX*            旋转点X* @param centerY*            旋转点Y* @param rad*            旋转的弧度*/public void getXY(float centerX, float centerY, float R, double rad) {//获取圆周运动的X坐标 SmallRockerCircleX = (float) (R * Math.cos(rad)) + centerX;//获取圆周运动的Y坐标SmallRockerCircleY = (float) (R * Math.sin(rad)) + centerY;}/* (non-Javadoc)* @see android.view.View#onDraw(android.graphics.Canvas)*/@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);try {//canvas.drawColor(Color.WHITE);//设置透明度paint.setColor(0x70808080);//绘制摇杆背景canvas.drawCircle(RockerCircleX, RockerCircleY, RockerCircleR, paint);paint.setColor(0x70ff0000);//绘制摇杆canvas.drawCircle(SmallRockerCircleX, SmallRockerCircleY, SmallRockerCircleR, paint);} catch (Exception e) {} }@Overridepublic double getX() {double temp;if(SmallRockerCircleX == SmallRockerInitCircleX)return 0;temp = SmallRockerCircleX - SmallRockerInitCircleX;temp = temp/RockerCircleR;return temp;}@Overridepublic double getY() {double temp;if(SmallRockerCircleY == SmallRockerInitCircleY)return 0;temp = SmallRockerCircleY - SmallRockerInitCircleY;temp = temp/RockerCircleR*(-1);return temp;}/*** 定时发送位置状态的守护进程动作*/@Overridepublic void run() {while (ThreadFlag) {if(!CarToContral.isBonded()){CarToContral.Connect();}CarToContral.setPositionTurn(this.getX());try {Thread.sleep(50);} catch (Exception ex) {}CarToContral.setPositionMove(this.getY());try {Thread.sleep(50);} catch (Exception ex) {}}}/*** 创建用于定时发送位置状态的守护进程*/public void ThreadCreate(){if(Guarder != null)  //确保只有一个守护进程可以被创建return;Guarder = new Thread(this);ThreadFlag = true;Guarder.start();}/*** 销毁守护进程*/public void ThreadDestory(){ThreadFlag = false;Guarder = null;     }/*** 设置守护进程位置状态输出对象* @param in*/public void setPositionSetable(CarPositionSetable in){CarToContral = in;}}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="fill_parent" android:orientation="vertical"><LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/linearLayout1" android:orientation="vertical"><RelativeLayout android:layout_width="fill_parent" android:id="@+id/relativeLayout2" android:layout_height="wrap_content"><Button android:layout_width="200dp" android:layout_height="wrap_content" android:id="@+id/btnF" android:text="Forward" android:layout_alignParentTop="true" android:layout_centerHorizontal="true"></Button></RelativeLayout></LinearLayout><LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/linearLayout2" android:orientation="vertical"><RelativeLayout android:layout_width="fill_parent" android:id="@+id/relativeLayout1" android:layout_height="wrap_content"><Button android:layout_width="100dp" android:layout_height="wrap_content" android:id="@+id/btnS" android:text="Middle" android:layout_alignParentTop="true" android:layout_centerHorizontal="true"></Button><Button android:layout_width="100dp" android:layout_height="wrap_content" android:id="@+id/btnR" android:text="Right" android:layout_alignParentTop="true" android:layout_alignParentRight="true"></Button><Button android:layout_width="100dp" android:layout_height="wrap_content" android:id="@+id/btnL" android:text="Left" android:layout_alignParentTop="true" android:layout_alignParentLeft="true"></Button></RelativeLayout></LinearLayout><LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/linearLayout3" android:orientation="vertical"><RelativeLayout android:layout_width="fill_parent" android:id="@+id/relativeLayout3" android:layout_height="wrap_content"><Button android:layout_width="200dp" android:layout_height="wrap_content" android:id="@+id/btnB" android:text="Back" android:layout_alignParentTop="true" android:layout_centerHorizontal="true"></Button></RelativeLayout></LinearLayout><LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/linearLayout4" android:orientation="vertical"><LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/linearLayout5" android:orientation="vertical"><TextView android:text="Joy Stick Switcher" android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content"></TextView><ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/JoyStickSwitcher" android:text="ToggleButton" android:checked="false"></ToggleButton></LinearLayout><android.lynx.BluetoothCar.VirtualJoyStickView android:layout_width="wrap_content" android:id="@+id/JoyStick" android:layout_height="fill_parent"></android.lynx.BluetoothCar.VirtualJoyStickView></LinearLayout>
</LinearLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="android.lynx.BluetoothCar"android:versionCode="1"android:versionName="1.0"><uses-sdk android:minSdkVersion="7" /><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=".AndroidBluetoothCarActivity"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/4013038

基于android手机的3G+GPS远程控制模型车工程-android手机编程2-摇杆控制相关推荐

  1. 基于android手机的3G+GPS远程控制模型车工程-android手机编程5-伪视频控制车载手机端程序(代码篇)

    程序介绍在上一个日志中 好多人问我要这个工程文件,受不了了,传上来了(地址在最后),不过连接时会有很多问题要自己去体会,这里教不了了(因为全是gtalk的设置问题) 首先放上包结构图: 图中显示的那两 ...

  2. python蓝牙控制手机打电话_通过蓝牙连接车机和手机,实现打电话功能实现

    前言:目前的汽车中控系统几乎都是清一色使用Android系统平台,来实现多媒体娱乐相关功能.当然也有苹果的CarPlay,谷歌的Android Auto.更多的都是厂家自己深度定制的Android,来 ...

  3. 滴滴KDD2017论文:基于组合优化的出租车分单模型 By 机器之心2017年8月14日 10:29 数据挖掘顶会 KDD 2017 已经开幕,国内有众多来自产业界的论文被 KDD 2017 接收。

    滴滴KDD2017论文:基于组合优化的出租车分单模型 By 机器之心2017年8月14日 10:29 数据挖掘顶会 KDD 2017 已经开幕,国内有众多来自产业界的论文被 KDD 2017 接收.本 ...

  4. 高德地图八:手机定位和GPS定位

    高德地图八:手机定位和GPS定位 高德地图八:手机定位和GPS定位 代码实现: layout/activity_main.xml <?xml version="1.0" en ...

  5. 新旧手机改车载导航,随车点火开机,熄火关机,全自动化操作,语音控制导航

    第一次写文章多多见谅 第一步手机必须root,自己手机怎么root自己百度方法,需要的软件有地图软件,百度carLife.高德地图车机版.根据个人爱好自己选择(个人推荐百度carLife,主要因为可以 ...

  6. 3g手机android应用新浪微博,3.1.1 新浪微博官方Android客户端

    3.1.1 新浪微博官方Android客户端 新浪是国内最早的微博厂商之一,很早就推出微博Android客户端.读者可以从地址http://weibo.com/mobile/android.php下载 ...

  7. qpython获取手机gps_基于Python获取照片的GPS位置信息

    这篇文章主要介绍了基于Python获取照片的GPS位置信息,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 昨天听人说,用手机拍照会带着GPS信息,原 ...

  8. 将TensorFlow训练的模型移植到Android手机

    2019独角兽企业重金招聘Python工程师标准>>> 前言 本文中出现的TF皆为TensorFlow的简称. 先说两句题外话吧,TensorFlow 前两天热热闹闹的发布了正式版r ...

  9. 通过MACE在Android手机上部署深度学习模型

    1. MACE的环境搭建 参考我的博客:MACE的环境搭建--conda实现 2. 构建项目 (1)下载MACE项目到本地 git clone https://github.com/XiaoMi/ma ...

最新文章

  1. pygame里面物体闪烁运动_教师资格【试讲示范】高中物理试讲答辩——《自由落体运动》试讲稿答辩...
  2. 线程安全机制 python
  3. Oracle在Linux平台“静默”安装(二)
  4. sql getdate() 时间格式设置
  5. java—IO流——读取键盘输入的字母并转换成大写字母输出在控制台上
  6. 【JavaScript】数学计算的函数与数字的格式化
  7. STL 源代码剖析 算法 stl_algo.h -- search
  8. SAP License:SAP实施Roll out项目经验谈(二)
  9. 织梦文章页模板使用php语法,织梦文章页面模板顶一下踩一下调用教程
  10. 微信 小程序 python 渲染_干货 | 微信小程序自动化测试最佳实践(附 Python 源码)...
  11. 服务 自动启动参数_使用ansible部署springboot系列02服务托管与jvm参数管理
  12. c#生成随机彩色验证码例子
  13. c语言中d1的分辨率是,C中的方法分辨率顺序
  14. xp 极限编程_极限编程(XP)简介
  15. 常用汉字字体字号的介绍及选用原则
  16. 运维为什么这么难招?
  17. RTI DDS的xml说明
  18. openmediavault安装
  19. 16.【Linux】window和linux下文件格式相互转换
  20. 动作捕捉用于仿生机器人的运动规划

热门文章

  1. svn更新报错:svn:Checksum mismatch while updating;expected: '9a8c8856b74e4545bf2e52e2b54b23a6', actual: '
  2. LeetCode_Sorting_1753. Maximum Score From Removing Stones 移除石子的最大得分【脑筋急转弯】【C++】【中等】
  3. 网络爬虫入门:网络爬虫的目的,企业获取数据的方式,可以用于做爬虫的程序语言,爬虫爬取数据的步骤
  4. FILA FUSION官宣首位品牌代言人欧阳娜娜
  5. python修改屏幕分辨率_Python学习第150课——虚拟机切换鼠标状态以及调整桌面分辨率...
  6. Java Spring 分库分表方案概述
  7. ARM与x86 CPU架构对比
  8. C++二维数组做函数参数
  9. 【Problem Solving】
  10. 苹果持续回调做多注意节奏,橡胶认购大涨,YP再创新低2022.4.18