一个给蓝牙模块升级的Android应用小工具

功能点不复杂,3天时间,完成一个蓝牙升级APP的工具。


4个界面:

  1. 闪屏页

  2. 主界面

  3. 蓝牙搜索界面

  4. 文件夹选择界面;


功能点:

1、闪屏页申请权限,其中包括蓝牙权限。

需要关注Android13的支持。


2、主界面操作升级功能;

显示进度,并反馈升级结果,升级日志。


3、蓝牙搜索界面搜索蓝牙设备,并进行选择。

4、文件夹选择界面选择指定的文件夹。



实现效果:

呱牛笔记


呱牛笔记


呱牛笔记






关键代码。

0、主界面代码:

package com.example.sifliotademo;

import static com.sifli.siflidfu.Protocol.DFU_SERVICE_EXIT;
import static com.sifli.siflidfu.Protocol.IMAGE_ID_CTRL;
import static com.sifli.siflidfu.Protocol.IMAGE_ID_DYN;
import static com.sifli.siflidfu.Protocol.IMAGE_ID_EX;
import static com.sifli.siflidfu.Protocol.IMAGE_ID_FONT;
import static com.sifli.siflidfu.Protocol.IMAGE_ID_HCPU;
import static com.sifli.siflidfu.Protocol.IMAGE_ID_LCPU;
import static com.sifli.siflidfu.Protocol.IMAGE_ID_MUSIC;
import static com.sifli.siflidfu.Protocol.IMAGE_ID_NAND_LCPU_PATCH;
import static com.sifli.siflidfu.Protocol.IMAGE_ID_NAND_RES;
import static com.sifli.siflidfu.Protocol.IMAGE_ID_NOR_LCPU_PATCH;
import static com.sifli.siflidfu.Protocol.IMAGE_ID_RES;
import static com.sifli.siflidfu.SifliDFUService.BROADCAST_DFU_LOG;
import static com.sifli.siflidfu.SifliDFUService.BROADCAST_DFU_PROGRESS;
import static com.sifli.siflidfu.SifliDFUService.BROADCAST_DFU_STATE;
import static com.sifli.siflidfu.SifliDFUService.EXTRA_DFU_PROGRESS;
import static com.sifli.siflidfu.SifliDFUService.EXTRA_DFU_PROGRESS_TYPE;
import static com.sifli.siflidfu.SifliDFUService.EXTRA_DFU_STATE;
import static com.sifli.siflidfu.SifliDFUService.EXTRA_DFU_STATE_RESULT;
import static com.sifli.siflidfu.SifliDFUService.EXTRA_LOG_MESSAGE;
import static com.sifli.siflidfu.SifliDFUService.startActionDFUNand;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

import com.afei.filepicker.FilePickerActivity;
import com.qmuiteam.qmui.widget.QMUIProgressBar;
import com.qmuiteam.qmui.widget.QMUITopBarLayout;
import com.qmuiteam.qmui.widget.dialog.QMUIDialog;
import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction;
import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton;
import com.sifli.siflidfu.*;

import top.keepempty.BaseActivity;
import top.keepempty.R;

public class MainActivity extends BaseActivity implements View.OnClickListener {
    private static  final String TAG = "MainActivity";
    private QMUITopBarLayout mTopBar;

    private BroadcastReceiver localBroadcastReceiver;
    private EditText macEt;
    private TextView binPath;
    private QMUIRoundButton chooseBluetooth;
    private QMUIRoundButton otaNorV2Tv;

    private QMUIRoundButton otaNorV2StopTv;
    private  QMUIRoundButton otaNorV2ResumeTv;
    private QMUIRoundButton searchBtn;
    private QMUIRoundButton setDirBtn;

    private TextView history_list;


    private QMUIProgressBar progressBar;

    public  String TEMP_FILE_PATH;


    private  String norV1CtrlFile;
    private  String norV1HcpuFile;
    private  String norV1LcpuFile;
    private  String norV1LcpuPatchFile;
    private  String norV1ResFile;
    private  String norV1FontFile;
    private  String norV1EXFile;


    private  AssetCopyer assetCopyer;
    private  boolean copyFileSuccess;

