Skip to content

Android 应用开发指南

工业 Android App 开发特点

工业 Android App 与消费级 App 的主要区别:

特性消费级 App工业 App
目标用户普通消费者专业操作员
UI 设计美观、时尚简洁、高效、大字体
网络依赖强依赖支持离线工作
更新方式Google PlayMDM 推送 / 本地更新
权限标准权限系统级权限(串口、GPIO)
稳定性一般极高(7×24小时)
安全性一般高(数据加密、防篡改)

串口通信开发

工业设备通常通过串口与外设通信(打印机、扫码枪、传感器等)。

使用移远串口库

java
// build.gradle 添加依赖
// 移远提供的串口库(从开发者中心下载)
implementation files('libs/quectel-serialport.jar')

// 串口通信封装类
public class SerialPortManager {
    private SerialPort serialPort;
    private InputStream inputStream;
    private OutputStream outputStream;
    private Thread readThread;
    private volatile boolean isRunning = false;
    
    public interface DataCallback {
        void onDataReceived(byte[] data, int length);
    }
    
    private DataCallback callback;
    
    public boolean open(String portPath, int baudRate, DataCallback cb) {
        try {
            this.callback = cb;
            serialPort = new SerialPort(new File(portPath), baudRate, 0);
            inputStream = serialPort.getInputStream();
            outputStream = serialPort.getOutputStream();
            
            isRunning = true;
            startReadThread();
            return true;
        } catch (Exception e) {
            Log.e("SerialPort", "Open failed: " + e.getMessage());
            return false;
        }
    }
    
    private void startReadThread() {
        readThread = new Thread(() -> {
            byte[] buffer = new byte[1024];
            while (isRunning) {
                try {
                    int bytesRead = inputStream.read(buffer);
                    if (bytesRead > 0 && callback != null) {
                        byte[] data = Arrays.copyOf(buffer, bytesRead);
                        callback.onDataReceived(data, bytesRead);
                    }
                } catch (IOException e) {
                    if (isRunning) {
                        Log.e("SerialPort", "Read error: " + e.getMessage());
                    }
                }
            }
        });
        readThread.start();
    }
    
    public void send(byte[] data) {
        try {
            outputStream.write(data);
            outputStream.flush();
        } catch (IOException e) {
            Log.e("SerialPort", "Send error: " + e.getMessage());
        }
    }
    
    public void close() {
        isRunning = false;
        try {
            if (serialPort != null) serialPort.close();
        } catch (Exception e) {
            Log.e("SerialPort", "Close error: " + e.getMessage());
        }
    }
}

使用示例

java
// 在 Activity 中使用串口
SerialPortManager serialManager = new SerialPortManager();

serialManager.open("/dev/ttyHS1", 9600, (data, length) -> {
    String received = new String(data, 0, length);
    runOnUiThread(() -> {
        tvReceived.setText(received);
    });
});

// 发送数据
btnSend.setOnClickListener(v -> {
    String text = etInput.getText().toString();
    serialManager.send(text.getBytes());
});

条码扫描集成

使用 ZXing 库

java
// build.gradle
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'

// 启动扫码
IntentIntegrator integrator = new IntentIntegrator(this);
integrator.setDesiredBarcodeFormats(IntentIntegrator.ALL_CODE_TYPES);
integrator.setPrompt("扫描条码");
integrator.setCameraId(0);
integrator.setBeepEnabled(true);
integrator.setBarcodeImageEnabled(false);
integrator.initiateScan();

// 处理扫码结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    IntentResult result = IntentIntegrator.parseActivityResult(
        requestCode, resultCode, data);
    if (result != null) {
        if (result.getContents() == null) {
            Toast.makeText(this, "取消扫描", Toast.LENGTH_LONG).show();
        } else {
            String barcode = result.getContents();
            processBarcode(barcode);
        }
    }
}

离线数据缓存

工业 App 必须支持离线工作,网络恢复后自动同步。

Room 数据库

