一、简介

截图(蓝牙查找界面,帮助界面,主控制界面1,主控制界面2,主控制界面3)

Android蓝牙更新_java

  

Android蓝牙更新_Android蓝牙更新_02

  

Android蓝牙更新_android_03

  

Android蓝牙更新_Android蓝牙更新_04

  

Android蓝牙更新_java_05

开发环境:Eclipse adt。

主要功能:

1.蓝牙连接、断开、接收数据、发送数据、自动连接。

2.指令发送按钮、拖动条、加减微调,定时发送。接收解析并设定参数到界面。

3.版本更新。含检查新版本,下载,安装。

4.帮助。使用说明、购买链接、版本等。

(一定要允许打开蓝牙、定位、读写存储、网络权限,APP提示时不要拒绝)

对应源码: 

─src 
     ├─com
     │  ├─iswitch
     │  │  └─iswitch
     │  │          AboutActivity.java//帮助界面
     │  │          DeviceListActivity.java//蓝牙查找界面
     │  │          MainActivity.java //主控制界面(含蓝牙连接、断开、收发数据)
     │  │          
     │  └─trinet
     │      └─util
     │              AppUtils.java //获得app通用信息,包名、版本等
     │              DialogHelper.java //对话框通用范例,下载对话框等
     │              NetHelper.java //网络通用处理 
     │              
     ├─SilentInstall
     │      SilentInstall.java //安装处理
     │      
     └─update
         └─test
                 UpdateManager.java //更新管理处理
                 Update_TestActivity.java //检查更新界面(启动界面)

二、开发的顺序

1.先通用蓝牙连接、断开、接收数据、发送数据稳定。基础没弄好就不能做后面的。

      有些蓝牙模块不一样,2.0与4.0不是一个结构,不是改改就行。

2.界面确定。xml文件确定好。

3.修改源码。源码是根据界面来写,所以先要确定界面。指令发送按钮、拖动条、加减微调,定时发送。接收解析设定参数。

4.版本更新等其它功能。

阅读源码是根据界面xml来找源码。源码都有注释。

 

三、详解——蓝牙连接、断开、接收数据、发送数据

蓝牙需要的权限

<uses-permission android:name="android.permission.BLUETOOTH" />
   <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
   <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>

ACCESS_COARSE_LOCATION没有,6.0及以上的手机会搜不到蓝牙模块。

自动连接逻辑:

连接成功后保存连接地址,再次打开APP时直接从已经配对的设备中找保存的连接地址,找到就直接返回地址去连接。没有找到就扫描。设备不在就删除保存的地址再扫描。扫描时能搜到的设备才显示出来(配对了不在线也不显示)。第一次需手工配对连接。自动连接连接的是上一次连接成功的设备。

连接逻辑:

放到线程中,UI更新放到UI线程。所以不死机。连接中再点连接会显示“请等待”。

new Thread(new Runnable() {
	            @Override
	            public void run() {
	               	//线程
	                runOnUiThread(new Runnable() {
	                    @Override
	                    public void run() {
	           			//线程结束后要更新的UI的线程
	                    }
	                });

	            }
	        }).start();

连接成功后,注册异常断开接收器,启动接收。

发送处理

// 发送响应
	public boolean sendString(String str) {
		if (_socket == null) {
			Toast.makeText(this, "未连接蓝牙", Toast.LENGTH_SHORT).show();
			return false;
		}
		if (str == null) {
			Toast.makeText(this, "发送内容为空", Toast.LENGTH_SHORT).show();
			return false;
		}

		try {

			OutputStream os = _socket.getOutputStream(); // 蓝牙连接输出流
			if(hex)
			{
				
				byte[] bos_hex = hexStringToBytes(str); // 十六进制
				os.write(bos_hex);
			}
			else
			{	
				byte[] bos = str.getBytes("GBK");	//native的Socket发送字节流默认是GB2312的,所以在Java方面需要指定GB2312
				os.write(bos);
			
					Toast.makeText(this, "发送成功"+str, Toast.LENGTH_SHORT).show();
					return true;
				
			}
			
		} catch (IOException e) {
			return false;
		}
		return true;
	}

接收处理