    private final int REQUEST_SEARCH_DEVICE = 4;

    private static final int REQUEST_PERMISSION = 1000;
    private static final int REQUEST_FILE_PICKER = 1001;

    private final String[] PERMISSIONS = new String[] {
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ota_main);
        this.setupView();
        init();
//        copyOtaFiles();
        //通过按钮选择文件

        localBroadcastReceiver = new LocalBroadcastReceiver();
        registerDfuLocalBroadcast();
    }

    private void setupView(){
        this.macEt = findViewById(R.id.sf_main_ota_mac_et);
        //this.macEt.setText("AB:89:67:45:23:01");
        this.binPath = findViewById(R.id.bin_path);
//        this.macEt.setText("11:22:33:44:56:DF");

        this.otaNorV2Tv = findViewById(R.id.sf_main_ota_nor_v2_tv);
        this.otaNorV2StopTv = findViewById(R.id.sf_main_ota_nor_v2_stop_tv);
        this.otaNorV2ResumeTv = findViewById(R.id.sf_main_ota_nor_v2_resume_tv);
        this.history_list = findViewById(R.id.history_list);
        this.searchBtn = findViewById(R.id.search_blue_device);
        this.setDirBtn = findViewById(R.id.choose_dir);

        otaNorV2StopTv.setEnabled(false);

        this.progressBar = findViewById(R.id.sf_main_ota_progress_pb);
        progressBar.setQMUIProgressBarTextGenerator(new QMUIProgressBar.QMUIProgressBarTextGenerator() {
            @Override
            public String generateText(QMUIProgressBar progressBar, int value, int maxValue) {
                return value + "/" + maxValue;
            }
        });

        this.otaNorV2Tv.setOnClickListener(this);
        this.otaNorV2StopTv.setOnClickListener(this);
        this.otaNorV2ResumeTv.setOnClickListener(this);
        this.searchBtn.setOnClickListener(this);
        this.setDirBtn.setOnClickListener(this);

    }

    private void init(){
        mTopBar = findViewById(R.id.topbar);
        mTopBar.setTitle(getString(R.string.app_name));

        String app_file = this.getExternalFilesDir(null)+"";
        File file = new File(app_file);
        assetCopyer = new AssetCopyer(this,file);

        TEMP_FILE_PATH = getSystemRootDir()+ "/norv1";
        initOtaFiles();
    }

    public static  String getSystemRootDir(){
        if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            File sdDir = Environment.getExternalStorageDirectory();//获取跟目录
            Log.e(TAG, "getCacheDir 得到的根目录路径:" + sdDir);
            return Environment.getExternalStorageDirectory().toString()+"/";
        }

        File directory_doc = Environment.getExternalStoragePublicDirectory(Environment. DIRECTORY_DOCUMENTS);
        //使用这个方法需要传入公共目录的类型如Environment.DIRECTORY_DOCUMENTS
        //查看公共目录文档文件的路径
        Log.e(TAG,"getCacheDir 得到的公共目录:"+directory_doc);

        return directory_doc.toString()+"/";
    }

    private void initOtaFiles(){
        norV1CtrlFile = TEMP_FILE_PATH + "/1.bin";
        norV1HcpuFile = TEMP_FILE_PATH + "/2.bin";
        norV1LcpuFile = TEMP_FILE_PATH + "/3.bin";
        norV1LcpuPatchFile = TEMP_FILE_PATH + "/4.bin";
        norV1ResFile = TEMP_FILE_PATH + "/5.bin";
        norV1FontFile = TEMP_FILE_PATH + "/6.bin";
        norV1EXFile = TEMP_FILE_PATH + "/7.bin";
    }

    @Override
    public void onClick(View v) {
        long viewId = v.getId();
        if(viewId == R.id.sf_main_ota_nor_v2_tv){
            this.otaNorV2(false);
        }else if(viewId == R.id.sf_main_ota_nor_v2_stop_tv){
            int mCurrentDialogStyle = com.qmuiteam.qmui.R.style.QMUI_Dialog;
            new QMUIDialog.MessageDialogBuilder(MainActivity.this)
                    .setTitle("确认取消升级")
                    .addAction("取消", new QMUIDialogAction.ActionListener() {
                        @Override
                        public void onClick(QMUIDialog dialog, int index) {
                            //lastCommand = 0;
                            dialog.dismiss();
                        }
                    })
                    .addAction("确定", new QMUIDialogAction.ActionListener() {
                        @Override
                        public void onClick(QMUIDialog dialog, int index) {
                            stop();
                        }
                    })
                    .create(mCurrentDialogStyle).show();
        }else if(viewId == R.id.sf_main_ota_nor_v2_resume_tv){
            this.otaNorV2(true);
        }else if (viewId == R.id.search_blue_device){
            Intent intent = new Intent(this, SearchBluetoothAcitivty.class);
//            startActivity(intent);
            startActivityForResult(intent, REQUEST_SEARCH_DEVICE);
        }else if (viewId == R.id.choose_dir){
            //选择文件夹
            if (checkPermission()){
                startActivityForResult(new Intent(MainActivity.this, FilePickerActivity.class), REQUEST_FILE_PICKER);
            }
        }
    }

    private boolean checkPermission() {
        for (int i = 0; i < PERMISSIONS.length; i++) {
            int state = ContextCompat.checkSelfPermission(this, PERMISSIONS[i]);
            if (state != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, PERMISSIONS, REQUEST_PERMISSION);
                return false;
            }
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        if (requestCode == REQUEST_PERMISSION) {
            if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Intent intent = new Intent();
                intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                Uri uri = Uri.fromParts("package", getPackageName(), null);
                intent.setData(uri);
                startActivityForResult(intent, REQUEST_PERMISSION);
            }
        }
    }

    private  void stop(){
        Intent intent = new Intent(this, SifliDFUService.class);
        this.stopService(intent);
    }

    private  void otaNorV2(boolean resume){
        String bluetoothAddress = macEt.getText().toString();
        ArrayList<DFUImagePath> imagePaths = getNorV1ImagePaths();
        if(imagePaths.size() == 0){
            Log.e(TAG,"otaNorV2 ctrl image file not exist");
            showError("请选择正确的升级包路径");
            return;
        }

        MainActivity.this.showLoading();
        if(resume){
            SifliDFUService.startActionDFUNorExt(this,bluetoothAddress,imagePaths,1,0);
        }else{
            SifliDFUService.startActionDFUNorExt(this,bluetoothAddress,imagePaths,0,0);
        }

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");// HH:mm:ss
//获取当前时间
        Date date = new Date(System.currentTimeMillis());
        //time1.setText("Date获取当前日期时间"+simpleDateFormat.format(date));
        receiveTxt.append(simpleDateFormat.format(date));
        receiveTxt.append(" 开始升级");
        receiveTxt.append("\n");
        history_list.setText(receiveTxt);
        otaNorV2Tv.setEnabled(false);
        otaNorV2StopTv.setEnabled(true);
    }

    private  void addOneDFUImagePath(ArrayList<DFUImagePath> paths, String file_path, int img_id){

        File novv1CtrolFile0 = new File(file_path);
        Uri norv1CtrlFileUri = Uri.fromFile(novv1CtrolFile0);
        Boolean isExist = this.isFileExists(norv1CtrlFileUri);
        Log.i(TAG,"norv1 Exist =" + isExist + ",path=" + norv1CtrlFileUri.getPath() + ",scheme=" + norv1CtrlFileUri.getScheme() + ",img_id:"+img_id);
        DFUImagePath ctrlPath = new DFUImagePath(null, norv1CtrlFileUri, img_id);
        if(isExist){
            paths.add(ctrlPath);
        }
    }

    private ArrayList<DFUImagePath> getNorV1ImagePaths() {
        ArrayList<DFUImagePath> paths = new ArrayList<>();

        addOneDFUImagePath(paths, norV1CtrlFile, IMAGE_ID_CTRL);

        addOneDFUImagePath(paths, norV1HcpuFile, IMAGE_ID_HCPU);
        addOneDFUImagePath(paths, norV1LcpuFile, IMAGE_ID_LCPU);
        addOneDFUImagePath(paths, norV1LcpuPatchFile, IMAGE_ID_NOR_LCPU_PATCH);
        addOneDFUImagePath(paths, norV1ResFile, IMAGE_ID_RES);
        addOneDFUImagePath(paths, norV1FontFile, IMAGE_ID_FONT);
        addOneDFUImagePath(paths, norV1EXFile, IMAGE_ID_EX);

        return  paths;
    }

    public boolean isFileExists(Uri uri) {
        File file = new File(uri.getPath());
        return file.exists();
    }

    private void updateProgress(int pro) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                progressBar.setProgress(pro);
            }
        });
    }

    private StringBuffer receiveTxt = new StringBuffer();
    class LocalBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            switch (action) {
                case BROADCAST_DFU_PROGRESS:
                    MainActivity.this.hideLoading();
                    int progress = intent.getIntExtra(EXTRA_DFU_PROGRESS, 0);
                    int type = intent.getIntExtra(EXTRA_DFU_PROGRESS_TYPE, 0);
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            progressBar.setProgress(progress);
//                            progressTv.setText("pro" + progress);
                        }
                    });
                    //Log.i(TAG, "dfu progress " + progress + ", type:"+type);
                    break;
                case BROADCAST_DFU_LOG:
                    String DFULog = intent.getStringExtra(EXTRA_LOG_MESSAGE);

                    Log.d(TAG, "DFU LOG - " + DFULog);
                    updateLogText(DFULog);
                    break;
                case BROADCAST_DFU_STATE:
                    int dfuState = intent.getIntExtra(EXTRA_DFU_STATE, 0);
                    int dfuStateResult = intent.getIntExtra(EXTRA_DFU_STATE_RESULT, 0);
                    try{
                        if (DFU_SERVICE_EXIT == dfuState) {
                            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");// HH:mm:ss
//获取当前时间
                            Date date = new Date(System.currentTimeMillis());
                            //time1.setText("Date获取当前日期时间"+simpleDateFormat.format(date));
                            receiveTxt.append(simpleDateFormat.format(date));
                            receiveTxt.append(" ");

                            if (dfuStateResult == 0) {
                                hideLoading();
                                //加一个时间

                                receiveTxt.append(macEt.getText().toString());
                                receiveTxt.append("升级成功");
                                showError("升级成功");
                            }else{
                                hideLoading();
                                receiveTxt.append(macEt.getText().toString());
                                receiveTxt.append("升级失败");
                                showError("升级失败");
                            }
                            receiveTxt.append("\n");
                            history_list.setText(receiveTxt);
                            progressBar.setProgress(0);

                            otaNorV2Tv.setEnabled(true);
                            otaNorV2StopTv.setEnabled(false);
                        }
                    }catch (Exception ex){
                        Log.e(TAG, "err:"+ex.getMessage().toString());
                    }
                    break;
            }
        }
    }

    private void registerDfuLocalBroadcast() {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BROADCAST_DFU_LOG);
        intentFilter.addAction(BROADCAST_DFU_STATE);
        intentFilter.addAction(BROADCAST_DFU_PROGRESS);
        // more action

        registerLocalReceiver(localBroadcastReceiver, intentFilter);
    }

    public void registerLocalReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        LocalBroadcastManager.getInstance(getBaseContext()).registerReceiver(receiver, filter);
    }
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == REQUEST_PERMISSION && resultCode == RESULT_OK) {
            checkPermission();
        }
        if (requestCode == REQUEST_FILE_PICKER && resultCode == RESULT_OK && data != null) {
            //ArrayList<String> paths = data.getStringArrayListExtra(FilePickerActivity.INTENT_EXTRA_CHOOSE_PATHS);
            String paths = data.getStringExtra(FilePickerActivity.INTENT_EXTRA_CHOOSE_PATHS);
            if (paths != null || paths.length() > 0) {
                Log.v(TAG, "choose file:"+paths);

                TEMP_FILE_PATH = paths;//getSystemRootDir();
                initOtaFiles();
                binPath.setText(TEMP_FILE_PATH);

                return;
            }
        }
        // 根据上面发送过去的请求code来区别
        switch (requestCode) {
            case REQUEST_SEARCH_DEVICE:
                if(resultCode == RESULT_OK){
                    //更新mac
                    String mac = data.getStringExtra("mac");
                    String name = data.getStringExtra("name");
                    if (mac != null && mac.length() > 0) {
                        macEt.setText(mac);
                    }
                }
                break;
            default:
                break;
        }
    }
}


