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

本文介绍了一个使用Android Bluetooth 4.0通过Socket进行图片传输的应用。项目涉及两台设备之间的蓝牙配对、服务端和客户端的Socket通信。服务端自动发送图片,客户端接收并显示,支持图片查看、删除和重命名。目前限制为300k以下图片的传输,源代码在链接中提供。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

安卓蓝牙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; // 服务端socket
    private 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")
    @Override
    protected 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(){
            @Override
            public 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();
        //初始化helper
        helper=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() {
            @Override
            public 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);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater=getMenuInflater();
        inflater.inflate(R.menu.actionbar_menu,menu);
        return super.onCreateOptionsMenu(menu);

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        showPopWindow();
        return super.onOptionsItemSelected(item);
    }

    @Override
    public 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,隐藏预览用的RelativeLayout
                checkPic();
                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);//获取焦点,就置于试图最顶层,盖住了activity
        TextView 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);
    }

    //显示popwindow
    private void showPopWindow() {
        fitPopupWindowOverStatusBar(true);
        View rootview = LayoutInflater.from(MainActivity.this).inflate(R.layout.activity_main,null);
        popupWindow.showAtLocation(rootview, Gravity.BOTTOM, 0, 0);

    }

    //隐藏popupwindow
    private 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();
    }

    //查询所有数据,插入list
    public 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;
        }

        @Override
        public 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() {
                    @Override
                    public void onClick(View view) {
                        final EditText et3=new EditText(contexts);
                        new AlertDialog.Builder(contexts).setTitle("请输入新的图片名")
                                .setView(et3).setPositiveButton("确定",new DialogInterface.OnClickListener() {
                            @Override
                            public 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(){
                    @Override
                    public 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() {
                    @Override
                    public 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_t
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值