功能点不复杂,3天时间,完成一个蓝牙升级APP的工具。
4个界面:
- 闪屏页
- 主界面
- 蓝牙搜索界面
- 文件夹选择界面;
功能点:
1、闪屏页申请权限,其中包括蓝牙权限。
需要关注Android13的支持。
2、主界面操作升级功能;
显示进度,并反馈升级结果,升级日志。
3、蓝牙搜索界面搜索蓝牙设备,并进行选择。
4、文件夹选择界面选择指定的文件夹。
实现效果:
关键代码。
0、主界面代码:
代码语言:javascript复制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布局:
代码语言:javascript复制<?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、蓝牙搜索界面布局:
代码语言:javascript复制<?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布局:
代码语言:javascript复制<?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>
蓝牙搜索界面:
代码语言:javascript复制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
- 上一篇: 一个语音通话断续的问题分析
- 下一篇: GB28181开源平台WVP-PRO使用体验