1、主界面xml布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout   xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:fcf="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_drawer_layout"
    android:background="@color/app_color_gray"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.qmuiteam.qmui.widget.QMUIWindowInsetLayout
        android:id="@+id/main_content_frame_parent"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                android:layout_marginTop="?attr/qmui_topbar_height"
                android:fitsSystemWindows="true">

            <com.qmuiteam.qmui.layout.QMUILinearLayout style="@style/button_wrapper_style"
                android:padding="@dimen/common_content_spacing"
                android:orientation="vertical">

                <TextView
                    style="@style/QDCommonTitle"
                    android:text="1.打开模块蓝牙,选择指定的模块" />

                <EditText
                    android:id="@+id/sf_main_ota_mac_et"
                    android:layout_width="250dp"
                    android:layout_height="50dp"
                    android:hint="已选择的蓝牙模块地址"/>

                <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
                    android:id="@+id/search_blue_device"
                    android:layout_width="80dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="left"
                    android:clickable="true"
                    android:gravity="center"
                    android:padding="10dp"
                    android:text="选择"
                    app:qmui_radius="4dp"/>
            </com.qmuiteam.qmui.layout.QMUILinearLayout>

            <com.qmuiteam.qmui.layout.QMUILinearLayout style="@style/button_wrapper_style"
                android:padding="@dimen/common_content_spacing"
                android:orientation="vertical">

                <TextView
                    style="@style/QDCommonTitle"
                    android:text="2.选择升级包文件夹路径" />
                <TextView
                    android:id="@+id/bin_path"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="5dp"
                    android:lineSpacingExtra="6dp"
                    android:text="默认升级包路径:/norv1/"
                    android:textColor="?attr/qmui_config_color_gray_5" />
                <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
                    android:id="@+id/choose_dir"
                    android:layout_width="80dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="left"
                    android:clickable="true"
                    android:gravity="center"
                    android:padding="10dp"
                    android:text="选择"
                    app:qmui_radius="4dp"/>
            </com.qmuiteam.qmui.layout.QMUILinearLayout>

            <com.qmuiteam.qmui.layout.QMUILinearLayout style="@style/button_wrapper_style"
                android:padding="@dimen/common_content_spacing"
                android:orientation="vertical">

                <TextView
                    style="@style/QDCommonTitle"
                    android:text="3.点击开始" />

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center_horizontal"
                    android:orientation="vertical" >
                    <com.qmuiteam.qmui.widget.QMUIProgressBar
                        android:id="@+id/sf_main_ota_progress_pb"
                        android:layout_width="match_parent"
                        android:layout_height="24dp"
                        android:textColor="@color/qmui_config_color_white"
                        android:textSize="16sp"
                        app:qmui_background_color="@color/qmui_config_color_gray_8"
                        app:qmui_progress_color="@color/app_color_blue_2"
                        app:qmui_type="type_rect"
                        app:qmui_skin_background="?attr/app_skin_progress_bar_bg_color"
                        app:qmui_skin_progress_color="?attr/app_skin_progress_bar_progress_color"
                        app:qmui_skin_text_color="?attr/app_skin_progress_bar_text_color"/>
                </LinearLayout>

                <LinearLayout
                    android:orientation="horizontal"
                    android:layout_width="match_parent"
                    android:paddingTop="10dp"
                    android:paddingBottom="10dp"
                    android:layout_height="wrap_content">

                    <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
                        android:id="@+id/sf_main_ota_nor_v2_tv"
                        android:layout_width="80dp"
                        android:layout_height="wrap_content"
                        android:layout_gravity="left"
                        android:clickable="true"
                        android:gravity="center"
                        android:padding="10dp"
                        android:text="开始"
                        app:qmui_radius="4dp"/>
                    <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
                        android:id="@+id/sf_main_ota_nor_v2_stop_tv"
                        android:layout_width="80dp"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:layout_marginLeft="10dp"
                        android:clickable="true"
                        android:gravity="center"
                        android:padding="10dp"
                        android:text="停止"
                        app:qmui_radius="4dp"/>
                    <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
                        android:id="@+id/sf_main_ota_nor_v2_resume_tv"
                        android:visibility="gone"
                        android:layout_width="80dp"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:layout_marginLeft="10dp"
                        android:clickable="true"
                        android:gravity="center"
                        android:padding="10dp"
                        android:text="恢复"
                        app:qmui_radius="4dp"/>
                </LinearLayout>
            </com.qmuiteam.qmui.layout.QMUILinearLayout>
            <ScrollView
                android:id="@+id/scrollView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginTop="13dp"
                android:layout_marginRight="13dp"
                android:layout_marginLeft="13dp"
                android:layout_marginBottom="10dp"
                android:background="@drawable/table_content_cell_bg">
                <TextView
                    android:id="@+id/history_list"
                    android:text=""
                    android:textSize="14sp"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"/>
            </ScrollView>


        </LinearLayout>


        <com.qmuiteam.qmui.widget.QMUITopBarLayout
            android:id="@+id/topbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"/>

    </com.qmuiteam.qmui.widget.QMUIWindowInsetLayout>


