Commit 95cd69f5 authored by 张宏's avatar 张宏

66666666666666666666666666

parent c3ea2815
......@@ -102,5 +102,6 @@ flutter {
}
dependencies {
// implementation 'com.github.jiangdongguo.AndroidUSBCamera:libausbc:3.3.3'
// WorkManager — 保活兜底检查
implementation "androidx.work:work-runtime:2.8.1"
}
......@@ -17,9 +17,26 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- ========== 保活相关权限 ========== -->
<!-- 前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<!-- 前台服务通知权限(Android 13+,运行时申请) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- CPU 唤醒锁(通知栏必备) -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- 请求豁免电池优化 -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!-- 开机自启 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- 后台启动 Activity 豁免(Android 10+ 非系统豁免场景需要) -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application
android:label="@string/app_name"
android:name="${applicationName}"
android:name=".MainApplication"
android:largeHeap="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
......@@ -49,6 +66,24 @@
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
<!-- 前台保活服务 -->
<service
android:name=".service.KeepAliveForegroundService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="specialUse" />
<!-- 开机自启广播接收器 -->
<receiver
android:name=".receiver.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
......
package com.qialg.laki_icu_app;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.qialg.laki_icu_app.service.KeepAliveForegroundService;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 启动前台保活服务
startKeepAliveForegroundService();
}
private void startKeepAliveForegroundService() {
Intent serviceIntent = new Intent(this, KeepAliveForegroundService.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
} else {
startService(serviceIntent);
}
// 检测并申请忽略电池优化
checkAndRequestBatteryOptimization();
}
private void checkAndRequestBatteryOptimization() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
android.os.PowerManager pm = (android.os.PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm != null && !pm.isIgnoringBatteryOptimizations(getPackageName())) {
try {
Intent intent = new Intent(android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(android.net.Uri.parse("package:" + getPackageName()));
startActivity(intent);
} catch (Exception e) {
Log.e(TAG, "无法跳转到电池优化设置界面", e);
}
}
}
}
}
package com.qialg.laki_icu_app;
import android.app.Application;
import com.qialg.laki_icu_app.utils.CrashHandler;
import com.qialg.laki_icu_app.utils.WatchDogManager;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import androidx.work.ExistingPeriodicWorkPolicy;
import java.util.concurrent.TimeUnit;
import com.qialg.laki_icu_app.worker.KeepAliveWorker;
public class MainApplication extends Application {
private static Application application;
@Override
public void onCreate() {
super.onCreate();
application = this;
// 初始化看门狗防卡死机制
WatchDogManager.start(this);
// 启动 WorkManager 周期性保活检查
startKeepAliveWorkManager();
// 初始化全局异常捕获
CrashHandler.getInstance().init(this);
}
/**
* 启动 WorkManager 周期性兜底保活任务
*/
private void startKeepAliveWorkManager() {
try {
// Android 系统的限制:PeriodicWorkRequest 的最小间隔时间是 15 分钟
PeriodicWorkRequest keepAliveRequest = new PeriodicWorkRequest.Builder(
KeepAliveWorker.class,
15, TimeUnit.MINUTES
).build();
// 使用 KEEP 策略:如果任务已经存在,则保持原样,不重复创建
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"KeepAliveWork",
ExistingPeriodicWorkPolicy.KEEP,
keepAliveRequest
);
} catch (Exception e) {
// 防止 WorkManager 初始化失败导致崩溃
e.printStackTrace();
}
}
public static Application getApplication() {
return application;
}
}
package com.qialg.laki_icu_app.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import com.qialg.laki_icu_app.MainActivity;
/**
* 监听设备开机广播,实现开机自启动 APP
*/
public class BootReceiver extends BroadcastReceiver {
private static final String TAG = "BootReceiver";
private static final String PREF_NAME = "AppConfig";
private static final String KEY_AUTO_START = "auto_start_enable";
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || intent.getAction() == null) {
return;
}
// 监听到系统开机完成广播
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
Log.d(TAG, "系统开机完成,检查自启动配置");
// 检查用户是否开启了自启动,默认开启
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
boolean isAutoStartEnabled = prefs.getBoolean(KEY_AUTO_START, true);
if (!isAutoStartEnabled) {
Log.d(TAG, "用户关闭了开机自启功能,取消拉起 APP");
return;
}
try {
// 启动主页
Intent launchIntent = new Intent(context, MainActivity.class);
// 必须添加 FLAG_ACTIVITY_NEW_TASK 标志,因为广播接收器没有任务栈
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(launchIntent);
Log.d(TAG, "APP 拉起成功");
} catch (Exception e) {
Log.e(TAG, "APP 拉起失败", e);
}
}
}
/**
* 设置是否允许开机自启动
*/
public static void setAutoStartEnabled(Context context, boolean enabled) {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
prefs.edit().putBoolean(KEY_AUTO_START, enabled).apply();
}
/**
* 获取当前开机自启动状态
*/
public static boolean isAutoStartEnabled(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
return prefs.getBoolean(KEY_AUTO_START, true); // 默认开启
}
}
package com.qialg.laki_icu_app.service;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import com.qialg.laki_icu_app.MainActivity;
import com.qialg.laki_icu_app.R;
/**
* 核心前台保活服务
* 目的:通过 startForeground 提升进程优先级,防止被系统因为内存不足而回收
*/
public class KeepAliveForegroundService extends Service {
private static final String TAG = "KeepAliveService";
private static final String CHANNEL_ID = "KeepAliveChannel";
private static final int NOTIFICATION_ID = 9527;
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "前台保活服务已创建");
startForegroundService();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "前台保活服务运行中...");
// START_STICKY:如果系统因为内存不足杀死了服务,内存恢复后,系统会尝试重新创建该服务
return START_STICKY;
}
private void startForegroundService() {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Android 8.0 及以上需要创建 NotificationChannel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"核心运行服务",
NotificationManager.IMPORTANCE_LOW // 使用 LOW 级别,避免频繁弹窗打扰用户,但足以保活
);
channel.setDescription("保证设备长连接的后台服务");
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
}
}
// 点击通知栏跳转回主界面
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(
this,
0,
intent,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT
);
// 构建通知
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("系统守护中")
.setContentText("保持设备实时在线接收指令")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(true) // 设置为常驻通知,用户无法滑动清除
.build();
// 启动前台服务
startForeground(NOTIFICATION_ID, notification);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
// 我们不需要绑定服务,所以返回 null
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "前台保活服务被销毁");
}
}
package com.qialg.laki_icu_app.utils;
import android.content.Context;
import android.util.Log;
import java.lang.Thread.UncaughtExceptionHandler;
public class CrashHandler implements UncaughtExceptionHandler {
private static CrashHandler instance;
private static Context context;
public static CrashHandler getInstance() {
if (instance == null) {
instance = new CrashHandler();
}
return instance;
}
public void init(Context ctx) {
context = ctx;
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 核心方法,当程序crash 会回调此方法, Throwable中存放这错误日志
*/
@Override
public void uncaughtException(Thread arg0, Throwable arg1) {
// TODO: 崩溃日志记录 — 后期确定方案后补充
// 可选方向:
// A. 写本地文件 (参考 qisuanfa: context.getExternalFilesDir + yyyy-MM-dd/errorLog.log)
// B. 接入第三方平台 (Bugly / Sentry / Firebase Crashlytics)
// C. 通过 Flutter MethodChannel 回传 Dart 层统一处理
// 待确定后再实现。
// TODO: 崩溃上报 — 同上,后期确定上报通道后补充
arg1.printStackTrace();
android.os.Process.killProcess(android.os.Process.myPid());
}
}
package com.qialg.laki_icu_app.utils;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.qialg.laki_icu_app.MainActivity;
/**
* 软件看门狗管理器
* 用于监控主线程(UI线程)是否发生严重卡死(ANR)。
* 当检测到主线程超过指定时间(如 10 秒)未响应时,将强制杀死当前进程,
* 配合 Android 系统的机制(或者通过显式启动 Activity 的方式)实现 APP 重启自愈。
*/
public class WatchDogManager {
private static final String TAG = "WatchDogManager";
private static final int TIMEOUT = 10000; // 10秒没响应判定为卡死
private static final int CHECK_INTERVAL = 3000; // 每3秒检查一次
private static volatile long lastResponseTime;
private static Handler mainHandler;
private static Thread watchDogThread;
private static boolean isRunning = false;
private static Context mContext;
/**
* 启动看门狗
* @param context 建议传入 Application Context 避免内存泄漏
*/
public static void start(Context context) {
if (isRunning) {
Log.w(TAG, "WatchDog is already running.");
return;
}
mContext = context.getApplicationContext();
mainHandler = new Handler(Looper.getMainLooper());
lastResponseTime = System.currentTimeMillis();
isRunning = true;
watchDogThread = new Thread(() -> {
Log.d(TAG, "WatchDog thread started.");
while (isRunning) {
// 1. 每隔一段时间向主线程投递一个更新响应时间的任务
mainHandler.post(() -> lastResponseTime = System.currentTimeMillis());
try {
Thread.sleep(CHECK_INTERVAL);
} catch (InterruptedException e) {
Log.w(TAG, "WatchDog thread interrupted.");
break;
}
// 2. 检查主线程是否已经超时未响应
if (System.currentTimeMillis() - lastResponseTime > TIMEOUT) {
Log.e(TAG, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
Log.e(TAG, "!!! 检测到主线程卡死超过10秒!准备强制重启 !!!");
Log.e(TAG, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
restartApp();
}
}
}, "WatchDog-Thread");
watchDogThread.setPriority(Thread.MAX_PRIORITY); // 提高看门狗线程优先级
watchDogThread.start();
}
/**
* 停止看门狗
*/
public static void stop() {
isRunning = false;
if (watchDogThread != null) {
watchDogThread.interrupt();
watchDogThread = null;
}
}
/**
* 强制重启 APP
*/
private static void restartApp() {
if (mContext != null) {
// 准备重启的 Intent,指向 MainActivity
Intent intent = new Intent(mContext, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivity(intent);
}
// 强制杀死当前进程,系统会尝试使用之前发出的 Intent 重新拉起应用
Log.e(TAG, "Killing process now...");
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
}
package com.qialg.laki_icu_app.worker;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import com.qialg.laki_icu_app.service.KeepAliveForegroundService;
import java.util.List;
/**
* 兜底保活 Worker
* 目的:通过 WorkManager 的周期性任务,定期检查前台服务是否还在运行,
* 如果被系统杀掉,则重新拉起。
*/
public class KeepAliveWorker extends Worker {
private static final String TAG = "KeepAliveWorker";
public KeepAliveWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
Log.d(TAG, "执行 WorkManager 周期性保活检查...");
Context context = getApplicationContext();
// 检查前台服务是否在运行
if (!isServiceRunning(context, KeepAliveForegroundService.class.getName())) {
Log.w(TAG, "检测到前台保活服务未运行,准备重新拉起...");
Intent serviceIntent = new Intent(context, KeepAliveForegroundService.class);
try {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent);
} else {
context.startService(serviceIntent);
}
Log.d(TAG, "前台保活服务拉起成功");
} catch (Exception e) {
Log.e(TAG, "拉起前台保活服务失败", e);
// 即使失败也返回 success,以便下次继续尝试,防止重试风暴
}
} else {
Log.d(TAG, "前台保活服务正常运行中");
}
// 返回成功,WorkManager 会根据设定的周期继续调度
return Result.success();
}
/**
* 判断某个服务是否正在运行
*/
private boolean isServiceRunning(Context context, String serviceClassName) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (activityManager != null) {
// 获取当前正在运行的服务列表 (注意:Android 8.0 之后这个方法被限制,只能获取自己应用的服务,刚好满足需求)
List<ActivityManager.RunningServiceInfo> runningServices = activityManager.getRunningServices(Integer.MAX_VALUE);
if (runningServices != null) {
for (ActivityManager.RunningServiceInfo serviceInfo : runningServices) {
if (serviceClassName.equals(serviceInfo.service.getClassName())) {
return true;
}
}
}
}
return false;
}
}
package com.qialg.laki_icu_app
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment