安卓蓝牙4.0Socket通信传输图片

开发环境介绍:

  • 开发工具:AndroidStudio 3.1.2
  • 测试机:华为荣耀八青春版 安卓8.0(7.0) 红米note1S(4.4)
  • SDK版本:28
  • 项目最低支持安卓4.0版本

项目介绍:

-一台设备作为服务端,一台作为客户端,两台设备需要先蓝牙配对成功,然后才能开始打开APP进行操作。
- 一台手机作为服务端,右上方顶部ActionBar有一个按钮,点击后从本地图库添加图片和拍照获取图片。中间是GridView,再无其他布局。打开APP后自动开启Socket等待连接,与客户端连接上后,自动开始发送数据。
- 一台手机作为接收端,右上方顶部ActionBar有一个按钮,点击后从本地图库添加图片和拍照获取图片。底部有一个按钮,连接上服务端之后点击,即可开启读取数据的线程,获取服务器发送来的数据。
- 整个项目就一个Activity,想要安装服务端,就注释掉ClientActivity。客户端也是类似的操作。
- 所有显示的图片,支持单击全屏查看,可以手指操控放大缩小,长按图片会提示是否删除图片,点击图片名字,会提示更改图片名,弹出文本框。
- 由于文字聊天太简单,就没有发送文字的功能,后期有空的话,会做一个类似qq聊天的,内置小表情,可以发送本地图片。
- 同步增删,在查询结束后,客户端删除任意图片,服务端也跟着删除,客户端添加新图片,服务端也会自动添加
- 目前只支持300k以下图片慢传,修改每次传输的sleep方法的值可以实现以10倍速率传输20~30k以下的图

代码部分