</androidx.drawerlayout.widget.DrawerLayout>


2、蓝牙搜索界面布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout   xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:fcf="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_drawer_layout"
    android:background="@color/app_color_gray"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.qmuiteam.qmui.widget.QMUIWindowInsetLayout
        android:id="@+id/main_content_frame_parent"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                android:layout_marginTop="?attr/qmui_topbar_height"
                android:fitsSystemWindows="true">

            <LinearLayout
                android:orientation="horizontal"
                android:layout_width="match_parent"
                android:padding="@dimen/common_content_spacing"
                android:layout_height="wrap_content">

                <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
                    android:id="@+id/search_btn"
                    android:layout_width="80dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="left"
                    android:clickable="true"
                    android:gravity="center"
                    android:padding="10dp"
                    android:text="搜索"
                    app:qmui_radius="4dp"/>
                <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
                    android:id="@+id/stop_btn"
                    android:layout_width="80dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginLeft="10dp"
                    android:clickable="true"
                    android:gravity="center"
                    android:padding="10dp"
                    android:text="停止"
                    app:qmui_radius="4dp"/>
                <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
                    android:id="@+id/clear_btn"
                    android:layout_width="80dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_marginLeft="10dp"
                    android:clickable="true"
                    android:gravity="center"
                    android:padding="10dp"
                    android:text="清除"
                    app:qmui_radius="4dp"/>
            </LinearLayout>
            <FrameLayout
                android:layout_marginTop="5dp"
                android:layout_marginRight="15dp"
                android:layout_marginLeft="15dp"
                android:padding="@dimen/common_content_spacing"
                android:background="@drawable/button_white_background"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/recyclerView"
                    android:scrollbarSize="9dp"
                    android:scrollbarThumbVertical="@color/app_list_scrollbar_color"
                    android:scrollbars="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />

                <com.qmuiteam.qmui.widget.QMUIEmptyView
                    android:id="@+id/emptyView"
                    android:visibility="gone"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@drawable/button_white_background"
                    app:qmui_title_text="@string/emptyView_mode_desc_double"/>
            </FrameLayout>

        </LinearLayout>


        <com.qmuiteam.qmui.widget.QMUITopBarLayout
            android:id="@+id/topbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"/>

    </com.qmuiteam.qmui.widget.QMUIWindowInsetLayout>


