react native android中,app在后台如何持续获取位置信息,还有headless js中setTimeout没有按预期执行两个问题。问我有什么解决方法,当时我就懵逼了,这不是触及到我装X盲区了吗,况且我只是js菜鸡,不会android,难受!
package | version |
react | 18.2.0 |
react-native | 0.71.2 |
@react-native-community/geolocation | ^3.0.5 |
Headless JS文档
在React Native 练习时长 2 月半,踩坑总结文章中有涉及到使用Headless JS后台播放raw本地音频文件,那里是使用AppRegistry.startHeadlessTask(taskId, taskKey, data)api开始后台任务的,在官方文档中有提到在service中启动,但是步骤都不是非常详细
具体步骤,详见这篇文章-7.4章节app后台播放音频示例步骤,每一步都很详细,对着步骤来。
怎么突然又冒出来WorkManager了?没办法啊,按着文档那种方式来,Headless JS中代码不执行,下面步骤1代码中会提到
WokerManager是什么?
WorkManager is the recommended way to perform background tasks in Android. WorkManager can schedule one-time or periodic tasks in a simple, reliable way.
意思就是说,WorkManager是android中推荐执行后台任务的方式,可以执行一次性任务和定时任务。
java复制代码package com.your-app-name;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import android.content.Context;
import android.app.ActivityManager;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import java.util.Timer;
import java.util.TimerTask;
import java.util.List;
import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit;
public class BackgroundPosition extends ReactContextBaseJavaModule {
private static ReactApplicationContext reactContext;
private Timer timer = null;//计时器
private TimerTask task = null;
// private LocationManager locationManager;
// private LocationListener locationListener;
private PeriodicWorkRequest workRequest;
private static final String TAGERROR = "START_BACKGROUND_TASK_ERROR";
public BackgroundPosition(ReactApplicationContext context) {
super(context);
reactContext = context;
workRequest = new PeriodicWorkRequest.Builder(BackgroundPositionWorker.class, 15, TimeUnit.MINUTES).build();
}
@Override
public String getName() {
return "BackgroundPosition";
}
private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
@ReactMethod
public void addListener(String eventName) {
// Set up any upstream listeners or background tasks as necessary
}
@ReactMethod
public void removeListeners(Integer count) {
// Remove upstream listeners, stop unnecessary background tasks
}
private boolean isAppOnForeground(Context context) {
/**
我们需要先检查应用当前是否在前台运行,否则应用会崩溃。
http://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not
**/
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List appProcesses =
activityManager.getRunningAppProcesses();
if (appProcesses == null) {
return false;
}
final String packageName = context.getPackageName();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance ==
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
appProcess.processName.equals(packageName)) {
return true;
}
}
return false;
}
@ReactMethod
public void startBackgroudTask(Promise promise) {
if(timer!=null) {
timer.cancel();
timer=null;
}
timer = new Timer();
task = new TimerTask() {
@Override
public void run() {
try {
if(!isAppOnForeground(reactContext)) {
WritableMap params = Arguments.createMap();
params.putString("msg", "app已经在后台了,准备启动BackgroundPostionWorker");
sendEvent(reactContext, "backgroundTask", params);
// 上面讲到为什么要冒出来WorkManager,就是因为这里
// 直接在js中调用startBackgroudTask,执行reactContext.startService(service)
// 但是headless js中的任务不执行
// 所以这里通过WorkManager开始一个work任务,然后在work中启动startService
// Intent service = new Intent(reactContext, BackgroundPositionServices.class);
// // service.putExtra("backgroundTask", "123");
// // reactContext.startService(service);
// Bundle bundle = new Bundle();
// bundle.putString("foo", "bar");
// service.putExtras(bundle);
// // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// // reactContext.startForegroundService(service);
// // } else {
// // reactContext.startService(service);
// // }
// reactContext.startService(service);
// // HeadlessJsTaskService.acquireWakeLockNow(reactContext);
WorkManager.getInstance().enqueueUniquePeriodicWork("BackgroundPositionWorker", ExistingPeriodicWorkPolicy.KEEP, workRequest);
WritableMap params2 = Arguments.createMap();
params2.putString("msg", "BackgroundPostionWorker started");
promise.resolve(params2);
}
} catch (Exception e) {
e.printStackTrace();
promise.reject(TAGERROR, e);
}
}
};
// 3s后执行1次
timer.schedule(task, 3000);
}
@ReactMethod
public void stopBackgroudTask(Promise promise) {
if(timer!=null) {
timer.cancel();
timer=null;
}
// if(locationManager != null && locationListener != null) {
// locationManager.removeUpdates(locationListener);
// }
WritableMap params = Arguments.createMap();
params.putString("msg", "BackgroundPostionWorker stop successed");
WorkManager.getInstance().cancelUniqueWork("BackgroundPositionWorker");
promise.resolve(params);
}
}
java复制代码package com.your-app-name;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class BackgroundPositionPackage implements ReactPackage {
@Override
public List createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List createNativeModules(ReactApplicationContext reactContext) {
List modules = new ArrayList<>();
modules.add(new BackgroundPosition(reactContext));
return modules;
}
}
diff复制代码+ import com.your-app-name.BackgroundPositionPackage;
public class MainApplication extends Application implements ReactApplication {
...
@Override
protected List getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List packages = new PackageList(this).getPackages();
+ packages.add(new BackgroundPositionPackage());// <-- 添加这一行,类名替换成你的Package类的名字 name.
return packages;
}
...
}
java复制代码package com.your-app-name;
import android.content.Intent;
import android.os.Bundle;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.jstasks.HeadlessJsTaskRetryPolicy;
import com.facebook.react.jstasks.LinearCountingRetryPolicy;
import javax.annotation.Nullable;
public class BackgroundPositionServices extends HeadlessJsTaskService {
@Override
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
Bundle extras = intent.getExtras();
WritableMap data = extras != null ? Arguments.fromBundle(extras) : Arguments.createMap();
// https://github.com/eduardomota/smsgate/blob/803f775ae419db2aea63aeac5def15eb0ec28542/smsrelay2/android/app/src/main/java/com/smsrelay2/SmsEventService.java
LinearCountingRetryPolicy retryPolicy = new LinearCountingRetryPolicy(
3, // Max number of retry attempts
1000 // Delay between each retry attempt
);
// if (extras != null) {
// return new HeadlessJsTaskConfig(
// "BackgroundTask",
// Arguments.fromBundle(extras),
// 5000, // 任务的超时时间
// false // 可选参数:是否允许任务在前台运行,默认为false
// );
// }
return new HeadlessJsTaskConfig(
"BackgroundPosition",
data,
10000, // 任务的超时时间
false, // 可选参数:是否允许任务在前台运行,默认为false
retryPolicy
);
}
}
java复制代码package com.your-app-name;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import android.os.Bundle;
import android.content.Intent;
import android.content.Context;
public class BackgroundPositionWorker extends Worker {
public BackgroundPositionWorker(
@NonNull Context context,
@NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
Intent service = new Intent(getApplicationContext(), BackgroundPositionServices.class);
Bundle bundle = new Bundle();
bundle.putString("msg", "backgroundPosition start");
service.putExtras(bundle);
getApplicationContext().startService(service);
return Result.success();
}
}
xml复制代码...
+
+
+
+
+
+
...
+
diff复制代码import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
+ import {backgroundPosition} from './src/utils';
AppRegistry.registerComponent(appName, () => App);
+ AppRegistry.registerHeadlessTask('BackgroundPosition', () => backgroundPosition);
javascript复制代码import {InteractionManager, AppState, NativeModules} from 'react-native';
import Geolocation from '@react-native-community/geolocation';
import AsyncStorage from '@react-native-async-storage/async-storage';
import dayjs from 'dayjs';
const BackgroundPosition = NativeModules.BackgroundPosition;
function handleListenerAppState(watchId = 0) {
const subscription = AppState.addEventListener('change', nextAppState => {
console.log('nextAppState', nextAppState);
if (nextAppState === 'active') {
flag = false;
console.log('app回到前台,后台任务停止');
console.log('watchId:', watchId);
BackgroundPosition.stopBackgroudTask();
Geolocation.clearWatch(watchId);
subscription.remove();
}
});
}
export async function backgroundPosition(e) {
await AsyncStorage.clear();
const handle = InteractionManager.createInteractionHandle();
InteractionManager.runAfterInteractions(() => {
// ...需要长时间同步执行的任务...
// getCurrentPosition();
let watchPositionId = Geolocation.watchPosition(
async info => {
const {
coords: {latitude, longitude},
} = info;
console.log('当前位置:', latitude, longitude);
let locationListStr = await AsyncStorage.getItem('location');
let locationObj =
locationListStr === null ? {list: []} : JSON.parse(locationListStr);
locationObj.list.push({
latitude,
longitude,
date: dayjs().format('YYYY-MM-DD HH:mm:ss'),
});
await AsyncStorage.setItem('location', JSON.stringify(locationObj));
},
err => {
console.warn('获取定位失败==>', err);
},
{
interval: 5000, // 每5s更新一次位置
timeout: 10000, // 获取一个位置,10s钟超时
maximumAge: 15000, // 可能缓存位置的最长时间(以毫秒为单位)
enableHighAccuracy: true, // 使用GPS
distanceFilter: 1, // 返回一个新位置之前,与前一个位置的最小距离。设置为0表示不过滤位置。默认为100m。
// useSignificantChanges?: boolean; // 只有当设备检测到一个重要的距离已经被突破时,才会返回位置。默认为FALSE。
},
);
console.log('watchPositionId:', watchPositionId);
handleListenerAppState(watchPositionId);
});
InteractionManager.clearInteractionHandle(handle);
// return await Promise.resolve();
}
javascript复制代码const BackgroundPosition = NativeModules.BackgroundPosition;
// 申请定位权限
const handleAndroidPositionPermission = async () => {
try {
// https://juejin.cn/post/7058265721540706311
// android 11及以上版本申请权限时系统对话框不存在始终允许的选项,并且只能够在系统设置页面打开后台权限。
const granted1 = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
]);
const granted2 = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION,
);
if (
granted1['android.permission.ACCESS_FINE_LOCATION'] ===
PermissionsAndroid.RESULTS.GRANTED &&
granted1['android.permission.ACCESS_COARSE_LOCATION'] ===
PermissionsAndroid.RESULTS.GRANTED &&
granted2 === PermissionsAndroid.RESULTS.GRANTED
) {
console.log('可以定位了');
return Promise.resolve();
} else {
console.log('拒绝获取定位权限');
Toast.fail({
content: "拒绝获取定位权限",
duration: 2,
stackable: true,
});
return Promise.reject({msg: '拒绝获取定位权限'});
}
} catch (error) {
console.warn(error);
return Promise.reject();
}
};
const handleBackgroundTask = async type => {
// 点击按钮后,将app切换到后台,即可执行后台任务,
// 或者这里通过AppState监听,app在后台,自动执行后台任务
try {
if (type === 'start') {
// 申请定位权限
await handleAndroidPositionPermission();
// 开始后台任务
await BackgroundPosition.startBackgroudTask();
} else {
// 结束后台任务
await BackgroundPosition.stopBackgroudTask();
}
} catch (error) {
console.error('handleBackgroundTask error', error);
}
};
测试机型小米10,android13
ISSUE里搜了下,也没什么关键信息,甚至显示有人已经提交过PR了
javascript复制代码const sleep = function (startTime, delay) {
return () => {
let cur = new Date().getTime();
while (cur < startTime + delay) {
cur = new Date().getTime();
}
};
};
function fun() {
// ...
sleep(new Date().getTime(), 3000)(); // 3S后递归执行下面fun方法
fun();
}
原文链接:https://juejin.cn/post/7254811625055944762
页面更新:2024-04-01
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号