// 接收数据线程
	Thread ReadThread = new Thread() {

		public void run() {
			int num = 0;
			byte[] buffer = new byte[1024];
			byte[] buffer_new = new byte[1024];
			bRun = true;
			// 接收线程
			while (true) {
				try {
					while (is.available() == 0) {        //无接收数据
						while (bRun == false) {   //线程阻塞
						}
					}
					while (true) {
						if (1 == is.read(buffer, 0, 1)) // 一个一个地接收,把需要的数据放在buffer_new中    
						{
							if(hex)
							{
								 smsg+=String.format("%02X ",buffer[0]);//转为十六进制格式 所有接收
							}
							else
							{								
								if(127<(buffer[0]&0xff)) //解决汉字被截断
								{
									if (1 == is.read(buffer, 1, 1))   //昇954E 4E =78 少于128  所以只能判断第一个字节
									{
										smsg+=new String(buffer, 0, 2, "GBK");  //+String.format("(%02X %02X )",buffer_new[0],buffer_new[1]);  //GBK   GBK  UTF-8
									}
									else
									{
										buffer_new[num++]=buffer[0];
										if(num==2)
										{
											smsg+=new String(buffer_new, 0, 2, "GBK");  //+String.format("(%02X %02X )",buffer_new[0],buffer_new[1]);  //GBK   GBK  UTF-8
											num=0;
										}
									}

								}
								else
									smsg+=new String(buffer, 0, 1, "GBK");  //GBK   GBK  UTF-8
							}
							
						}
						/*
						num = is.read(buffer); // 读入数据
						smsg += new String(buffer, 0, num, "GBK");  //GBK   GBK  UTF-8
						*/
						if (is.available() == 0)
							break; // 短时间没有数据才跳出进行显示
					}
					// 发送显示消息,进行显示刷新
					handler.sendMessage(handler.obtainMessage());
				} catch (IOException e) {
				}
			}
		}
	};

四、详解——指令发送按钮、拖动条、加减微调,定时发送。接收解析设定参数。

主要是批量处理(控件绑定,控件响应,控件指令,接收解析设定参数到界面)

控件批量处理范例

private String[][] toggleCommandArray={
			{"ty","tz"},                        			                        			                     
			{"tg","tk"},      //关闭,打开                			                   
			{"at","mt"},                       
			{"wx","yx"},
	}; 
	private int[] toggleIdArray={
			.ToggleButton11,
			.ToggleButton12,
			.ToggleButton01,
			.ToggleButton02,
	};
	private ToggleButton[] toggleArray=new ToggleButton[toggleIdArray.length];
		 private void setElse() //设置其它控件
		    {				
				for(int i=0;i<toggleIdArray.length;i++)
				{
					toggleArray[i] = (ToggleButton) findViewById(toggleIdArray[i]);
					toggleArray[i].setChecked(false); // 显示原状态 退刀关闭水钻机
					toggleArray[i].setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
								// @Override
								public void onCheckedChanged(CompoundButton v,
										boolean isChecked) {
								    //判断是不是点击触发的,否则当我setChecked()时会触发此listener
								    if(isToggle)
								        return; 
									for(int i=0;i<toggleIdArray.length;i++){
										if(v.getId()==toggleIdArray[i])
										{
											if (isChecked) 
											{
												sendString(toggleCommandArray[i][1]);
											}		
											else
											{
												sendString(toggleCommandArray[i][0]);
											}
										}
									}
								}
							});
				}
		    }

定时1秒发送

private void setElse() //设置其它控件
		    {
				toggleButton_switch3 = (ToggleButton) findViewById(.ToggleButton03);
				toggleButton_switch3.setChecked(false); // 显示原状态 
				toggleButton_switch3.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
							// @Override
							public void onCheckedChanged(CompoundButton buttonView,
									boolean isChecked) {
								if (isChecked) 
								{
									if (_socket != null) 
									{
										Toast.makeText(MainActivity.this, "启动定时发送", Toast.LENGTH_SHORT).show();
					            		handler2.postDelayed(runnable2,TIME);
									}
					            	else
					            	{
					         			Toast.makeText(MainActivity.this, "请先连接设备", Toast.LENGTH_SHORT).show();
					         			toggleButton_switch3.setChecked(false); // 显示原状态 
					            	}
								}
								else
									Toast.makeText(MainActivity.this, "关闭定时发送", Toast.LENGTH_SHORT).show();
								
							}
						});


		    }
		  Handler handler2 = new Handler();
			Runnable runnable2 = new Runnable() {

				@Override
				public void run() {
					// handler自带方法实现定时器
					try {
						if(toggleButton_switch3.isChecked())
		            	{		
							
							smsg="";
							dis.setText(smsg);//先清空接收区
				           	sendString(btCommandArray[0]);
		            		handler2.postDelayed(runnable2,TIME);	   //定时到后,重新定时         		
		            	}
		            	else
		            	{
		          
		            		
		            	}

						
					} catch (Exception e) {
						// 
						e.printStackTrace();
						// System.out.println("exception...");
					}
				}
			};