</androidx.drawerlayout.widget.DrawerLayout>


item布局:

<?xml version="1.0" encoding="utf-8"?><!--
 Tencent is pleased to support the open source community by making QMUI_Android available.

 Copyright (strength) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved.

 Licensed under the MIT License (the "License"); you may not use this file except in
 compliance with the License. You may obtain a copy of the License at

 http://opensource.org/licenses/MIT

 Unless required by applicable law or agreed to in writing, software distributed under the License is
 distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 either express or implied. See the License for the specific language governing permissions and
 limitations under the License.
-->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                        <TextView
                            android:id="@+id/item_name"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_marginTop="10dp"
                            android:gravity="left|top"
                            android:textSize="18sp"
                            android:lines="1"
                            android:text="1"
                            android:fontFamily="sans-serif"
                            android:textColor="@color/app_float_box_title_color" />

                        <TextView
                            android:id="@+id/item_desc"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_marginTop="7dp"
                            android:layout_marginBottom="5dp"
                            android:gravity="left|top"
                            android:textSize="15sp"
                            android:lines="1"
                            android:text="2"
                            android:fontFamily="sans-serif"
                            android:textColor="@color/app_float_box_title_color" />
                </LinearLayout>
                <ImageButton
                    android:id="@+id/item_selected"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@mipmap/no_selected"
                    android:layout_centerVertical="true"
                    android:layout_alignParentRight="true"
                    android:layout_marginRight="10dp"
                    />
        </RelativeLayout>
        <View
            android:layout_width="match_parent"
            android:layout_height="2dp"
            android:background="@color/app_color_gray" />
