
????最近做了一個(gè)小功能,由于沒有需求,只有一個(gè)一代的app services功能實(shí)現(xiàn)進(jìn)行提示。由于更換了外包廠商,所以在升級(jí)版本上需要自行研發(fā)。然而一直從事底層開發(fā)的我,一臉懵逼,后來(lái)驗(yàn)證了,這根本就是n臉懵逼。
????首先下載dex2jar對(duì)apk進(jìn)行反編譯,然后用jd-gui打開。經(jīng)理說(shuō)可以參考這個(gè)進(jìn)行開發(fā),我當(dāng)時(shí)一看這不是很easy么,源碼都有了,再編譯一下就成。然而實(shí)在是太年輕,當(dāng)時(shí)不明白這些變量的名字為什么這么奇怪,后來(lái)才知道,apk經(jīng)過(guò)混淆,變量名都變了的。首先摸清楚業(yè)務(wù)邏輯就花了大力氣,因?yàn)榛煜^(guò)的app反解出來(lái)的代碼,邏輯不完全一樣,之可以知道大概做了些什么事,至于邏輯,需要重新組織。
????至于需要使用的第三方dfu library也是不甚了解,這里將上面反解出來(lái)的classes-dex2jar.jar作為jar包,放入到android studio工程中的app/libs即可以完成dfu library庫(kù)的導(dǎo)入。這里就可以使用第三方的接口進(jìn)行升級(jí)操作了。首先新建一個(gè)android項(xiàng)目,然后如上導(dǎo)入jar包,然后開始創(chuàng)建reciever。
package com.包名;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import java.lang.reflect.Method;
import java.io.File;
public class ControllerOTARec extends BroadcastReceiver {
private static final String TAG = "ControllerRec";
private static final String ACTION_READY_FOR_OTA = "com.action.readyForOTA";
private static final String DEVICE_NAME = "DEVICE";
public static final String RETRY_OTA_UPGRADE = "retry.ota.upgrade";
private static final String BT_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED";
private static final String GET_HAND_VERSION ="com.test.gethandversion";
private static final File otaFile = new File(ControllerOTAService.mFile);
public static boolean mHaveDisable = false;
public static boolean mHaveStart = false;
private int mOldVersion;
private int mNewVersion;
private Method getStringMethod = null;
public static BluetoothDevice mDevice = null;
@Override
public void onReceive(final Context context, final Intent intent0)
{
String actionStr = intent0.getAction();
if (BT_CONNECTED.equals(actionStr))
{
mDevice = (BluetoothDevice)intent0.getParcelableExtra("android.bluetooth.device.extra.DEVICE");
} else if(ACTION_READY_FOR_OTA.equals(actionStr)){
mNewVersion = getStringMethod("ro.build.controller.version", null);
int mOldVersion = Integer.parseInt(getStringMethod("sys.hand.appVersion","1"));
//catch if target version lower than present
if ((mNewVersion <= mOldVersion) || (mOldVersion == 0)) {
Log.d(TAG, "controller version is very new,not ota!");
return;
}
//catch if zipfile is not exist
if (!otaFile.exists()) {
Log.d(TAG, "ota file not exists");
return;
}
//begin to do the things
if (mDevice != null) {
final String deviceName = mDevice.getName();
final String deviceAddr = mDevice.getAddress();
int deviceBond = mDevice.getBondState();
Log.d(TAG, "deviceName:" + deviceName + ";and bond state is " + deviceBond + "; and device address is " + deviceAddr);
//通知藍(lán)牙狀態(tài)機(jī)馬上進(jìn)入ota模式,停止
if(!mHaveDisable) {
Log.d(TAG, "tell the bluetooth machine to stop for ota");
Intent intent = new Intent("bluetooth_ota_update");
intent.putExtra("start_bluetooth_state", false);
context.sendBroadcast(intent);
//delay 500ms for bt to stop
try {
Thread.sleep(500);
} catch (Exception e){
Log.d(TAG, "Sleep error");
}
}
if (!mHaveStart) {
removeBondStatus(mDevice);//這是清楚藍(lán)牙配對(duì),這是遇到的最大的一個(gè)問(wèn)題
//儲(chǔ)存設(shè)備地址和設(shè)備名信息
Log.d(TAG, "mHaveStart = true");
ControllerOTARec.mHaveStart = true;
SharedPreferences.Editor editor = context.getSharedPreferences("Controller_OTA", 0).edit();
editor.putString("deviceName", deviceName);
editor.putString("deviceAddress", deviceAddr);
editor.commit();
//告訴cs開始進(jìn)入ota模式
Log.d(TAG,"will tell the controller service to stop for ota");
Intent intent1 = new Intent("com.startsignal");
context.sendBroadcast(intent1);
//準(zhǔn)備工作完成,啟動(dòng)服務(wù)
Log.d(TAG, "start ota:" + mDevice.getAddress());
Intent intent2 = new Intent(context, ControllerOTAService.class);
intent2.putExtra("deviceName", deviceName);
intent2.putExtra("deviceAddress", deviceAddr);
context.startService(intent2);
}
}
} else if(RETRY_OTA_UPGRADE.equals(actionStr)){
Log.d(TAG, "wille retry for hand devices ota");
Intent intent = new Intent(context, ControllerOTAService.class);
String add = context.getSharedPreferences("Controller_OTA",0).getString("deviceAddress","default");
intent.putExtra("deviceName",DEVICE_NAME);
intent.putExtra("deviceAddress",add);
context.startService(intent);
}
}
//反射的方式訪問(wèn)系統(tǒng)屬性
public String getStringMethod(final String key, final String def) {
try {
if (getStringMethod == null) {
getStringMethod = Class.forName("android.os.SystemProperties").getMethod("get", String.class, String.class);
}
return ((String) getStringMethod.invoke(null, key, def)).toString();
} catch (Exception e) {
return def;
}
}
//反射的方式訪問(wèn)藍(lán)牙設(shè)備的隱藏接口removebond
public void removeBondStatus(BluetoothDevice btDevices){
boolean result = false;
try {
final Method removeBondStatus = btDevices.getClass().getMethod("removeBond");
if (removeBondStatus != null) {
result = (Boolean) removeBondStatus.invoke(btDevices);
Log.w(TAG, "removeBondStatus result is " + result);
while (btDevices.getBondState() != BluetoothDevice.BOND_NONE){
Thread.sleep(20);
}
}
} catch (final Exception e) {
Log.w(TAG, "An exception occurred while removing bond information", e);
}
}
}
然后創(chuàng)建服務(wù)
package com.包名;
import android.app.ProgressDialog;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import java.io.File;
import no.nordicsemi.android.dfu.DfuProgressListener;
import no.nordicsemi.android.dfu.DfuServiceInitiator;
import no.nordicsemi.android.dfu.DfuServiceListenerHelper;
public class ControllerOTAService extends Service {
//定義安裝包路徑
public static final String ZIP_FILE_PATH = "/sdcard/Controller/";
//獲取安裝包文件名
public static final String mFile = getmPathFile();
//進(jìn)度dialog
private ProgressDialog mDialog;
//通過(guò)路徑找文件名
public static String getmPathFile(){
String otapackagepath = null;
File file = new File(ZIP_FILE_PATH);
File[] array = file.listFiles();
if(array[0].isFile()){
otapackagepath = ZIP_FILE_PATH +array[0].getName();
Log.d("ControllerOTAService", "file path is :" + otapackagepath);
} else{
Log.d("ControllerOTAService", "can not find the zip file");
}
return otapackagepath;
}
public ControllerOTAService() {
}
//注銷監(jiān)聽,停止服務(wù)
private void stopService()
{
DfuServiceListenerHelper.unregisterProgressListener(this, mDfuProgressListener);//取消監(jiān)聽升級(jí)回調(diào)
stopSelf();
}
//啟動(dòng)升級(jí)流程
private void beginToUpdate(String devname, String addr)
{
Log.d("ControllerOTAService", "begin to update hand device by DFU,create dialog");
createDialog();
if (this.mDialog != null)
this.mDialog.show();
Log.d("ControllerOTAService", "devices name is " + devname +", address is " + addr + ", zip file path is " + mFile);
final DfuServiceInitiator dfuservice = new DfuServiceInitiator(addr)
.setDeviceName(devname)
.setKeepBond(true);//升級(jí)完成后保持連接
dfuservice.setZip(null, mFile);
dfuservice.start(this, DfuService.class);
DfuServiceListenerHelper.registerProgressListener(this, mDfuProgressListener);//注冊(cè)監(jiān)聽
Log.d("ControllerOTAService", "dismiss dialog");
//取消dialog
new Thread(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(5000);
ControllerOTAService.this.mDialog.dismiss();
} catch(InterruptedException e){
e.printStackTrace();
}
}
}).start();
}
//創(chuàng)建dialog
private void createDialog()
{
Log.d("ControllerOTAService", "create dialog");
this.mDialog = new ProgressDialog(this);
this.mDialog.setMax(100);
this.mDialog.setProgress(0);
this.mDialog.setProgressStyle(1);
this.mDialog.setTitle("hand ota");
this.mDialog.setMessage("start update hand device,please use head mode");
this.mDialog.setIndeterminate(false);
this.mDialog.setCancelable(true);
this.mDialog.getWindow().setType(2003);
}
//startservice會(huì)調(diào)到這里
public int onStartCommand(Intent intent, int int1, int int2)
{
String devname = intent.getStringExtra("deviceName");
String addr = intent.getStringExtra("deviceAddress");
Log.d("ControllerOTAService", "deviceName:" + devname + " deviceAddress:" + addr);
beginToUpdate(devname, addr);
return Service.START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
//新建dfu升級(jí)流程監(jiān)聽(新建后自動(dòng)彈出接口函數(shù))
private final DfuProgressListener mDfuProgressListener = new DfuProgressListener() {
//device connecting
@Override
public void onDeviceConnecting(String deviceAddress) {
Log.d("ControllerOTAService", "onDeviceConnecting");
}
//devices begin to connect
@Override
public void onDeviceConnected(String deviceAddress) {
Log.d("ControllerOTAService", "onDeviceConnected");
}
//before ota process start
@Override
public void onDfuProcessStarting(String deviceAddress) {
Log.d("ControllerOTAService", "onDfuProcessStarting");
}
@Override
public void onDfuProcessStarted(String deviceAddress) {
Log.d("ControllerOTAService", "onDfuProcessStarted");
}
@Override
public void onEnablingDfuMode(String deviceAddress) {
Log.d("ControllerOTAService", "onEnablingDfuMode");
}
@Override
public void onProgressChanged(String deviceAddress, int percent, float speed, float avgSpeed, int currentPart, int partsTotal) {
Log.d("ControllerOTAService", "onProgressChanged");
}
@Override
public void onFirmwareValidating(String deviceAddress) {
Log.d("ControllerOTAService", "onFirmwareValidating");
}
@Override
public void onDeviceDisconnecting(String deviceAddress) {
Log.d("ControllerOTAService", "onDeviceDisconnecting");
}
@Override
public void onDeviceDisconnected(String deviceAddress) {
Log.d("ControllerOTAService", "onDeviceDisconnected");
}
//完成升級(jí)會(huì)調(diào)入到這里
@Override
public void onDfuCompleted(String deviceAddress) {
Log.d("ControllerOTAService", "onDfuCompleted");
//發(fā)出停止廣播
Intent intent = new Intent("com.handota.stop");
ControllerOTAService.this.sendBroadcast(intent);
//恢復(fù)狀態(tài)機(jī)
Intent intent1 = new Intent("bluetooth_ota_update");
intent1.putExtra("start_bluetooth_state", true);
ControllerOTAService.this.sendBroadcast(intent1);
//是否啟動(dòng)ota狀態(tài)改為false
ControllerOTARec.mHaveStart = false;
//tell app the result
Intent intent2 = new Intent("hand_device_ota_process");
intent2.putExtra("handOTAProcess",1);
ControllerOTAService.this.sendBroadcast(intent2);
//DFU completed will delete the zip file
Log.d("ControllerOTAService", "begin to delete the update zipfile");
clearFile(ZIP_FILE_PATH);
Log.d("ControllerOTAService", "<<<onDfuCompleted");
ControllerOTAService.this.stopService();
}
@Override
public void onDfuAborted(String deviceAddress) {
Log.d("ControllerOTAService", "onDfuAborted");
Intent intent = new Intent("bluetooth_ota_update");
intent.putExtra("start_bluetooth_state", true);
ControllerOTAService.this.sendBroadcast(intent);
ControllerOTARec.mHaveStart = false;
//tell app the result
Intent intent2 = new Intent("hand_device_ota_process");
intent2.putExtra("handOTAProcess",2);
ControllerOTAService.this.sendBroadcast(intent2);
//DFU completed will delete the zip file
Log.d("ControllerOTAService", "begin to delete the update zipfile");
clearFile(ZIP_FILE_PATH);
ControllerOTAService.this.stopService();
}
@Override
public void onError(String deviceAddress, int error, int errorType, String message) {
Log.d("", "ERROR"+error+"message"+message);
Intent intent = new Intent("bluetooth_ota_update");
intent.putExtra("start_bluetooth_state", true);
ControllerOTAService.this.sendBroadcast(intent);
//tell app the result
Intent intent2 = new Intent("hand_device_ota_process");
intent2.putExtra("handOTAProcess",3);
ControllerOTAService.this.sendBroadcast(intent2);
ControllerOTARec.mHaveStart = false;
//DFU completed will delete the zip file
Log.d("ControllerOTAService", "begin to delete the update zipfile");
clearFile(ZIP_FILE_PATH);
ControllerOTAService.this.stopService();
}
};
//刪除文件函數(shù)
public static void clearFile(String filePath){
File targetFile = new File(filePath);
File[] fileList = targetFile.listFiles();
Log.d("ControllerOTAService", "clearFile");
if(fileList[0].exists()){
fileList[0].delete();
Log.d("ControllerOTAService", "clearFile success");
}else {
Log.d("ControllerOTAService", "no file to delete");
}
}
}
????這里還要?jiǎng)?chuàng)建dfuservices類
package com.包名;
import android.app.Activity;
import no.nordicsemi.android.dfu.DfuBaseService;
public class DfuService extends DfuBaseService
{
protected Class<? extends Activity> getNotificationTarget()
{
return NotificationActivity.class;
}
protected boolean isDebug()
{
return true;
}
}
????創(chuàng)建NotificationActivity通知類
package com.包名;
import android.os.Bundle;
import android.app.Activity;
public class NotificationActivity extends Activity {
@Override
protected void onCreate(Bundle bundle)
{
super.onCreate(bundle);
finish();
}
}
????配置xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com包名">
<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.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".ControllerOTAService"
android:enabled="true"
android:exported="true" />
<service
android:name=".DfuService"
android:enabled="true"
android:exported="true" />
<receiver android:name=".ControllerOTARec">
<intent-filter>
<action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
<action android:name="retry.ota.upgrade" />
<action android:name="com.action.readyForOTA" />
</intent-filter>
</receiver>
</application>
</manifest>
????配合藍(lán)牙狀態(tài)機(jī)升級(jí)流程大致如下

