根据NORDIC官方Android-DFU-Library实现,具体步骤如下:
1、build.gradle配置
implementation 'no.nordicsemi.android:dfu:1.9.0'
2、AndroidMainfest.xml中申请BLE的相关权限、读写权限、定位等。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
3、创建DfuService,实现 getNotificationTarget()
方法, 在进行DFU时,该方法会返回一个活动类 ,该活动通过Intent ,FLAG_ACTIVITY_NEW_TASK标志开启,下面步骤是定义的具体活动类。
public class DfuService extends DfuBaseService {
public DfuService() {
}
@Nullable
@Override
protected Class<? extends Activity> getNotificationTarget() {
/*
* As a target activity the NotificationActivity is returned, not the MainActivity. This is because
* the notification must create a new task:
*
* intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
*
* when you press it. You can use NotificationActivity to check whether the new activity
* is a root activity (that means no other activity was open earlier) or that some
* other activity is already open. In the latter case the NotificationActivity will just be
* closed. The system will restore the previous activity. However, if the application has been
* closed during upload and you click the notification, a NotificationActivity will
* be launched as a root activity. It will create and start the main activity and
* terminate itself.
*
* This method may be used to restore the target activity in case the application
* was closed or is open. It may also be used to recreate an activity history using
* startActivities(...).
*/
return NotificationActivity.class;
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
4、创建NotificationActivity类,继承自Activity,该类主要用于组织APP的其他实例启动。
public class NotificationActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// If this activity is the root activity of the task, the app is not running
if (isTaskRoot()) {
// Start the app before finishing
final Intent intent = new Intent(this, MineDfuActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtras(getIntent().getExtras()); // copy all extras
startActivity(intent);
}
// Now finish, which will drop you to the activity at which you were at the top of the task stack
finish();
}
}
5、开启DFU服务
final DfuServiceInitiator starter = new DfuServiceInitiator(MineDeviceActivity.bleDevice.getMac())
.setDeviceName(MineDeviceActivity.bleDevice.getName());
// If you want to have experimental buttonless DFU feature supported call additionally:starter.setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true);
String fileName = urlDfuPackage.substring(urlDfuPackage.lastIndexOf("/"));
String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
starter.setZip(directory + fileName);
final DfuServiceController controller = starter.start(MyApplication.getContext(), DfuService.class);
//以下是NORDIC DEMO分文件包的格式进行不同处理,我的直接是在固定存储路径下压缩包文件,所以就按照以上写了,以下供参考
// Init packet is required by Bootloader/DFU from SDK 7.0+ if HEX or BIN file is given above.
// In case of a ZIP file, the init packet (a DAT file) must be included inside the ZIP file.
if (mFileType == DfuService.TYPE_AUTO)
starter.setZip(mFileStreamUri, mFilePath);
else {
starter.setBinOrHex(mFileType, mFileStreamUri, mFilePath).setInitFile(mInitFileStreamUri, mInitFilePath);
}
final DfuServiceController controller = starter.start(this, DfuService.class);
// You may use the controller to pause, resume or abort the DFU process.
6、以上步骤完成了DFU过程,但是我们拿单板和APP测试也看不到DFU的过程,所以还需要创建DFU进度监听器,可以掌握DFU的动态情况,比如可以在界面显示DFU进度等。
//DFU进度监听器
private final DfuProgressListener mDfuProgressListener = new DfuProgressListenerAdapter(){
@Override
public void onDeviceConnecting(final String deviceAddress) {
relativeDfuProgress.setVisibility(View.VISIBLE);
}
@Override
public void onDfuProcessStarting(final String deviceAddress) {
relativeDfuProgress.setVisibility(View.VISIBLE);
}
@Override
public void onEnablingDfuMode(final String deviceAddress) {
relativeDfuProgress.setVisibility(View.VISIBLE);
}
@Override
public void onFirmwareValidating(final String deviceAddress) {
relativeDfuProgress.setVisibility(View.VISIBLE);
}
@Override
public void onDeviceDisconnecting(final String deviceAddress) {
relativeDfuProgress.setVisibility(View.VISIBLE);
}
@Override
public void onDfuCompleted(final String deviceAddress) {
Toast.makeText(MineDfuActivity.this,"固件升级成功",Toast.LENGTH_SHORT).show();
// let's wait a bit until we cancel the notification. When canceled immediately it will be recreated by service again.
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// onTransferCompleted();
// if this activity is still open and upload process was completed, cancel the notification
final NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(DfuService.NOTIFICATION_ID);
}
}, 200);
}
@Override
public void onDfuAborted(final String deviceAddress) {
// let's wait a bit until we cancel the notification. When canceled immediately it will be recreated by service again.
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// onUploadCanceled();
// if this activity is still open and upload process was completed, cancel the notification
final NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(DfuService.NOTIFICATION_ID);
}
}, 200);
}
@Override
public void onProgressChanged(final String deviceAddress, final int percent, final float speed, final float avgSpeed, final int currentPart, final int partsTotal) {
int temp = (percent * 360)/100;
circleDfuProgress.update(temp,"");
// if (partsTotal > 1)
// mTextUploading.setText(getString(R.string.dfu_status_uploading_part, currentPart, partsTotal));
// else
// mTextUploading.setText(R.string.dfu_status_uploading);
}
@Override
public void onError(final String deviceAddress, final int error, final int errorType, final String message) {
// showErrorMessage(message);
// We have to wait a bit before canceling notification. This is called before DfuService creates the last notification.
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// if this activity is still open and upload process was completed, cancel the notification
final NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.cancel(DfuService.NOTIFICATION_ID);
}
}, 200);
}
};
7、在当前活动中重写onResume及onPause方法,分别进行监听器的注册和取消注册。
@Override
protected void onResume() {
super.onResume();
DfuServiceListenerHelper.registerProgressListener(this, mDfuProgressListener);
}
@Override
protected void onPause() {
super.onPause();
DfuServiceListenerHelper.unregisterProgressListener(this, mDfuProgressListener);
}
【参考】
https://github.com/NordicSemiconductor/Android-DFU-Library/tree/release/documentation