服务器端
package com.kaytion.hgl.bluetoopicmanager;import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;import com.kaytion.hgl.bluetoopicmanager.entity.BluetoothMsg;
import com.kaytion.hgl.bluetoopicmanager.entity.Contant;
import com.kaytion.hgl.bluetoopicmanager.entity.MediaBean;
import com.kaytion.hgl.bluetoopicmanager.entity.Pic;
import com.kaytion.hgl.bluetoopicmanager.util.Constant;
import com.kaytion.hgl.bluetoopicmanager.util.DBManager;
import com.kaytion.hgl.bluetoopicmanager.util.MySQLiteHelper;
import com.kaytion.hgl.bluetoopicmanager.view.ZoomImageView;import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;public class MainActivity extends AppCompatActivity implements View.OnClickListener{Context context;public View contentView;public PopupWindow popupWindow;private MySQLiteHelper helper;ZoomImageView ziv;RelativeLayout zrl,choice;ImageView hide,choicepic;List<Pic> list;LayoutInflater inflater;LinearLayout bottom;GridviewAdapter gridviewAdapter;GridView gridView;Bitmap get_bitmap;Button queren;int FLAG=0;//判断是否获取到了图片String picName="0";private Handler mHandler;Boolean connectState=false;private BluetoothServerSocket mServerSocket; // 服务端socketprivate BluetoothAdapter mBluetoothAdapter;  // Bluetooth适配器private ServerThread mServerThread;//服务线程private ReadThread mReadThread;//读取数据线程public static final String PROTOCOL_SCHEME_RFCOMM = "btspp";// 服务器名称private BluetoothSocket socket1;              // 蓝牙socket对象Queue<byte[]> qb=new LinkedList<>();     //一个用来装byte[]的队列,每装完一张图片的数据后,就会清空,遵循先进先出原则List<MediaBean> mediaBeen = new ArrayList<>(); //装图片对象private ArrayList<String> chatMsg;@SuppressLint("HandlerLeak")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);context=this;//控件绑定和初始化initView();initPopupWindow();//设置点击监听事件setListener();//创建或打开数据库createDB();//查询数据库所有数据SelectALL();//判断页面是否有显示的内容if(list.size()<1){Toast.makeText(this,"您还没有录入过照片,快点击右上角添加图片吧!",Toast.LENGTH_SHORT).show();}else {//设置适配器,传递参数list,并显示图片和名字gridviewAdapter=new GridviewAdapter(this,list);gridView.setAdapter(gridviewAdapter);}mHandler=new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what==100) {SelectALL();//设置适配器,传递参数list,并显示图片和名字gridviewAdapter = new GridviewAdapter(context, list);gridView.setAdapter(gridviewAdapter);}}};}//获取Handler实例对象public Handler getHandler() {return this.mHandler;}public void initView() {mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();//初始化helperhelper=DBManager.getIntance(this);inflater= LayoutInflater.from(this);ziv=(ZoomImageView) findViewById(R.id.zoomimageview_iv);zrl=(RelativeLayout) findViewById(R.id.zoomimageview_rl);//初始化zoomimageview,避免空指针异常chuShi();bottom=(LinearLayout) findViewById(R.id.bottom_rl);choice=(RelativeLayout) findViewById(R.id.choice_rl);choice.setVisibility(View.GONE);hide=(ImageView) findViewById(R.id.hiden_iv);choicepic=(ImageView) findViewById(R.id.choice_iv);hide.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {zrl.setVisibility(View.GONE);}});gridView=(GridView) findViewById(R.id.pic_gridview);queren=(Button) findViewById(R.id.queren_btn);chatMsg= new ArrayList<String>();// 初始化list,我们有需要的话,后期可以将数据以列表的形式进行显示}public void setListener(){queren.setOnClickListener(this);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {MenuInflater inflater=getMenuInflater();inflater.inflate(R.menu.actionbar_menu,menu);return super.onCreateOptionsMenu(menu);}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {showPopWindow();return super.onOptionsItemSelected(item);}@Overridepublic void onClick(View view) {switch (view.getId()){//拍照case R.id.open_from_camera:Toast.makeText(this,"拍照",Toast.LENGTH_SHORT).show();FLAG=1;// 指定开启系统相机ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},1);Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);startActivityForResult(intent,101);hiddenWindow();break;//打开本地图库case R.id.open_album:Toast.makeText(this,"打开图库",Toast.LENGTH_SHORT).show();FLAG=1;Intent intent02 = new Intent();/* 开启Pictures画面Type设定为image */intent02.setType("image/*");/* 使用Intent.ACTION_GET_CONTENT这个Action */intent02.setAction(Intent.ACTION_GET_CONTENT);/* 取得相片后返回本画面 */startActivityForResult(intent02, 102);hiddenWindow();break;// 取消case R.id.cancel:hiddenWindow();break;case R.id.queren_btn://保存图片和随机生成的名字到本地指定文件夹和SQLite,隐藏预览用的RelativeLayoutcheckPic();break;}}//popupwindow界面设置private void initPopupWindow() { //要在布局中显示的布局contentView = LayoutInflater.from(this).inflate(R.layout.popupwindow_layout, null, false);
//实例化PopupWindow并设置宽高popupWindow = new PopupWindow(contentView, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);popupWindow.setBackgroundDrawable(new BitmapDrawable());//点击外部消失,这里因为PopupWindow填充了整个窗口,所以这句代码就没用了popupWindow.setOutsideTouchable(true); //设置可以点击popupWindow.setTouchable(true); //进入退出的动画popupWindow.setAnimationStyle(R.style.PopupAnimation);popupWindow.setFocusable(true);//获取焦点,就置于试图最顶层,盖住了activityTextView canmero=(TextView) contentView.findViewById(R.id.open_from_camera);TextView album=(TextView) contentView.findViewById(R.id.open_album);TextView cancle=(TextView) contentView.findViewById(R.id.cancel);cancle.setOnClickListener(this);canmero.setOnClickListener(this);album.setOnClickListener(this);}//显示popwindowprivate void showPopWindow() {fitPopupWindowOverStatusBar(true);View rootview = LayoutInflater.from(MainActivity.this).inflate(R.layout.activity_main,null);popupWindow.showAtLocation(rootview, Gravity.BOTTOM, 0, 0);}//隐藏popupwindowprivate void hiddenWindow(){Toast.makeText(this,"隐藏popupwindow!",Toast.LENGTH_SHORT).show();popupWindow.dismiss();}//popupwindow是否全屏显示,遮住其他控件public void fitPopupWindowOverStatusBar(boolean needFullScreen) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {try { //利用反射重新设置mLayoutInScreen的值,当mLayoutInScreen为true时则PopupWindow覆盖全屏。Field mLayoutInScreen = PopupWindow.class.getDeclaredField("mLayoutInScreen");mLayoutInScreen.setAccessible(true);mLayoutInScreen.set(popupWindow, needFullScreen);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}}//ZoomImageView需要一个初始图片public void chuShi() {Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(),R.drawable.defaults);ziv.setImage(bitmap);zrl.setVisibility(View.GONE);}//创建数据库public void createDB(){//getReadableDatabase()和getWritableDatabase()都是SQLiteOpenHelper类提供的(此处用的是其自定义子类的)用来创建或者打开SQLite数据库的//区别在于,如果磁盘已满,getReadableDatabase()就是只有“只读”的功能SQLiteDatabase db=helper.getWritableDatabase();//如果数据库存在,打开数据库;不存在,则先创建,再打开db.close();}//修改数据public void UpdatePicName(String picUrl,String name){SQLiteDatabase db2=helper.getWritableDatabase();String sql2="update "+ Constant.TABLE_NAME+" set "+Constant.NAME+"='"+name+"' where "+Constant.URL+"='"+picUrl+"'";DBManager.execSQL(db2,sql2);db2.close();}//删除数据public void DeletePic(String picUrl){SQLiteDatabase db3=helper.getWritableDatabase();String sql3="delete from "+Constant.TABLE_NAME+" where "+Constant.URL+"='"+picUrl+"'";DBManager.execSQL(db3,sql3);db3.close();}//查询所有数据,插入listpublic void SelectALL(){SQLiteDatabase db=helper.getWritableDatabase();String sql="select * from "+Constant.TABLE_NAME;Cursor cursor=DBManager.SelectDataBySql(db,sql,null);list=DBManager.cursorToList(cursor);db.close();//list已经获取到SQlite中所有数据if(list.size()>0) {Log.i("selectall", "" + list.get(0).getUrl());}}//GridView的适配器public class GridviewAdapter extends BaseAdapter {Context contexts;List<Pic> list2=new ArrayList<>();private class Holder{ImageView item_img;TextView item_tex;public ImageView getItem_img() {return item_img;}public void setItem_img(ImageView item_img) {this.item_img = item_img;}public TextView getItem_tex() {return item_tex;}public void setItem_tex(TextView item_tex) {this.item_tex = item_tex;}}public GridviewAdapter(Context context,List<Pic> l) {this.contexts=context;this.list2=l;}@Overridepublic View getView(final int position, View view, ViewGroup viewGroup) {Holder holder;if(view==null){view=inflater.inflate(R.layout.gridview_item_main,null);holder=new Holder();holder.item_img=(ImageView) view.findViewById(R.id.pic_iv);holder.item_tex=(TextView)view.findViewById(R.id.name_tv);//更换图片名holder.item_tex.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {final EditText et3=new EditText(contexts);new AlertDialog.Builder(contexts).setTitle("请输入新的图片名").setView(et3).setPositiveButton("确定",new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {if(et3.getText().toString().length()<1||et3.getText().toString().length()>20){Toast.makeText(contexts,"请输入1~20位字符数字组合!",Toast.LENGTH_SHORT).show();}else{UpdatePicName(list2.get(position).getUrl(),et3.getText().toString());RefreshUI();}}}).setNegativeButton("取消", null).show();UpdatePicName(list2.get(position).getUrl(),list2.get(position).getName());}});//设置长按事件holder.item_img.setOnLongClickListener(new bigClickListener(){@Overridepublic boolean onLongClick(View view) {//删除Toast.makeText(contexts,"正在准备删除",Toast.LENGTH_SHORT).show();AlertDialog alertDialog = new AlertDialog.Builder(contexts).setTitle("删除").setMessage("是否删除"+list2.get(position).getName()).setPositiveButton("确定", new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {//删除数据库记录DeletePic(list2.get(position).getUrl());//删除本地文件deleteSingleFile(list2.get(position).getUrl());RefreshUI();}}).setNegativeButton("取消",new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {return;}}).create(); // 创建对话框alertDialog.show(); // 显示对话框return true;}});holder.item_img.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//绝对路径转换为Bitmap类型图片try {//读取指定picName的图片转换为Bitmap类位图FileInputStream localStream = null;try {localStream = openFileInput(list2.get(position).getName());} catch (FileNotFoundException e) {e.printStackTrace();Toast.makeText(contexts,"没有在app安装目录data/data/找到指定名称的图片!",Toast.LENGTH_SHORT).show();}Bitmap bitmap = BitmapFactory.decodeStream(localStream);ziv.setImage(bitmap);zrl.setVisibility(View.VISIBLE);}catch (NullPointerException npe){npe.printStackTrace();Toast.makeText(contexts,"要放大的图片不存在!空指针异常,请0. 检查当前安卓版本是否支持file路径读取!",Toast.LENGTH_SHORT).show();}Toast.makeText(contexts,"您点击了第"+(position+1)+"个imageView",Toast.LENGTH_SHORT).show();}});view.setTag(holder);}else{holder=(Holder) view.getTag();}//读取指定picName的图片转换为Bitmap类位图FileInputStream localStream = null;try {localStream = openFileInput(list2.get(position).getName());} catch (FileNotFoundException e) {e.printStackTrace();Toast.makeText(contexts,"没有在app安装目录data/data/找到指定名字的图片!",Toast.LENGTH_SHORT).show();}Bitmap bitmap = BitmapFactory.decodeStream(localStream);holder.item_img.setImageBitmap(bitmap);holder.item_tex.setText(list2.get(position).getName());Log.i("uuuu","url="+list2.get(position).getUrl()+"\n"+"name="+list2.get(position).getName()+"\n"+"list长度:"+list2.size());return view;}@Overridepublic Object getItem(int position) {return position;}@Overridepublic long getItemId(int position) {return position;}@Overridepublic int getCount() {//根据图片对象数量设置对应item数量return list.size();}private class bigClickListener implements View.OnLongClickListener {@Overridepublic boolean onLongClick(View view) {return true;}}}//改写物理按键——返回的逻辑,先退出全屏@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {// TODO Auto-generated method stubif (keyCode == KeyEvent.KEYCODE_BACK) {//退出全屏if(zrl.getVisibility()==View.VISIBLE||choice.getVisibility()==View.VISIBLE) {zrl.setVisibility(View.GONE);choice.setVisibility(View.GONE);return true;}else {this.finish();System.exit(0);}}return super.onKeyDown(keyCode, event);}//删除指定的本地文件private boolean deleteSingleFile(String filePath$Name) {File file = new File(filePath$Name);// 如果文件路径所对应的文件存在,并且是一个文件,则直接删除if (file.exists() && file.isFile()) {if (file.delete()) {Log.e("--Method--", "Copy_Delete.deleteSingleFile: 删除单个文件" + filePath$Name + "成功!");RefreshUI();return true;} else {Toast.makeText(getApplicationContext(), "删除单个文件" + filePath$Name + "失败!", Toast.LENGTH_SHORT).show();return false;}} else {Toast.makeText(getApplicationContext(), "删除单个文件失败:" + filePath$Name + "不存在!", Toast.LENGTH_SHORT).show();return false;}}//保存图片的方法,把Bitmap图片放在根目录指定path的文件夹//安卓7.0以上需要注意Environment.getExternalStorageDirectory()不可以显示真实地址public void saveImage(Bitmap bm,String picname){try {FileOutputStream out=openFileOutput(picName,MODE_APPEND);//追加存储在data/data/目录下bm.compress(Bitmap.CompressFormat.JPEG, 100, out);out.flush();out.close();Toast.makeText(this,"保存路径为app安装路径的data/data/目录下:"+"\n"+"图片名称为:"+picname,Toast.LENGTH_LONG).show();}catch (IOException ioe){ioe.printStackTrace();}}//插入数据到SQLite数据库public void InsertPic(String url,String name){MySQLiteHelper helper= DBManager.getIntance(this);SQLiteDatabase db1=helper.getWritableDatabase();//方法一,调用api,可以看到结果ContentValues values=new ContentValues();values.put(Constant.URL,url);values.put(Constant.NAME,name);long result=db1.insert(Constant.TABLE_NAME,null,values);if (result>0){Log.i("insert", "成功插入到SQLite");}else {Log.i("insert", "插入SQLite失败");}db1.close();Log.i("insert", "成功保存到SQLite");}@Overrideprotected void onActivityResult(int requestCode, int resultCode,Intent data) {super.onActivityResult(requestCode, resultCode, data);//一搬的情况,取消是0, 拍照成功是-1if(resultCode==RESULT_OK) {//其他操作switch (requestCode) {case 101://调用相机,返回后的相关操作//获取图片路径Bitmap takephotos = (Bitmap) data.getExtras().get("data");//更新相片对应的Bitmap信息get_bitmap=takephotos;//显示预览图choicepic.setImageBitmap(takephotos);choice.setVisibility(View.VISIBLE);Log.i("cccc", "本地图片选择完成"  +takephotos);FLAG=1;break;case 102://选择完图片后,执行的操作Uri uri2= data.getData();//获取图片路径String  img_url=uri2.getPath();//获取BitMap对象ContentResolver cr = this.getContentResolver();try {Bitmap local_pic = BitmapFactory.decodeStream(cr.openInputStream(uri2));//更新图片信息get_bitmap=local_pic;//显示选择的图片的小图choicepic.setImageBitmap(local_pic);choice.setVisibility(View.VISIBLE);Log.i("bbbbb", "本地图片选择完成,img_url=" +img_url);} catch (FileNotFoundException e) {Log.e("Exception", e.getMessage(),e);}break;}}}//判断是否获取到了图片,如果有,则显示预览,执行保存图片等方法public void checkPic(){//将得到的bitmap对象存入PIC_URL对应的目录,并添加到SQLite数据库if(FLAG==0){Toast.makeText(this,"老哥,你丫还没搞到图片呢,玩个个鸡儿?",Toast.LENGTH_SHORT).show();}else {choice.setVisibility(View.GONE);picName = "" + System.currentTimeMillis() + ".jpg";saveImage(get_bitmap,picName);FLAG=0;//开始保存信息到Sqlite数据库,目前url没啥用。有效数据是picNameInsertPic(Constant.PATH+picName,picName);RefreshUI();}}//第十六部分...存储byte[]为图片文件/*** 存储图片16进制串为图片文件* @param queue  保存了很多图片的byte[]数据* @param length 所有byte[]的数据加起来的长度* @param path 要保存的图像文件路径* @param imgName 图片名*/public void saveToImgFile( Queue<byte []>queue,int length,String path,String imgName){int index=0,k=0;byte[] b=new byte[length];while(length>index){k = k + 1;
//              byte[] m=queue.poll();if(queue.peek()!=null){int ls=queue.peek().length;System.arraycopy(queue.poll(), 0, b, index, ls);index = index + ls;System.out.println("----LENGTH的值(传递来的队列b的容量)为----"+length );System.out.println("----当前拼接次数为:----" + k);System.out.println("----当前拼接对象的大小为:----" + ls);System.out.println("----下一次的起始位置INDEX为:----" + index);}}System.out.println("拼接完成,开始保存!");if (b == null || b.length == 0) {return;}//方法一//byte[]转BitmappicName=imgName;Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);// Bitmap保存到/data目录下saveImage(bitmap,picName);//同步保存图片信息至SQLiteInsertPic(Contant.PATH+picName,picName);Log.i("savaToImgFile","保存成功"+picName);}//蓝牙功能块//扫描周围蓝牙private void scanDevice() {// TODO Auto-generated method stubif (mBluetoothAdapter.isDiscovering()) {  //如果正在处于扫描过程...mBluetoothAdapter.cancelDiscovery();  //取消扫描...} else {chatMsg.clear();// 每次扫描前都先判断一下是否存在已经配对过的设备Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();if (pairedDevices.size() > 0) {String s="已保存的蓝牙";for (BluetoothDevice devices : pairedDevices) {chatMsg.add( devices.getAddress());s=s+devices.getAddress();}Toast.makeText(getApplicationContext(),"扫描到的已配对蓝牙设备名(MAC地址已保存):"+s,Toast.LENGTH_SHORT).show();} else {Toast.makeText(getApplicationContext(),"未扫描到配对过的蓝牙设备",Toast.LENGTH_SHORT).show();}/* 开始搜索 */mBluetoothAdapter.startDiscovery();}}//检测蓝牙状态和服务线程状态public void checkBTServer(){if (BluetoothMsg.isOpen) {Toast.makeText(this, "服务器端口已打开,可以通信", Toast.LENGTH_SHORT).show();}if (mBluetoothAdapter != null) {Toast.makeText(this,"机器带有蓝牙" ,Toast.LENGTH_SHORT).show();if (!mBluetoothAdapter.isEnabled()) {// 发送打开蓝牙的意图Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableIntent, RESULT_FIRST_USER);// 设置蓝牙的可见性,最大值3600秒,默认120秒,0表示永远可见(作为客户端,可见性可以不设置,服务端必须要设置)Intent displayIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);displayIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0);startActivity(displayIntent);Toast.makeText(this, "蓝牙已打开", Toast.LENGTH_SHORT).show();// 直接打开mBluetoothAdapter.enable();}}}//开启服务线程public void startServer(){Toast.makeText(this, "准备服务线程对象", Toast.LENGTH_SHORT).show();//取消搜索设备的动作,否则接下来的设备连接会失败mBluetoothAdapter.cancelDiscovery();mServerThread = new ServerThread(this);mServerThread.start();Toast.makeText(this, "服务线程已开启", Toast.LENGTH_SHORT).show();BluetoothMsg.isOpen = true;}//服务器线程private class ServerThread extends Thread {Context serverContext;public ServerThread(Context context){serverContext=context;}public void run() {try {/* 创建一个蓝牙服务器* 参数分别:服务器名称、UUID   */mServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(PROTOCOL_SCHEME_RFCOMM,UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));Log.i("serverthread","服务线程已开启");/* 接受客户端的连接请求 */// 这是一个阻塞过程,直到建立一个连接或者连接失效// 通过BluetoothServerSocket得到一个BluetoothSocket对象,管理这个连接socket1 = mServerSocket.accept();//上一步连接成功则执行下一步Log.i("serverthread","与客户端连接成功");openReadThread();//openReadThread();//直接发送数据//                耗时操作在子线程中执行new Thread(new Runnable() {@Overridepublic void run() {//sp.getBoolean("boolean",false)//判断数据是否为空Looper.prepare();for(int i=0;i<list.size();i++) {String pictureName = null;pictureName = list.get(i).getName();if (pictureName != null) {//获取指定图片的BitmapsendByteCut(splitPacketFor20Byte(BitmapToByte(getBitMap(pictureName))));}}}}).start();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();Log.i("serverthread","刚刚与客户端连接失败");}}};//数据读取线程//打开数据接收端口public void openReadThread(){//启动接受数据的线程mReadThread = new ReadThread(this);mReadThread.start();Log.i("serverthread","数据接收端口打开成功");}//接收数据private class ReadThread extends Thread {Context readContext;public ReadThread(Context context){readContext=context;}public void run() {//存储位置byte[] buffer;//收到的所有数据段的长度之和int allLength=0;//数据数量int bytes;InputStream mmInStream = null;try {mmInStream = socket1.getInputStream();} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}Looper.prepare();//Looper初始化boolean k=true;while (k) {try {// Read from the InputStream// 读取客户端发送的一个byte[]信息到buffer并写入内存,如果客户端没有发送byte[](bytes=0)将被阻塞int count=0;while (count == 0) {//avaliable()获取下一次读取流的长度if(mmInStream.available()<20&&mmInStream.available()>0){count=20;}else {count = mmInStream.available();}}buffer=new byte[count];if ((bytes = mmInStream.read(buffer)) > 0) {byte[] buf_data = new byte[bytes];for (int i = 0; i < bytes; i++) {buf_data[i] = buffer[i];}//打印接收到的数据System.out.println("---GET到的buf__data---" + buf_data);System.out.println("---GET到的数据长度---" + buf_data.length);//buffer_data是获取到的一个byte[]//判断接收到的数据是不是终结符,是的话,就说明queue已经接收到一个完整图片的所有byte[],否则继续存入queueString s = "no";s = new String(buf_data);Log.i("ssssss", "s的值为" + s);if (s.endsWith("ok")) {//执行byte[]拼接方法,并且将拼接得到的数据保存为本地指定路径图片System.out.println("----保存图片保存图片保存图片保存图片----");String jj = "s" + System.currentTimeMillis() + ".jpg";saveToImgFile(qb, allLength, Contant.PATH, jj);System.out.println("----保存完毕,清空QUEUE----");
//                            k=false;qb.clear();allLength = 0;//更新界面RefreshUI();}else if(s.contains("delete")){System.out.println("准备执行删除操作!");String order_d=s.replace("delete","");int p=Integer.parseInt(order_d);deleteSingleFile(list.get(p).getUrl());DeletePic(list.get(p).getUrl());System.out.println("删除操作执行完毕!");RefreshUI();} else{//未读取到终止符,则将byte[]继续存入queueif(s.endsWith(".jpg")){System.out.println("----成功接收到图片名,开始准备接收以下图片数据:----" + s);qb.clear();allLength=0;}else {qb.offer(buf_data);allLength=allLength+bytes;System.out.println("----存入的buf_data的长度----" + bytes);System.out.println("----存入数据成功,当前qb.size()----" + qb.size());}}} else {try {mmInStream.close();} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}}} catch(IOException e){e.printStackTrace();break;} catch (Exception e) {e.printStackTrace();}}}}//发送数据线程//第十部分...读取指定位置/data/data的图片,转换为Bitmap位图public Bitmap getBitMap(String picName) {//读取指定picName的图片转换为Bitmap类位图FileInputStream localStream = null;try {localStream = openFileInput(picName);} catch (FileNotFoundException e) {e.printStackTrace();Toast.makeText(getApplicationContext(),"没有在app安装目录data/data/找到要显示的图片!",Toast.LENGTH_SHORT).show();}Bitmap bitmap = BitmapFactory.decodeStream(localStream);return bitmap;}//第十一部分...BitMap转换为byte[]            <!---然后再进一步转换为16进制字符串-->public byte[] BitmapToByte(Bitmap bitMap){ByteArrayOutputStream stream = new ByteArrayOutputStream();Log.i("stream","----stream="+stream);bitMap.compress(Bitmap.CompressFormat.JPEG, 100, stream);// (0 - 100)压缩文件byte[] bt = stream.toByteArray();System.out.println("图片转换得到的byte[]长度为:"+bt.length);return bt;}//第十二部分...将byte[]分段存储到queue中public Queue<byte[]> splitPacketFor20Byte(byte[] data) {Queue<byte[]> dataInfoQueue = new LinkedList<>();if (data != null) {int index = 0;do {byte[] surplusData=new byte[data.length-index];byte[] currentData=null;System.arraycopy(data, index, surplusData, 0, data.length-index);if (data.length-index <= 20) {currentData = new byte[data.length-index];System.arraycopy(surplusData, 0, currentData, 0, data.length-index);index =data .length;} else {currentData = new byte[20];System.arraycopy(data, index, currentData, 0, 20);index =index+20;}dataInfoQueue.offer(currentData);} while (index < data.length);}Log.i("picdatalength","图片转为数组后的数组长度"+data.length);System.out.println("第一次,byte数组分段存入queue后,queue的长度:"+dataInfoQueue.size());return dataInfoQueue;}//第十三部分...分段发送byte[](每次发送queue中的一个,发完即删),全部发送完后,最后面跟着发送一个指定长度的终止信号public void sendByteCut(Queue<byte[]> qb){int qSize=qb.size();byte[] b;String sign = "ok";for (int i=0;i<qSize+1 ;i++) {if (socket1 == null) {Toast.makeText(getApplicationContext(), "没有可用的连接", Toast.LENGTH_SHORT).show();return;}System.out.println("-----一次发送的开始-------"+(qSize%20));//如果连接上了,那么获取输出流...try {OutputStream os = socket1.getOutputStream();//判断图片的所有数据是否传输完if (i==qSize) {//添加终止信号isendb = sign.getBytes();System.out.println("---图片数据队列的长度QUEUEsize(不算终止符)---"+qSize);System.out.println("----b不为空,终止符获取打包成功,即将传送,当前数据段段数:----"+(i+1));} else {//poll()获取队头元素并移除int blength=qb.peek().length;b=new byte[blength];System.arraycopy(qb.poll(),0,b,0,blength);System.out.println("----拷贝成功,即将发送的数据段长度为:----"+blength);}System.out.println("---SEND---即将发送,当前数据段b:"+(i+1)+"---b:"+b);if (b==null){System.out.println("--- b=NULL ---:");}else {os.write(b);//获取所有的字节然后往外发送...System.out.println("----b不为空,发送成功,已发送数据段段数:----"+(i+1));}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}try {//每发送一段就休息10ms,等待接收端单片机处理完刚接收的数据。(设置为1ms,可以提升十倍速度快速传输不超过25k的图片,10ms支持300k以下的图慢传)Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}//应用就绪时开始扫描蓝牙设备,获取周围蓝牙设备名称和MAC地址保存起来@Overrideprotected void onResume() {super.onResume();//检测设备是否具备蓝牙功能,如果有则打开蓝牙checkBTServer();//扫描周边设备,获取他们的信息(主要是已配对设备的MAC地址)scanDevice();//启动服务startServer();bottom.setVisibility(View.GONE);}//应用销毁时释放资源@Overrideprotected void onDestroy() {// TODO Auto-generated method stubsuper.onDestroy();if (mBluetoothAdapter != null) {mBluetoothAdapter.cancelDiscovery();// 关闭蓝牙mBluetoothAdapter.disable();BluetoothMsg.isOpen = false;}closeClient();}// 停止服务,释放资源private void closeClient() {new Thread() {public void run() {if (mServerThread != null) {mServerThread.interrupt();mServerThread = null;}try {if (socket1 != null) {socket1.close();socket1 = null;}} catch (IOException e) {// TODO: handle exception}}}.start();}//刷新UIpublic void RefreshUI(){//刷新UI界面new Thread(new Runnable() {@Overridepublic void run() {Message msg=Message.obtain();msg.what=100;getHandler().sendMessage(msg);Log.i("deletelog","已发送消息给Handler:"+msg);}}).start();}
}
客户端
package com.kaytion.hgl.bluetoopicmanager;import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;import com.kaytion.hgl.bluetoopicmanager.entity.BluetoothMsg;
import com.kaytion.hgl.bluetoopicmanager.entity.Contant;
import com.kaytion.hgl.bluetoopicmanager.entity.MediaBean;
import com.kaytion.hgl.bluetoopicmanager.entity.Pic;
import com.kaytion.hgl.bluetoopicmanager.util.Constant;
import com.kaytion.hgl.bluetoopicmanager.util.DBManager;
import com.kaytion.hgl.bluetoopicmanager.util.MySQLiteHelper;
import com.kaytion.hgl.bluetoopicmanager.view.ZoomImageView;import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;public class MainActivity extends AppCompatActivity implements View.OnClickListener{public View contentView;public PopupWindow popupWindow;private MySQLiteHelper helper;ZoomImageView ziv;RelativeLayout zrl,choice;ImageView hide,choicepic;List<Pic> list;LayoutInflater inflater;GridviewAdapter gridviewAdapter;GridView gridView;Bitmap get_bitmap;Button testBtn,creatBtn,connectBtn,queren,refresh;int FLAG=0;//判断是否获取到了图片String picName="0";private BluetoothDevice device;             // 蓝牙设备对象private BluetoothAdapter mBluetoothAdapter;  // Bluetooth适配器private ClientThread mClientThread;//客户端线程public static final String PROTOCOL_SCHEME_RFCOMM = "btspp";// 服务器名称private BluetoothSocket socket2;              // 蓝牙socket对象Queue<byte[]> qb=new LinkedList<>();     //一个用来装byte[]的队列,每装完一张图片的数据后,就会清空,遵循先进先出原则private ReadThread mReadThread; //接收数据线程private ArrayList<String> chatMsg;Boolean connectState=false;//客户端连接状态private Handler mHandler;Context context;@SuppressLint("HandlerLeak")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);context=this;//控件绑定和初始化initView();initPopupWindow();//设置点击监听事件setListener();//创建或打开数据库createDB();//查询数据库所有数据SelectALL();//判断页面是否有显示的内容if(list.size()<1){Toast.makeText(this,"您还没有录入过照片,快点击右上角添加图片吧!",Toast.LENGTH_SHORT).show();}else {//设置适配器,传递参数list,并显示图片和名字gridviewAdapter=new GridviewAdapter(this,list);gridView.setAdapter(gridviewAdapter);}mHandler=new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);if (msg.what==100) {SelectALL();//设置适配器,传递参数list,并显示图片和名字gridviewAdapter = new GridviewAdapter(context, list);gridView.setAdapter(gridviewAdapter);}}};}public Handler getHandler(){return this.mHandler;}public void initView() {mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();//初始化helperhelper=DBManager.getIntance(this);inflater= LayoutInflater.from(this);ziv=(ZoomImageView) findViewById(R.id.zoomimageview_iv);zrl=(RelativeLayout) findViewById(R.id.zoomimageview_rl);//初始化zoomimageview,避免空指针异常chuShi();choice=(RelativeLayout) findViewById(R.id.choice_rl);choice.setVisibility(View.GONE);hide=(ImageView) findViewById(R.id.hiden_iv);choicepic=(ImageView) findViewById(R.id.choice_iv);hide.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {zrl.setVisibility(View.GONE);}});gridView=(GridView) findViewById(R.id.pic_gridview);testBtn=(Button) findViewById(R.id.test_connection_btn);creatBtn=(Button) findViewById(R.id.creat_server);connectBtn=(Button) findViewById(R.id.connection_server);queren=(Button) findViewById(R.id.queren_btn);refresh=(Button) findViewById(R.id.refresh_btn);refresh.setVisibility(View.GONE);chatMsg= new ArrayList<String>();// 初始化list,我们有需要的话,后期可以将数据以列表的形式进行显示}public void setListener(){testBtn.setOnClickListener(this);creatBtn.setOnClickListener(this);connectBtn.setOnClickListener(this);queren.setOnClickListener(this);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {MenuInflater inflater=getMenuInflater();inflater.inflate(R.menu.actionbar_menu,menu);return super.onCreateOptionsMenu(menu);}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {showPopWindow();return super.onOptionsItemSelected(item);}@Overridepublic void onClick(View view) {switch (view.getId()){//拍照case R.id.open_from_camera:Toast.makeText(this,"拍照",Toast.LENGTH_SHORT).show();FLAG=1;// 指定开启系统相机ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},1);Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);startActivityForResult(intent,101);hiddenWindow();break;//打开本地图库case R.id.open_album:Toast.makeText(this,"打开图库",Toast.LENGTH_SHORT).show();FLAG=1;Intent intent02 = new Intent();/* 开启Pictures画面Type设定为image */intent02.setType("image/*");/* 使用Intent.ACTION_GET_CONTENT这个Action */intent02.setAction(Intent.ACTION_GET_CONTENT);/* 取得相片后返回本画面 */startActivityForResult(intent02, 102);hiddenWindow();break;// 取消case R.id.cancel:hiddenWindow();break;//连接测试case R.id.test_connection_btn:connectBTSuround();//发送消息break;case R.id.queren_btn://保存图片和随机生成的名字到本地指定文件夹和SQLite,隐藏预览用的RelativeLayoutcheckPic();break;}}//popupwindow界面设置private void initPopupWindow() { //要在布局中显示的布局contentView = LayoutInflater.from(this).inflate(R.layout.popupwindow_layout, null, false);
//实例化PopupWindow并设置宽高popupWindow = new PopupWindow(contentView, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);popupWindow.setBackgroundDrawable(new BitmapDrawable());//点击外部消失,这里因为PopupWindow填充了整个窗口,所以这句代码就没用了popupWindow.setOutsideTouchable(true); //设置可以点击popupWindow.setTouchable(true); //进入退出的动画popupWindow.setAnimationStyle(R.style.PopupAnimation);popupWindow.setFocusable(true);//获取焦点,就置于试图最顶层,盖住了activityTextView canmero=(TextView) contentView.findViewById(R.id.open_from_camera);TextView album=(TextView) contentView.findViewById(R.id.open_album);TextView cancle=(TextView) contentView.findViewById(R.id.cancel);cancle.setOnClickListener(this);canmero.setOnClickListener(this);album.setOnClickListener(this);}//显示popwindowprivate void showPopWindow() {fitPopupWindowOverStatusBar(true);View rootview = LayoutInflater.from(MainActivity.this).inflate(R.layout.activity_main,null);popupWindow.showAtLocation(rootview, Gravity.BOTTOM, 0, 0);}//隐藏popupwindowprivate void hiddenWindow(){Toast.makeText(this,"隐藏popupwindow!",Toast.LENGTH_SHORT).show();popupWindow.dismiss();}//popupwindow是否全屏显示,遮住其他控件public void fitPopupWindowOverStatusBar(boolean needFullScreen) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {try { //利用反射重新设置mLayoutInScreen的值,当mLayoutInScreen为true时则PopupWindow覆盖全屏。Field mLayoutInScreen = PopupWindow.class.getDeclaredField("mLayoutInScreen");mLayoutInScreen.setAccessible(true);mLayoutInScreen.set(popupWindow, needFullScreen);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}}//ZoomImageView需要一个初始图片public void chuShi() {Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(),R.drawable.defaults);ziv.setImage(bitmap);zrl.setVisibility(View.GONE);}//创建数据库public void createDB(){//getReadableDatabase()和getWritableDatabase()都是SQLiteOpenHelper类提供的(此处用的是其自定义子类的)用来创建或者打开SQLite数据库的//区别在于,如果磁盘已满,getReadableDatabase()就是只有“只读”的功能SQLiteDatabase db=helper.getWritableDatabase();//如果数据库存在,打开数据库;不存在,则先创建,再打开db.close();}//修改数据public void UpdatePicName(String picUrl,String name){SQLiteDatabase db2=helper.getWritableDatabase();String sql2="update "+ Constant.TABLE_NAME+" set "+Constant.NAME+"='"+name+"' where "+Constant.URL+"='"+picUrl+"'";DBManager.execSQL(db2,sql2);db2.close();}//删除数据public void DeletePic(String picUrl){SQLiteDatabase db3=helper.getWritableDatabase();String sql3="delete from "+Constant.TABLE_NAME+" where "+Constant.URL+"='"+picUrl+"'";DBManager.execSQL(db3,sql3);db3.close();}//查询所有数据,插入listpublic void SelectALL(){SQLiteDatabase db=helper.getWritableDatabase();String sql="select * from "+Constant.TABLE_NAME;Cursor cursor=DBManager.SelectDataBySql(db,sql,null);list=DBManager.cursorToList(cursor);db.close();//list已经获取到SQlite中所有数据if(list.size()>0) {Log.i("selectall", "" + list.get(0).getUrl());}}//GridView的适配器public class GridviewAdapter extends BaseAdapter {Context contexts;List<Pic> list2=new ArrayList<>();private class Holder{ImageView item_img;TextView item_tex;public ImageView getItem_img() {return item_img;}public void setItem_img(ImageView item_img) {this.item_img = item_img;}public TextView getItem_tex() {return item_tex;}public void setItem_tex(TextView item_tex) {this.item_tex = item_tex;}}public GridviewAdapter(Context context,List<Pic> l) {this.contexts=context;this.list2=l;}@Overridepublic View getView(final int position, View view, ViewGroup viewGroup) {Holder holder;if(view==null){view=inflater.inflate(R.layout.gridview_item_main,null);holder=new Holder();holder.item_img=(ImageView) view.findViewById(R.id.pic_iv);holder.item_tex=(TextView)view.findViewById(R.id.name_tv);//更换图片名holder.item_tex.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {final EditText et3=new EditText(contexts);new AlertDialog.Builder(contexts).setTitle("请输入新的图片名").setView(et3).setPositiveButton("确定",new DialogInterface.OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {if(et3.getText().toString().length()<1||et3.getText().toString().length()>20){Toast.makeText(contexts,"请输入1~20位字符数字组合!",Toast.LENGTH_SHORT).show();}else{UpdatePicName(list2.get(position).getUrl(),et3.getText().toString());RefreshUI();}}}).setNegativeButton("取消", null).show();UpdatePicName(list2.get(position).getUrl(),list2.get(position).getName());}});//设置长按事件holder.item_img.setOnLongClickListener(new bigClickListener(){@Overridepublic boolean onLongClick(View view) {//删除Toast.makeText(contexts,"正在准备删除",Toast.LENGTH_SHORT).show();AlertDialog alertDialog = new AlertDialog.Builder(contexts).setTitle("删除").setMessage("是否删除"+list2.get(position).getName()).setPositiveButton("确定", new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {//删除数据库记录DeletePic(list2.get(position).getUrl());//删除本地文件deleteSingleFile(list2.get(position).getUrl());//发送要删除的位置的图片给服务器sendMessageHandler("delete"+position);RefreshUI();}}).setNegativeButton("取消",new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {return;}}).create(); // 创建对话框alertDialog.show(); // 显示对话框return true;}});holder.item_img.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//绝对路径转换为Bitmap类型图片try {//读取指定picName的图片转换为Bitmap类位图FileInputStream localStream = null;try {localStream = openFileInput(list2.get(position).getName());} catch (FileNotFoundException e) {e.printStackTrace();Toast.makeText(contexts,"没有在app安装目录data/data/找到指定名称的图片!",Toast.LENGTH_SHORT).show();}Bitmap bitmap = BitmapFactory.decodeStream(localStream);ziv.setImage(bitmap);zrl.setVisibility(View.VISIBLE);}catch (NullPointerException npe){npe.printStackTrace();Toast.makeText(contexts,"要放大的图片不存在!空指针异常,请0. 检查当前安卓版本是否支持file路径读取!",Toast.LENGTH_SHORT).show();}Toast.makeText(contexts,"您点击了第"+(position+1)+"个imageView",Toast.LENGTH_SHORT).show();}});view.setTag(holder);}else{holder=(Holder) view.getTag();}//读取指定picName的图片转换为Bitmap类位图FileInputStream localStream = null;try {localStream = openFileInput(list2.get(position).getName());} catch (FileNotFoundException e) {e.printStackTrace();Toast.makeText(contexts,"没有在app安装目录data/data/找到指定名字的图片!",Toast.LENGTH_SHORT).show();}Bitmap bitmap = BitmapFactory.decodeStream(localStream);holder.item_img.setImageBitmap(bitmap);holder.item_tex.setText(list2.get(position).getName());Log.i("uuuu","url="+list2.get(position).getUrl()+"\n"+"name="+list2.get(position).getName()+"\n"+"list长度:"+list2.size());return view;}@Overridepublic Object getItem(int position) {return position;}@Overridepublic long getItemId(int position) {return position;}@Overridepublic int getCount() {//根据图片对象数量设置对应item数量return list.size();}private class bigClickListener implements View.OnLongClickListener {@Overridepublic boolean onLongClick(View view) {return true;}}}//改写物理按键——返回的逻辑,先退出全屏@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {// TODO Auto-generated method stubif (keyCode == KeyEvent.KEYCODE_BACK) {//退出全屏if(zrl.getVisibility()==View.VISIBLE||choice.getVisibility()==View.VISIBLE) {zrl.setVisibility(View.GONE);choice.setVisibility(View.GONE);return true;}else {this.finish();System.exit(0);}}return super.onKeyDown(keyCode, event);}//删除指定的本地文件public boolean deleteSingleFile(String filePath$Name) {File file = new File(filePath$Name);// 如果文件路径所对应的文件存在,并且是一个文件,则直接删除if (file.exists() && file.isFile()) {if (file.delete()) {Log.e("--Method--", "Copy_Delete.deleteSingleFile: 删除单个文件" + filePath$Name + "成功!");RefreshUI();return true;} else {Toast.makeText(getApplicationContext(), "删除单个文件" + filePath$Name + "失败!", Toast.LENGTH_SHORT).show();return false;}} else {Toast.makeText(getApplicationContext(), "删除单个文件失败:" + filePath$Name + "不存在!", Toast.LENGTH_SHORT).show();return false;}}//保存图片的方法,把Bitmap图片放在根目录指定path的文件夹//安卓7.0以上需要注意Environment.getExternalStorageDirectory()不可以显示真实地址public void saveImage(Bitmap bm,String picname){try {FileOutputStream out=openFileOutput(picName,MODE_APPEND);//追加存储在data/data/目录下bm.compress(Bitmap.CompressFormat.JPEG, 100, out);out.flush();out.close();Toast.makeText(this,"保存路径为app安装路径的data/data/目录下:"+"\n"+"图片名称为:"+picname,Toast.LENGTH_LONG).show();}catch (IOException ioe){ioe.printStackTrace();}}//插入数据到SQLite数据库public void InsertPic(String url,String name){MySQLiteHelper helper= DBManager.getIntance(this);SQLiteDatabase db1=helper.getWritableDatabase();//方法一,调用api,可以看到结果ContentValues values=new ContentValues();values.put(Constant.URL,url);values.put(Constant.NAME,name);long result=db1.insert(Constant.TABLE_NAME,null,values);if (result>0){Log.i("insert", "成功插入到SQLite");}else {Log.i("insert", "插入SQLite失败");}db1.close();Log.i("insert", "成功保存到SQLite");}@Overrideprotected void onActivityResult(int requestCode, int resultCode,Intent data) {super.onActivityResult(requestCode, resultCode, data);//一搬的情况,取消是0, 拍照成功是-1if(resultCode==RESULT_OK) {//其他操作switch (requestCode) {case 101://调用相机,返回后的相关操作//获取图片路径Bitmap takephotos = (Bitmap) data.getExtras().get("data");//更新相片对应的Bitmap信息get_bitmap=takephotos;//显示预览图choicepic.setImageBitmap(takephotos);choice.setVisibility(View.VISIBLE);Log.i("cccc", "本地图片选择完成"  +takephotos);FLAG=1;break;case 102://选择完图片后,执行的操作Uri uri2= data.getData();//获取图片路径String  img_url=uri2.getPath();//获取BitMap对象ContentResolver cr = this.getContentResolver();try {Bitmap local_pic = BitmapFactory.decodeStream(cr.openInputStream(uri2));//更新图片信息get_bitmap=local_pic;//显示选择的图片的小图choicepic.setImageBitmap(local_pic);choice.setVisibility(View.VISIBLE);Log.i("bbbbb", "本地图片选择完成,img_url=" +img_url);} catch (FileNotFoundException e) {Log.e("Exception", e.getMessage(),e);}break;}}}//判断是否获取到了图片,如果有,则显示预览,执行保存图片等方法public void checkPic(){//将得到的bitmap对象存入PIC_URL对应的目录,并添加到SQLite数据库if(FLAG==0){Toast.makeText(this,"老哥,你丫还没搞到图片呢,玩个个鸡儿?",Toast.LENGTH_SHORT).show();}else {choice.setVisibility(View.GONE);picName = "" + System.currentTimeMillis() + ".jpg";saveImage(get_bitmap,picName);//分段发送图片FLAG=0;//开始保存信息到Sqlite数据库,目前url没啥用。有效数据是picNameInsertPic(Constant.PATH+picName,picName);RefreshUI();//发送新增的图片到服务端new Thread(new Runnable() {@Overridepublic void run() {sendByteCut(splitPacketFor20Byte(BitmapToByte(get_bitmap)));}}).start();}}//蓝牙功能块//扫描周围蓝牙private void scanDevice() {// TODO Auto-generated method stubif (mBluetoothAdapter.isDiscovering()) {  //如果正在处于扫描过程...mBluetoothAdapter.cancelDiscovery();  //取消扫描...} else {chatMsg.clear();// 每次扫描前都先判断一下是否存在已经配对过的设备Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();if (pairedDevices.size() > 0) {String s="已保存的蓝牙";for (BluetoothDevice devices : pairedDevices) {chatMsg.add( devices.getAddress());s=s+devices.getAddress();}Toast.makeText(getApplicationContext(),"扫描到的已配对蓝牙设备名(MAC地址已保存):"+s,Toast.LENGTH_SHORT).show();} else {Toast.makeText(getApplicationContext(),"未扫描到配对过的蓝牙设备",Toast.LENGTH_SHORT).show();}/* 开始搜索 */mBluetoothAdapter.startDiscovery();}}//逐个尝试,与周围已配对蓝牙设备进行连接,直到连上服务器public void connectBTSuround(){if(chatMsg!=null){// 停止扫描// BluetoothAdapter.startDiscovery()很耗资源,在尝试配对前必须中止它mBluetoothAdapter.cancelDiscovery();for(int i=0;i<chatMsg.size();i++){//判断是否客户端已经连接上服务端,未连接上才继续连接if(!connectState) {//获取MAC地址...其实就是硬件地址...BluetoothMsg.BlueToothAddress = chatMsg.get(i);// 通过Mac地址去尝试连接一个设备device = mBluetoothAdapter.getRemoteDevice(BluetoothMsg.BlueToothAddress);BluetoothMsg.isOpen = true;}}//若线程已开启或者处于阻塞状态,则关闭,重新开启一个新的线程mClientThread = new ClientThread(this);  //开启新的线程...mClientThread.start();}}//检测蓝牙状态和服务线程状态public void checkBTServer(){if (BluetoothMsg.isOpen) {Toast.makeText(this, "服务器端口已打开,可以通信", Toast.LENGTH_SHORT).show();}if (mBluetoothAdapter != null) {Toast.makeText(this,"机器带有蓝牙" ,Toast.LENGTH_SHORT).show();if (!mBluetoothAdapter.isEnabled()) {// 发送打开蓝牙的意图Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableIntent, RESULT_FIRST_USER);// 设置蓝牙的可见性,最大值3600秒,默认120秒,0表示永远可见(作为客户端,可见性可以不设置,服务端必须要设置)Intent displayIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);displayIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 0);startActivity(displayIntent);Toast.makeText(this, "蓝牙已打开", Toast.LENGTH_SHORT).show();// 直接打开mBluetoothAdapter.enable();}}}//打开数据接收端口public void openReadThread(){//启动接受数据的线程mReadThread = new ReadThread(this);mReadThread.start();Log.i("serverthread","数据接收端口打开成功");}//接收数据private class ReadThread extends Thread {Context readContext;public ReadThread(Context context){readContext=context;}public void run() {//存储位置byte[] buffer;//收到的所有数据段的长度之和int allLength=0;//数据数量int bytes;InputStream mmInStream = null;try {mmInStream = socket2.getInputStream();} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}Looper.prepare();//Looper初始化boolean k=true;while (k) {try {// Read from the InputStream// 读取客户端发送的一个byte[]信息到buffer并写入内存,如果客户端没有发送byte[](bytes=0)将被阻塞int count=0;while (count == 0) {//acaliable()获取下一次读取流的长度if(mmInStream.available()<20&&mmInStream.available()>0){count=20;}else {count = mmInStream.available();}}buffer=new byte[count];if ((bytes = mmInStream.read(buffer)) > 0) {byte[] buf_data = new byte[bytes];for (int i = 0; i < bytes; i++) {buf_data[i] = buffer[i];}//打印接收到的数据System.out.println("---GET到的buf__data---" + buf_data);System.out.println("---GET到的数据长度---" + buf_data.length);//buffer_data是获取到的一个byte[]//判断接收到的数据是不是终结符,是的话,就说明queue已经接收到一个完整图片的所有byte[],否则继续存入queueString s = "no";s = new String(buf_data);Log.i("ssssss", "s的值为" + s);if (s.endsWith("ok")) {//执行byte[]拼接方法,并且将拼接得到的数据保存为本地指定路径图片System.out.println("----保存图片保存图片保存图片保存图片----");String jj = "s" + System.currentTimeMillis() + ".jpg";saveToImgFile(qb, allLength, Contant.PATH, jj);System.out.println("----保存完毕,清空QUEUE----");
//                            k=false;qb.clear();allLength = 0;//更新界面RefreshUI();Thread.sleep(200);if(s.contains("end")){// 在这里执行你要想的操作 比如直接在这里更新ui或者调用回调在 在回调中更新ui//                                Message message=new Message();
//                                message.obj=100;
//                                getHandler().sendMessage(message);}}else {//未读取到终止符,则将byte[]继续存入queueif(s.endsWith(".jpg")){System.out.println("----成功接收到图片名,开始准备接收以下图片数据:----" + s);qb.clear();allLength=0;}else {qb.offer(buf_data);allLength=allLength+bytes;System.out.println("----存入的buf_data的长度----" + bytes);System.out.println("----存入数据成功,当前qb.size()----" + qb.size());}}} else {try {mmInStream.close();} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}}} catch(IOException e){e.printStackTrace();break;} catch (InterruptedException e) {e.printStackTrace();}}}}//第十六部分...存储byte[]为图片文件/*** 存储图片16进制串为图片文件* @param queue  保存了很多图片的byte[]数据* @param length 所有byte[]的数据加起来的长度* @param path 要保存的图像文件路径* @param imgName 图片名*/public void saveToImgFile( Queue<byte []>queue,int length,String path,String imgName){int index=0,k=0;byte[] b=new byte[length];while(length>index){k = k + 1;
//              byte[] m=queue.poll();if(queue.peek()!=null){int ls=queue.peek().length;System.arraycopy(queue.poll(), 0, b, index, ls);index = index + ls;System.out.println("----LENGTH的值(传递来的队列b的容量)为----"+length );System.out.println("----当前拼接次数为:----" + k);System.out.println("----当前拼接对象的大小为:----" + ls);System.out.println("----下一次的起始位置INDEX为:----" + index);}}System.out.println("拼接完成,开始保存!");if (b == null || b.length == 0) {return;}//方法一//byte[]转BitmappicName=imgName;Bitmap bitmap = BitmapFactory.decodeByteArray(b, 0, b.length);// Bitmap保存到/data目录下saveImage(bitmap,picName);//同步保存图片信息至SQLiteInsertPic(Contant.PATH+picName,picName);Log.i("savaToImgFile","保存成功"+picName);}//连接服务
// 开启客户端连接服务端,一个新的线程...private class ClientThread extends Thread {Context clientContext;public ClientThread(Context context){clientContext=context;}@Overridepublic void run() {// TODO Auto-generated method stubif (device != null) {try {/* 下面这步也是关键,我们如果想要连接服务器,我们需要调用方法createRfcommSocketToServiceRecord* 参数00001101-0000-1000-8000-00805F9B34FB表示的是默认的蓝牙串口...通过传递参数调用方法,* 会返回给我们一个套接字..这一步就是获取套接字,实现连接的过程...** */socket2 = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));// 连接Log.i("clientthread","请稍后,正在连接服务器...");// 通过socket连接服务器,正式形成连接...这是一个阻塞过程,直到连接建立或者连接失效...try {socket2.connect();}catch (IOException ioe){ioe.printStackTrace();System.out.println("IOException,未重启服务端,Socket连接失败!");}//如果实现了连接,那么服务端和客户端就共享一个RFFCOMM信道...Log.i("clientthread","恭喜,已连接上服务器!!");// 如果服务器连接成功了...这步就会执行...否则走catch(IOException e)//标记已经成功连接connectState=true;// 可以开启其它线程,或执行其他操作openReadThread();} catch (IOException e) {// TODO Auto-generated catch block// socket.connect()连接失效e.printStackTrace();connectState=false;Log.i("clientthread","连接异常,请断开后再尝试重新连接...");}}}}//发送数据线程//这一步表示的是发送数据的过程...private void sendMessageHandler(String msg) {if (socket2 == null) {Toast.makeText(context, "没有可用的连接", Toast.LENGTH_SHORT).show();return;}System.out.println("post");//如果连接上了,那么获取输出流...try {OutputStream os = socket2.getOutputStream();os.write(msg.getBytes());//获取所有的字节然后往外发送...} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}//应用就绪时开始扫描蓝牙设备,获取周围蓝牙设备名称和MAC地址保存起来@Overrideprotected void onResume() {super.onResume();//检测设备是否具备蓝牙功能,如果有则打开蓝牙checkBTServer();//扫描周边设备,获取他们的信息(主要是已配对设备的MAC地址)scanDevice();//启动服务creatBtn.setVisibility(View.GONE);connectBtn.setVisibility(View.GONE);}//应用销毁时释放资源@Overrideprotected void onDestroy() {// TODO Auto-generated method stubsuper.onDestroy();if (mBluetoothAdapter != null) {mBluetoothAdapter.cancelDiscovery();// 关闭蓝牙mBluetoothAdapter.disable();BluetoothMsg.isOpen = false;}closeClient();}// 停止服务,释放资源private void closeClient() {new Thread() {public void run() {if (mClientThread != null) {mClientThread.interrupt();mClientThread = null;}if (mReadThread != null) {mReadThread.interrupt();mReadThread = null;}try {if (socket2!= null) {socket2.close();socket2 = null;}} catch (IOException e) {// TODO: handle exception}}}.start();}//刷新UIpublic void RefreshUI(){//刷新UI界面new Thread(new Runnable() {@Overridepublic void run() {Message msg=Message.obtain();msg.what=100;getHandler().sendMessage(msg);Log.i("deletelog","已发送消息给Handler:"+msg);}}).start();}//第十一部分...BitMap转换为byte[]            <!---然后再进一步转换为16进制字符串-->public byte[] BitmapToByte(Bitmap bitMap){ByteArrayOutputStream stream = new ByteArrayOutputStream();Log.i("stream","----stream="+stream);bitMap.compress(Bitmap.CompressFormat.JPEG, 100, stream);// (0 - 100)压缩文件byte[] bt = stream.toByteArray();System.out.println("图片转换得到的byte[]长度为:"+bt.length);return bt;}//第十二部分...将byte[]分段存储到queue中public Queue<byte[]> splitPacketFor20Byte(byte[] data) {Queue<byte[]> dataInfoQueue = new LinkedList<>();if (data != null) {int index = 0;do {byte[] surplusData=new byte[data.length-index];byte[] currentData=null;System.arraycopy(data, index, surplusData, 0, data.length-index);if (data.length-index <= 20) {currentData = new byte[data.length-index];System.arraycopy(surplusData, 0, currentData, 0, data.length-index);index =data .length;} else {currentData = new byte[20];System.arraycopy(data, index, currentData, 0, 20);index =index+20;}dataInfoQueue.offer(currentData);} while (index < data.length);}Log.i("picdatalength","图片转为数组后的数组长度"+data.length);System.out.println("第一次,byte数组分段存入queue后,queue的长度:"+dataInfoQueue.size());return dataInfoQueue;}//第十三部分...分段发送byte[](每次发送queue中的一个,发完即删),全部发送完后,最后面跟着发送一个指定长度的终止信号public void sendByteCut(Queue<byte[]> qb){int qSize=qb.size();byte[] b;String sign = "ok";for (int i=0;i<qSize+1 ;i++) {if (socket2== null) {Toast.makeText(getApplicationContext(), "没有可用的连接", Toast.LENGTH_SHORT).show();return;}System.out.println("-----一次发送的开始-------"+(qSize%20));//如果连接上了,那么获取输出流...try {OutputStream os = socket2.getOutputStream();//判断图片的所有数据是否传输完if (i==qSize) {//添加终止信号isendb = sign.getBytes();System.out.println("---图片数据队列的长度QUEUEsize(不算终止符)---"+qSize);System.out.println("----b不为空,终止符获取打包成功,即将传送,当前数据段段数:----"+(i+1));} else {//poll()获取队头元素并移除int blength=qb.peek().length;b=new byte[blength];System.arraycopy(qb.poll(),0,b,0,blength);System.out.println("----拷贝成功,即将发送的数据段长度为:----"+blength);}System.out.println("---SEND---即将发送,当前数据段b:"+(i+1)+"---b:"+b);if (b==null){System.out.println("--- b=NULL ---:");}else {os.write(b);//获取所有的字节然后往外发送...System.out.println("----b不为空,发送成功,已发送数据段段数:----"+(i+1));}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}try {//每发送一段就休息10ms,等待接收端单片机处理完刚接收的数据。Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><LinearLayout
        android:id="@+id/bottom_rl"android:layout_width="match_parent"android:layout_height="60dp"app:layout_constraintBottom_toBottomOf="parent"android:orientation="horizontal"android:background="@color/colorWhite"><RelativeLayout
            android:layout_width="match_parent"android:layout_height="match_parent"android:layout_weight="1"><Button
            android:id="@+id/test_connection_btn"android:layout_width="match_parent"android:layout_height="match_parent"android:text="查询"android:background="@drawable/pressed_unpressed_backgroud"/><Button
                android:visibility="gone"android:id="@+id/refresh_btn"android:layout_width="match_parent"android:layout_height="match_parent"android:text="刷新"android:background="@drawable/pressed_unpressed_backgroud"/></RelativeLayout><Button
            android:id="@+id/creat_server"android:layout_width="match_parent"android:layout_height="match_parent"android:text="创建服务"android:background="@drawable/pressed_unpressed_backgroud"android:layout_weight="1"/><Button
            android:id="@+id/connection_server"android:layout_width="match_parent"android:layout_height="match_parent"android:text="连接服务"android:background="@drawable/pressed_unpressed_backgroud"android:layout_weight="1 "/></LinearLayout><GridView
        android:id="@+id/pic_gridview"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingTop="20dp"android:gravity="center"android:numColumns="4"android:horizontalSpacing="30dp"android:verticalSpacing="30dp"></GridView><RelativeLayout
        android:id="@+id/zoomimageview_rl"android:layout_width="match_parent"android:layout_height="match_parent"><com.kaytion.hgl.bluetoopicmanager.view.ZoomImageView
            android:id="@+id/zoomimageview_iv"android:layout_width="match_parent"android:layout_height="match_parent"></com.kaytion.hgl.bluetoopicmanager.view.ZoomImageView><RelativeLayout
            android:layout_alignParentTop="true"android:layout_width="match_parent"android:layout_height="50dp"><ImageView
                android:id="@+id/hiden_iv"android:layout_alignParentRight="true"android:layout_alignParentEnd="true"android:layout_width="50dp"android:layout_height="50dp"android:src="@drawable/closescale"/></RelativeLayout></RelativeLayout><RelativeLayout
        android:id="@+id/choice_rl"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/colorWhite"><ImageView
        android:id="@+id/choice_iv"android:layout_centerInParent="true"android:layout_width="200dp"android:layout_height="200dp" /><Button
            android:id="@+id/queren_btn"android:layout_width="80dp"android:layout_height="40dp"android:text="确认"android:background="@drawable/pressed_unpressed_backgroud"android:layout_centerInParent="true"android:layout_below="@+id/choice_iv"android:layout_marginTop="20dp"/></RelativeLayout></android.support.constraint.ConstraintLayout>

Androidmainfest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.kaytion.hgl.bluetoopicmanager"><!--sd卡、文件系统读写权限--><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><uses-feature android:name="android.hardware.camera" /><!--蓝牙权限--><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><application
        android:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>
其余的文件和配置都在下面链接项目里去找(只有一个类,用上面的服务器端和客户端代码分别替换,就可以了。布局和资源部分不用修改)

https://download.csdn.net/download/hgl1263538576/10601195

安卓蓝牙4.0通信之Socket图片传输相关推荐

  1. 基于intel芯片的安卓蓝牙4.0 BLE通信总结

    基于intel芯片的安卓蓝牙4.0 BLE通信问题总结 使用设备: 台电 x98 air 3G 系统:安卓4.4.4 系统搭建: 1.安装ADT驱动,可以搜索intel_mobile_usb_driv ...

  2. iOS蓝牙BLE4.0通信功能

    概述 iOS蓝牙BLE4.0通信功能,最近刚学的苹果,为了实现蓝牙门锁的项目,找了一天学习了下蓝牙的原理,亲手测试了一次蓝牙的通信功能,结果成功了,那么就把我学习的东西分享一下. 详细 代码下载:ht ...

  3. Android开发:IBeacon系列——安卓蓝牙4.0(BLE)开发之检测IBeacon热点初步

    检测ibeacon热点信号 软硬件要求:Android4.3及以上中支持BLE技术,同时蓝牙需要满足Bluetooth4.0及以上. iBeacon的工作原理是基于Bluetooth Low Ener ...

  4. android 蓝牙速率测试软件,安卓蓝牙4.0开发测试 一个测试 APP - 下载 - 搜珍网

    压缩包 : MyBleTestDemo.rar 列表 MyBleTestDemo/.classpath MyBleTestDemo/.project MyBleTestDemo/.settings/o ...

  5. android蓝牙4.0 BLE低功耗应用

    转自    http://www.cnblogs.com/zdz8207/archive/2012/10/17/bluetooth_ble_android.html 谈谈几个月以来开发android蓝 ...

  6. 谈谈几个月以来开发android蓝牙4.0 BLE低功耗应用的感受

    谈谈几个月以来开发android蓝牙4.0 BLE低功耗应用的感受 谈谈几个月以来开发android蓝牙4.0 BLE低功耗应用的感受,注明下时间:2012-10-17写的博客,后期更新的也注明了时间 ...

  7. 开发android蓝牙4.0 BLE低功耗应用的感受

    文章转自: http://www.cnblogs.com/zdz8207/archive/2012/10/17/bluetooth_ble_android.html 谈谈几个月以来开发android蓝 ...

  8. C# Socket 简易的图片传输

    关于网络的数据传输我就是个小白,所以今天学习一下简易的Socket图片传输. 客户端和服务器的连接咱们上次已经学过了,咱们先从简易的文件传输入手.下面开始代码分析了. Server.cs using ...

  9. android 手环app(蓝牙4.0)

    android版本的一款 手环app,通过蓝牙4.0通信. 一个ec的工程,一个as的工程 此app是本人学习android时所写,肯定写的不好. 分享出来主要是让朋友学习蓝牙4.0通信的开发, as ...

最新文章

  1. 一口气说出 5 种 IO 模型,蒙圈了!
  2. docker 命令详解
  3. linux/usr/src/kernels 目录下没有内核源码 解决方法
  4. 通讯故障_伦茨lenze全数字直流调速器通讯故障维修经验很丰富
  5. 什么是 TypeScript 变量的 declared type
  6. SAP Fiori Launchpad pageSet请求的处理原理
  7. QMarkDowner编译
  8. 中文格式_常见中文编码格式
  9. css label 居中布局_用好这20个css技巧快速提升你的CSS技能
  10. go string 转 uint64_如何优雅的使用Go接口?
  11. Linux工作笔记033---Linux(CentOS7)安装zip、unzip命令
  12. python绘制风向玫瑰图和污染物玫瑰图
  13. [转]vue解决刷新页面vuex数据、params参数消失的问题
  14. Java、JSP药品库房管理系统
  15. wow.js动画插件
  16. zookeeper因内存不足造成的CPU占用率高
  17. 负载均衡之加权轮询算法
  18. 物联网发展方向(复制来的)
  19. 【再学Tensorflow2】TensorFlow2的建模流程:Titanic生存预测
  20. Expert 诊断优化系列------------------内存不够用么?

热门文章

  1. OpenCV矩阵运算之顶点法向量计算
  2. 路由器上的虚拟服务器安全吗,路由器可以当虚拟主机吗
  3. turtle库使用——画布大小调整,用不同颜色填充多边形
  4. 人工智能发展史:4张图看尽AI重大里程碑
  5. 1.QT------史上最详细的Hello World(详解)
  6. ios 简书 获取通讯录信息_iOS9以后获取手机通讯录
  7. 仿闲鱼验货宝链接源码
  8. Visio常见使用问题整理(不断更新中)
  9. Open-falcon技术系列文章——Open-Falcon特性梳理
  10. CF1373A Donut Shops