</LinearLayout>


蓝牙搜索界面:

package com.example.sifliotademo;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.PersistableBundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;

import com.qmuiteam.qmui.widget.QMUITopBarLayout;
import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton;

import top.keepempty.base.BaseRecyclerAdapter;
import top.keepempty.base.RecyclerViewHolder;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import top.keepempty.BaseActivity;
import top.keepempty.R;
import top.keepempty.data.BluetoothDeviceItem;
import top.keepempty.data.PosisitionDetailData;

public class SearchBluetoothAcitivty extends BaseActivity {


    private static final String TAG = "SearchBluetoothAcitivty";
    private BluetoothAdapter bluetoothAdapter;

    QMUIRoundButton mSearchButton;
    QMUIRoundButton mStopButton;
    QMUIRoundButton mClearButton;
    private QMUITopBarLayout mTopBar;
    private Button mRightButton;
    private int selectItemId = -1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.i(TAG, "*****************onCreate*************");
        setContentView(R.layout.activity_search_bluetooth);

        initView();
        initRecyclerView();
        initBluetooth();

        startSearch();
    }

    void startSearch(){
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                requestPermission();
            }
        }, 100);
    }

    @Override
    public void onBackPressed() {
        // super.onBackPressed();//不能够有该行代码,否则返回崩溃
        Intent intent = new Intent();
        setResult(RESULT_CANCELED, intent);
        finish();
    }

    private void initView(){
        mTopBar = findViewById(R.id.topbar);
        mTopBar.setTitle("搜索并选择升级的标签");

        mTopBar.addLeftBackImageButton().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onBackPressed();
            }
        });

        mRightButton = mTopBar.addRightTextButton("确认", R.id.topbar_right_confirm_button);
        mRightButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (selectItemId == -1){
                    showError("请选择搜索到的蓝牙设备");
                    return;
                }
                BluetoothDeviceItem item = mAdapter.getItem(selectItemId);
                Intent intent = new Intent();
                intent.putExtra("name", item.name);
                intent.putExtra("mac", item.mac);
                // 设置结果,并进行传送
                SearchBluetoothAcitivty.this.setResult(RESULT_OK, intent);
                finish();
            }
        });

        mSearchButton = findViewById(R.id.search_btn);
        mSearchButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                requestPermission();
            }
        });
        mStopButton = findViewById(R.id.stop_btn);
        mStopButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bluetoothAdapter.cancelDiscovery();
                selectItemId = -1;
                mSearchButton.setEnabled(true);
                mStopButton.setEnabled(false);
            }
        });
        mClearButton = findViewById(R.id.clear_btn);
        mClearButton.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                mAdapter.removeAll();
                selectItemId = -1;
                listResult.clear();
                mAdapter.notifyDataSetChanged();
            }
        });

    }

    RecyclerView mRecyclerView;
    private View mEmptyView;
    private BaseRecyclerAdapter<BluetoothDeviceItem> mAdapter;
    private HashSet<String> listResult = new HashSet<>();
    private void initRecyclerView() {
        mRecyclerView = findViewById(R.id.recyclerView);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this) {
            @Override
            public RecyclerView.LayoutParams generateDefaultLayoutParams() {
                return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
            }
        });
        mEmptyView = findViewById(R.id.emptyView);

        //每个页面都有这个adapter适配器
        mAdapter =  new BaseRecyclerAdapter<BluetoothDeviceItem>(this, null) {
            @Override
            public int getItemLayoutId(int viewType) {
                return R.layout.ble_tag_list_view_item;
            }

            @Override
            public int getItemViewType(int position) {
                return super.getItemViewType(position);
            }

            @Override
            public void bindData(RecyclerViewHolder holder, int position, BluetoothDeviceItem item) {
                holder.getTextView(R.id.item_name).setText(item.name);
                holder.getTextView(R.id.item_desc).setText(item.mac);

                ImageButton image_selected = holder.getImageButton(R.id.item_selected);
                if (selectItemId == position){
                    image_selected.setImageResource(R.mipmap.selected);
                }else{
                    image_selected.setImageResource(R.mipmap.no_selected);
                }

            }
        };
        mAdapter.setOnItemClickListener(new BaseRecyclerAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View itemView, int pos) {
                selectItemId = pos;
                mAdapter.notifyDataSetChanged();
            }
        });
        mRecyclerView.setAdapter(mAdapter);
        mAdapter.setUseHeaderView(false);

    }

    /**
     * 动态处理权限
     */
    private void requestPermission() {
        if (Build.VERSION.SDK_INT >= 23) {
            int checkAccessFinePermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
            if (checkAccessFinePermission != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        1);
                Log.d(TAG, "没有权限,请求权限");
                return;
            } else {
                /**
                 * 如果已经同意了该权限则开始搜索设备
                 */
                mAdapter.removeAll();
                listResult.clear();
                bluetoothAdapter.startDiscovery();
                mSearchButton.setEnabled(false);
                mStopButton.setEnabled(true);
                showLoading();
                //bluetoothAdapter.startLeScan(leScanCallback);
            }
            Log.d(TAG, "已有定位权限");
        }
    }


    private BluetoothAdapter.LeScanCallback leScanCallback =
            new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(final BluetoothDevice device, final int rssi,
                                     byte[] scanRecord) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {

                        }
                    });
                }
            };
    /**
     * 申请权限回调方法 处理用户是否授权
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case 1: {
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    /**
                     * 用户同意授权开始搜索设备
                     */
                    bluetoothAdapter.startDiscovery();

                } else {
                    //用户拒绝授权 则给用户提示没有权限功能无法使用,
                    Log.d(TAG, "没有定位权限,请先开启!");
                }
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mReceiver);
    }

    private void initBluetooth(){

        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter == null){
            Toast.makeText(this,"设备不支持蓝牙", Toast.LENGTH_SHORT).show();
        }
        if (!bluetoothAdapter.isEnabled()){
            bluetoothAdapter.enable();
        }
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        registerReceiver(mReceiver,filter);
        IntentFilter filter1 = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        registerReceiver(mReceiver,filter1);
    }

    /**
     * 创建广播接收器
     */
    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(BluetoothDevice.ACTION_FOUND)){
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                BluetoothDeviceItem item = new BluetoothDeviceItem();
                item.name = device.getName();
                if (item.name == null || item.name == "" || (!item.name.contains("xiaomi") && !item.name.contains("huwei"))){
                    return;
                }
                hideLoading();
                if (listResult.contains(device.toString())){
                    return;
                }
                item.mac = device.getAddress();
                Log.i(TAG, "ITEM:"+device.toString());
                listResult.add(device.toString());
                if (mEmptyView.getVisibility() != View.GONE){
                    mEmptyView.setVisibility(View.GONE);
                    mRecyclerView.setVisibility(View.VISIBLE);
                }
                mAdapter.add(mAdapter.getItemCount() , item);
                mAdapter.notifyDataSetChanged(); 
            }else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)){ 
                mSearchButton.setEnabled(true);
                mStopButton.setEnabled(false);
                hideLoading();
            }
        }
    };
}


本文为呱牛笔记原创文章,转载无需和我联系,但请注明来自呱牛笔记 ,it3q.com

请先登录后发表评论
  • 最新评论
  • 总共0条评论