????遇到的問(wèn)題:
????1.不同版本的官方nordic軟件,升級(jí)方式也不一樣,1.61.3版本連接狀態(tài)下可以直接進(jìn)行升級(jí);2.6.0版本必須設(shè)備通過(guò)硬件進(jìn)入ota模式才能正常升級(jí),同時(shí),高版本的dfu代碼在18年7月已經(jīng)對(duì)O代碼做了適應(yīng),同時(shí)到O有些接口發(fā)生了變化,得確認(rèn)是否在自己項(xiàng)目里面支持,不對(duì)應(yīng)的話,需要下載對(duì)應(yīng)的dfu版本的三方庫(kù);
????2.讀取設(shè)備版本這里遇到過(guò)一些問(wèn)題,由于代碼設(shè)計(jì)問(wèn)題,設(shè)備當(dāng)前版本在目前項(xiàng)目里面經(jīng)常被重置為1,這里采取臨時(shí)的系統(tǒng)屬性來(lái)儲(chǔ)存正確讀取的當(dāng)前版本;
????3.由于一開始使用的是反編譯的apk做jar包,有些庫(kù)函數(shù)并不能被反解出來(lái),導(dǎo)致內(nèi)部邏輯無(wú)法獲知,后來(lái)在github上下載了源碼,就可以看到了;
????4.遇到一個(gè)讀取dfu版本失敗的問(wèn)題,追Src read.p_value ptr is NULL發(fā)現(xiàn)深陷藍(lán)牙協(xié)議棧中的調(diào)用無(wú)法自拔,找不到根本原因,追Reading DFU version number發(fā)現(xiàn)是dfuservices要發(fā)起一此讀取dfu版本信息的請(qǐng)求,然后gattserver收到這個(gè)請(qǐng)教,通過(guò)jni,向藍(lán)牙協(xié)議棧發(fā)送了這個(gè)請(qǐng)求,然后返回的結(jié)果不對(duì),status為1,正確的情況status為0,一開始藍(lán)牙這一塊的東西什么都不懂的時(shí)候有點(diǎn)摸不清頭腦。為什么這里要去讀這個(gè),而且這個(gè)讀取操作在升級(jí)過(guò)程中有兩到三次。整個(gè)升級(jí)流程也是一頭霧水。后來(lái)慢慢的梳理log,發(fā)現(xiàn)在gatt server向手柄發(fā)送了進(jìn)入ota模式的命令后,然后再去讀取手柄相關(guān)屬性就會(huì)失敗。
????這里涉及到一個(gè)隱藏任務(wù),設(shè)備在ota模式和非ota模式,屬性值會(huì)發(fā)生變化,在手柄進(jìn)入ota模式后,主機(jī)設(shè)備的藍(lán)牙服務(wù)還會(huì)進(jìn)行一次掃描,然后才會(huì)開始發(fā)送安裝包進(jìn)行升級(jí)的操作。然而新產(chǎn)品上為什么協(xié)議棧沒有進(jìn)行掃描,這一點(diǎn)需要一定藍(lán)牙知識(shí)進(jìn)行調(diào)查可能會(huì)更快解決。這里的臨時(shí)做法,就是先進(jìn)行設(shè)備配對(duì)信息清除,清楚完之后,設(shè)備就會(huì)進(jìn)行掃描(可能跟產(chǎn)品里面的狀態(tài)機(jī)有關(guān)系,在有配對(duì)信息的情況下,就不會(huì)進(jìn)行掃描。)。即是這里的removeBondStatus(mDevice);這一塊困住了很長(zhǎng)時(shí)間,在宇神的一次操作下成功繞出去了,就得到了這個(gè)workaround。
DfuImpl : Reading DFU version number...
WCNSS_FILTER: ibs_recv_ibs_cmd: Received IBS_WAKE_IND: 0xFD
WCNSS_FILTER: ibs_recv_ibs_cmd: Writing IBS_WAKE_ACK
WCNSS_FILTER: do_write: IBS write: fc
bt_btif : btapp_gattc_req_data :Src read.p_value ptr is NULL for event 0x3
BluetoothGatt: onCharacteristicRead() - Device=D0:F0:63:61:D4:B1 handle=28 Status=1
DfuImpl : Characteristic read error: 1
DfuBaseService: Unable to read version number (error 1)
DfuBaseService: Disconnecting from the device...
BluetoothGatt: cancelOpen() - device: D0:F0:63:61:D4:B1
BtGatt.GattService: clientDisconnect() - address=D0:F0:63:61:D4:B1, connId=8
BtGatt.GattService: onDisconnected() - clientIf=8, connId=8, address=D0:F0:63:61:D4:B1
BluetoothGatt: onClientConnectionState() - status=0 clientIf=8 device=D0:F0:63:61:D4:B1
DfuBaseService: Disconnected from GATT server
DfuBaseService: Cleaning up...
BluetoothGatt: close()
BluetoothGatt: unregisterApp() - mClientIf=8
????5.apk實(shí)現(xiàn)后,怎樣將android studio集成到android source項(xiàng)目當(dāng)中去也是因?yàn)椴惶煜み@塊有太多的問(wèn)題,首先是sdk版本過(guò)高,將apk放入android項(xiàng)目失敗的問(wèn)題。這里去android studio中手動(dòng)從O降級(jí)到N,第三方庫(kù)的降級(jí)方法就是一個(gè)個(gè)去解決編譯錯(cuò)誤,找到對(duì)應(yīng)的地版本對(duì)象進(jìn)行替換。這里的25都是由27轉(zhuǎn)換過(guò)來(lái)的。
apply plugin: 'com.android.application'
android {
signingConfigs {
config {
keyAlias 'controllerota'
keyPassword '123456'
storeFile file('/home/edward/bin/android-studio/keystore/key.jks')
storePassword '123456'
}
}
compileSdkVersion 25
defaultConfig {
applicationId "com.qiyi.controllerota1"
minSdkVersion 23
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:25.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
implementation files('libs/DFUlibrary.jar')
}
????6.關(guān)于系統(tǒng)ro屬性的修改方法,在前一篇中記錄了,之修改buildinfo.sh似乎是不奏效的。
????最后一點(diǎn)忠告:就是盡量做需求清晰,設(shè)計(jì)明確的工作,降低跟同事工作的耦合度---------------雖然空白做法也挺鍛煉人。
????完全知識(shí)分享,謝謝支持