接收解析

接收测试数据示例:

sz:00.3
tf:00.2
rs:023
zf:02.2A
qs:024S
tz
tk
mt
yx

接收解析源码 

private void receive(String smsg)
		    {
		    	try {
		    		//Toast.makeText(this, "接收:"+smsg, Toast.LENGTH_SHORT).show();
					if (smsg.contains(":") && smsg.endsWith("\r\n") &&smsg.length()>5) {						
						for(int i=0;i<seekBarIdArray.length;i++)
						{
							String s = valueCommandPreArray[i]+":"; //前缀,格式sz:00.0\r\n
							if (smsg.contains(s)) {
								int beginIndex = smsg.lastIndexOf(s);
								int endIndex = smsg.indexOf("\r\n",beginIndex + s.length()); //后缀
								String strData=smsg.substring(beginIndex + s.length(), endIndex).replace("A", "").replace("S", "");//取得的数据
								if(i<4 || i==6 || i==9)//这些有一位小数
								{
								
									valueArray[i]=(int)(10*Float.parseFloat(strData));
								}
								else
								{
									valueArray[i]=Integer.parseInt(strData);
								}
								tvsbSetText(i);	
								sbArray[i].setProgress(valueArray[i] );
							}
						}						
					}
					
				} catch (Exception e) {
				}
		    	
				try {
					//toggle开关
					 if (smsg.length()>15 && (smsg.endsWith("yx\r\n") || smsg.endsWith("wx\r\n"))) {	
						 for(int i=0;i<toggleIdArray.length;i++)
						 {
							 isToggle=true;
							 if (smsg.contains(toggleCommandArray[i][0]+"\r\n"))//ty off
							 {
								 toggleArray[i].setChecked(false);
								// toggleArray[i].invalidate();
							 }
							 else  if(smsg.contains(toggleCommandArray[i][1]+"\r\n"))//tz on
							{
								toggleArray[i].setChecked(true);
								//toggleArray[i].validate();
							}
							 isToggle=false;
						 }
					 }
		        } catch (Exception e) {
		            // TODO Auto-generated catch block
		            e.printStackTrace();
		        }
		    }

五、详解——版本更新

版本更新需要的权限

<uses-permission android:name="android.permission.INTERNET"></uses-permission>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 
   <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

逻辑顺序:

1.先检查更新。读这个版本说明文件Update_Test_version.txt内容 ,里面的版本与APP版本不一致就提示更新。所以版本说明文件Update_Test_version.txt内容 版本号要与更新的MainActivitySZJ.apk版本一致。verCode一致就行。android手机不允许更新的版本小于当前安装版本。无网络、网络处理失败和用户取消更新,就跳过。

[{"verCode":"4","verName":"1.4"}]

2. 下载apk文件。下载完后当前是放在手机根目录下。

3.安装。先静默安装(全自动,需root权限) ,不具备或失败就使用提示安装。

4.启动 新安装的apk(静默安装才有效)。


Android蓝牙更新_Android蓝牙更新_06

版本更新配置修改

检查更新