java
// 定义实体
@Entity(tableName = "sensor_data")
public class SensorData {
    @PrimaryKey(autoGenerate = true)
    public int id;
    
    public float temperature;
    public float humidity;
    public long timestamp;
    
    @ColumnInfo(name = "is_synced")
    public boolean isSynced = false;
}

// 定义 DAO
@Dao
public interface SensorDataDao {
    @Insert
    void insert(SensorData data);
    
    @Query("SELECT * FROM sensor_data WHERE is_synced = 0")
    List<SensorData> getUnsyncedData();
    
    @Query("UPDATE sensor_data SET is_synced = 1 WHERE id = :id")
    void markAsSynced(int id);
}

// 数据同步服务
public class SyncService extends Service {
    private SensorDataDao dao;
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 检查网络
        if (isNetworkAvailable()) {
            syncData();
        }
        return START_STICKY;
    }
    
    private void syncData() {
        List<SensorData> unsyncedData = dao.getUnsyncedData();
        for (SensorData data : unsyncedData) {
            if (uploadToServer(data)) {
                dao.markAsSynced(data.id);
            }
        }
    }
}

MDM 远程管理

工业设备通常需要 MDM(Mobile Device Management)进行远程管理。

常用 MDM 功能

java
// 远程锁定设备
DevicePolicyManager dpm = (DevicePolicyManager) 
    getSystemService(Context.DEVICE_POLICY_SERVICE);
dpm.lockNow();

// 远程擦除数据
dpm.wipeData(0);

// 静默安装 APK(需要系统权限)
PackageInstaller installer = getPackageManager().getPackageInstaller();
// ... 静默安装逻辑

// 禁用 USB 调试(生产环境)
Settings.Global.putInt(getContentResolver(), 
    Settings.Global.ADB_ENABLED, 0);

自定义 MDM 客户端

java
// MDM 客户端:接收服务器指令
public class MDMClient {
    private static final String MDM_SERVER = "https://mdm.example.com";
    
    // 定期轮询服务器指令
    public void pollCommands() {
        String deviceId = getDeviceId();
        
        // 发送心跳并获取指令
        String url = MDM_SERVER + "/device/" + deviceId + "/commands";
        // HTTP GET 请求...
        
        // 执行指令
        for (Command cmd : commands) {
            executeCommand(cmd);
        }
    }
    
    private void executeCommand(Command cmd) {
        switch (cmd.type) {
            case "install_app":
                installApp(cmd.params.get("apk_url"));
                break;
            case "update_config":
                updateConfig(cmd.params);
                break;
            case "reboot":
                reboot();
                break;
            case "collect_logs":
                uploadLogs();
                break;
        }
    }
}

应用稳定性保障

崩溃恢复

java
// 全局异常处理
public class CrashHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        // 记录崩溃日志
        saveCrashLog(ex);
        
        // 上报到服务器
        uploadCrashLog();
        
        // 重启 App
        Intent intent = new Intent(context, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
        
        // 结束当前进程
        android.os.Process.killProcess(android.os.Process.myPid());
    }
}

// 在 Application 中注册
Thread.setDefaultUncaughtExceptionHandler(new CrashHandler());

看门狗

java
// 软件看门狗:定期检查 App 状态
public class WatchdogService extends Service {
    private Handler handler = new Handler();
    private static final int WATCHDOG_INTERVAL = 30000; // 30秒
    
    private Runnable watchdogRunnable = new Runnable() {
        @Override
        public void run() {
            // 检查关键服务是否正常
            if (!isMainServiceRunning()) {
                // 重启主服务
                startMainService();
            }
            
            // 检查网络连接
            if (!isNetworkAvailable()) {
                // 尝试重连
                reconnectNetwork();
            }
            
            // 继续下一次检查
            handler.postDelayed(this, WATCHDOG_INTERVAL);
        }
    };
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        handler.post(watchdogRunnable);
        return START_STICKY;  // 服务被杀死后自动重启
    }
}

褚成志的笔记