private void getCurVersion() { //得到当前版本号
		try {
			PackageInfo pInfo = ctx.getPackageManager().getPackageInfo(
					ctx.getPackageName(), 0);
			curVersion = pInfo.versionName;
			curVersionCode = pInfo.versionCode;
		} catch (NameNotFoundException e) {
			Log.e("update", e.getMessage());
			curVersion = "1.1.1000";
			curVersionCode = 111000;
		}

	}

	public void checkUpdate() {		//检查是否有更新
		hasNewVersion = false;
		new Thread(){
			// ***************************************************************
			/**
			 * @by wainiwann add
			 * 
			 */
			@Override
			public void run() {
				Log.i("@@@@@", ">>>>>>>>>>>>>>>>>>>>>>>>>>>getServerVerCode() ");
				try {

					String verjson = NetHelper.httpStringGet(UPDATE_CHECKURL);
					Log.i("@@@@", verjson
							+ "**************************************************");
					JSONArray array = new JSONArray(verjson);

					if (array.length() > 0) {
						JSONObject obj = array.getJSONObject(0);
						try {
							newVersionCode = Integer.parseInt(obj.getString("verCode"));
							newVersion = obj.getString("verName");
							updateInfo = "";
							Log.i("newVerCode", newVersionCode
									+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
							Log.i("newVerName", newVersion
									+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
							if (newVersionCode > curVersionCode) {
								hasNewVersion = true; //有新版本,需更新
							}
						} catch (Exception e) {
							newVersionCode = -1;
							newVersion = "";
							updateInfo = "";
							
						}
					}
				} catch (Exception e) {
					Log.e("update", e.getMessage());
				}
				updateHandler.sendEmptyMessage(UPDATE_CHECKCOMPLETED);
			};
			// ***************************************************************
		}.start();

	}

下载apk

public void downloadPackage() 
	{
		
		
		new Thread() {			
			 @Override  
		        public void run() {  
		            try {  
		                URL url = new URL(UPDATE_DOWNURL);  
		              
		                HttpURLConnection conn = (HttpURLConnection)url.openConnection();  
		                conn.connect();  
		                int length = conn.getContentLength();  
		                InputStream is = conn.getInputStream();  
		                  
		               
		                File ApkFile = new File(savefolder,UPDATE_SAVENAME);
		                
		                
		                if(ApkFile.exists())
		                {
		                	
		                	ApkFile.delete();
		                }
		                
		                
		                FileOutputStream fos = new FileOutputStream(ApkFile);  
		                 
		                int count = 0;  
		                byte buf[] = new byte[512];  
		                  
		                do{  
		                	
		                    int numread = is.read(buf);  
		                    count += numread;  
		                    progress =(int)(((float)count / length) * 100);  
		                   
		                    updateHandler.sendMessage(updateHandler.obtainMessage(UPDATE_DOWNLOADING)); 
		                    if(numread <= 0){      
		                        
		                    	updateHandler.sendEmptyMessage(UPDATE_DOWNLOAD_COMPLETED);
		                        break;  
		                    }  
		                    fos.write(buf,0,numread);  
		                }while(!canceled);  
		                if(canceled)
		                {
		                	updateHandler.sendEmptyMessage(UPDATE_DOWNLOAD_CANCELED);
		                }
		                fos.close();  
		                is.close();  
		            } catch (MalformedURLException e) {  
		                e.printStackTrace(); 
		                
		                updateHandler.sendMessage(updateHandler.obtainMessage(UPDATE_DOWNLOAD_ERROR,e.getMessage()));
		            } catch(IOException e){  
		                e.printStackTrace();  
		                
		                updateHandler.sendMessage(updateHandler.obtainMessage(UPDATE_DOWNLOAD_ERROR,e.getMessage()));
		            }  
		              
		        } 
		}.start();
	}

提示安装

private void installSelf(String fileName ) {//提示安装
			try{
            	Intent intent = new Intent(Intent.ACTION_VIEW); 
            	intent.setDataAndType(Uri.fromFile(new File(fileName)), "application/vnd.android.package-archive"); 
            	startActivity(intent);
			}catch(Exception e)
			{				
			}			
        }

启动

private void doStartApplicationWithPackageName(String packagename) {

			// 通过包名获取此APP详细信息,包括Activities、services、versioncode、name等等
			PackageInfo packageinfo = null;
			try {
				packageinfo = getPackageManager().getPackageInfo(packagename, 0);
			} catch (NameNotFoundException e) {
				e.printStackTrace();
			}
			if (packageinfo == null) {
				return;
			}

			// 创建一个类别为CATEGORY_LAUNCHER的该包名的Intent
			Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null);
			resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER);
			resolveIntent.setPackage(packageinfo.packageName);

			// 通过getPackageManager()的queryIntentActivities方法遍历
			List<ResolveInfo> resolveinfoList = getPackageManager()
					.queryIntentActivities(resolveIntent, 0);

			ResolveInfo resolveinfo = resolveinfoList.iterator().next();
			if (resolveinfo != null) {
				// packagename = 参数packname
				String packageName = resolveinfo.activityInfo.packageName;
				// 这个就是我们要找的该APP的LAUNCHER的Activity[组织形式:packagename.mainActivityname]
				String className = ;
				// LAUNCHER Intent
				Intent intent = new Intent(Intent.ACTION_MAIN);
				intent.addCategory(Intent.CATEGORY_LAUNCHER);

				// 设置ComponentName参数1:packagename参数2:MainActivity路径
				ComponentName cn = new ComponentName(packageName, className);

				intent.setComponent(cn);
				startActivity(intent);
			}
		}