Skip to content

Commit

Permalink
Merge branch 'release-4.2.6'
Browse files Browse the repository at this point in the history
  • Loading branch information
christocracy committed Nov 29, 2024
2 parents ccc1a1e + 340ea10 commit a40287b
Show file tree
Hide file tree
Showing 48 changed files with 224 additions and 153 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# CHANGELOG

## 4.2.6 — 2024-11-29
* [Android] Implement support for new "Bridgeless Architecture".

## 4.2.5 — 2024-04-22
* [iOS] Code-sign `TSBackgroundFetch.xcframework` with new Apple Organization (*9224-2932 Quebec Inc*) certificate.

Expand Down
5 changes: 5 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ android {
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION)
targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)
consumerProguardFiles 'proguard-rules.pro'
versionCode 1
versionName "1.0"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

repositories{
Expand Down
Binary file not shown.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8179b611fd9f6fb07357b9b7dfde781b
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eb0a4f6a810f05eec51d5b92b846a70693c52be6
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7678cef9a5ff3942720e2839aa82473a81e06ba5e0ca672cfaa2100a98b66468
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7fa2f89c2a79cf5ff6070e3986914fff7f46d91dfe40ab21a70875556f429bffb99b085c59b10951063cb717896f94fb7a478611607fb544eb838a87470bea1c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.transistorsoft</groupId>
<artifactId>tsbackgroundfetch</artifactId>
<version>1.0.2</version>
<version>1.0.4</version>
<packaging>aar</packaging>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d17405d8d49a051f96c9e960f7059109
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1fd4138746b94af71350424a33c0160c154812d9
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3702c65bb77f6c0741f6005e770968fb015877378f3a1c8b8e7c31f47da65aa1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
820e817adf42044cbab55e028a9d142741ae2b211c829e145813ad720320d92f118136f73835f096123168a18fbabf5c056f3e57fe0f95b403a682ce73854dd7
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
<groupId>com.transistorsoft</groupId>
<artifactId>tsbackgroundfetch</artifactId>
<versioning>
<latest>1.0.2</latest>
<release>1.0.2</release>
<latest>1.0.4</latest>
<release>1.0.4</release>
<versions>
<version>1.0.1</version>
<version>1.0.2</version>
<version>1.0.3</version>
<version>1.0.4</version>
</versions>
<lastUpdated>20230820223945</lastUpdated>
<lastUpdated>20241128152535</lastUpdated>
</versioning>
</metadata>
16 changes: 16 additions & 0 deletions android/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
# [react-native-background-fetch]
-keep class com.transistorsoft.rnbackgroundfetch.HeadlessTask { *; }

# for react-native Headless on new architecture
-keep class com.facebook.react.defaults.DefaultNewArchitectureEntryPoint {
public <methods>;
}
-keep class com.facebook.react.ReactApplication {
public <methods>;
}
-keep class com.facebook.react.ReactHost {
public <methods>;
}
-keep class * extends com.facebook.react.ReactHost {
public <methods>;
}
-keep class com.facebook.react.fabric.** { *; }

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
import android.os.Handler;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.facebook.infer.annotation.Assertions;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceEventListener;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.bridge.ReactContext;
Expand All @@ -13,50 +18,34 @@
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import com.facebook.react.jstasks.HeadlessJsTaskContext;
import com.facebook.react.jstasks.HeadlessJsTaskEventListener;

import com.transistorsoft.tsbackgroundfetch.BGTask;
import com.transistorsoft.tsbackgroundfetch.BackgroundFetch;
import com.facebook.react.common.LifecycleState;

import java.lang.reflect.Method;

/**
* Created by chris on 2018-01-17.
*/

public class HeadlessTask implements HeadlessJsTaskEventListener {
private static String HEADLESS_TASK_NAME = "BackgroundFetch";
private static Handler mHandler = new Handler();
private ReactNativeHost mReactNativeHost;
private HeadlessJsTaskContext mActiveTaskContext;
public class HeadlessTask {
private static final String HEADLESS_TASK_NAME = "BackgroundFetch";
private static final Handler mHandler = new Handler();

private final BGTask mBGTask;
public HeadlessTask(Context context, BGTask task) {
try {
ReactApplication reactApplication = ((ReactApplication) context.getApplicationContext());
mReactNativeHost = reactApplication.getReactNativeHost();
} catch (AssertionError | ClassCastException e) {
Log.e(BackgroundFetch.TAG, "Failed to fetch ReactApplication. Task ignored.");
return; // <-- Do nothing. Just return
}
mBGTask = task;
WritableMap clientEvent = new WritableNativeMap();
clientEvent.putString("taskId", task.getTaskId());
clientEvent.putBoolean("timeout", task.getTimedOut());
HeadlessJsTaskConfig config = new HeadlessJsTaskConfig(HEADLESS_TASK_NAME, clientEvent, 30000);
startTask(config);
}

public void finish() {
if (mActiveTaskContext != null) {
mActiveTaskContext.removeTaskEventListener(this);
try {
startTask(config, context);
} catch (AssertionError e) {
Log.d(BackgroundFetch.TAG, "[HeadlessTask] Failed invoke HeadlessTask: " + e.getMessage());
}
}
@Override
public void onHeadlessJsTaskStart(int taskId) {
Log.d(BackgroundFetch.TAG,"onHeadlessJsTaskStart: " + taskId);
}
@Override
public void onHeadlessJsTaskFinish(int taskId) {
Log.d(BackgroundFetch.TAG, "onHeadlessJsTaskFinish: " + taskId);
mActiveTaskContext.removeTaskEventListener(this);
}

/**
* Start a task. This method handles starting a new React instance if required.
Expand All @@ -65,27 +54,13 @@ public void onHeadlessJsTaskFinish(int taskId) {
*
* @param taskConfig describes what task to start and the parameters to pass to it
*/
protected void startTask(final HeadlessJsTaskConfig taskConfig) {
protected void startTask(final HeadlessJsTaskConfig taskConfig, Context context) {
UiThreadUtil.assertOnUiThread();
final ReactInstanceManager reactInstanceManager = mReactNativeHost.getReactInstanceManager();
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();

ReactContext reactContext = getReactContext(context);

if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(final ReactContext reactContext) {
// Hack to fix unknown problem executing asynchronous BackgroundTask when ReactContext is created *first time*. Fixed by adding short delay before #invokeStartTask
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
invokeStartTask(reactContext, taskConfig);
}
}, 500);
reactInstanceManager.removeReactInstanceEventListener(this);
}
});
if (!reactInstanceManager.hasStartedCreatingInitialContext()) {
reactInstanceManager.createReactContextInBackground();
}
createReactContextAndScheduleTask(taskConfig, context);
} else {
invokeStartTask(reactContext, taskConfig);
}
Expand All @@ -96,24 +71,107 @@ private void invokeStartTask(ReactContext reactContext, final HeadlessJsTaskConf
return;
}
final HeadlessJsTaskContext headlessJsTaskContext = HeadlessJsTaskContext.getInstance(reactContext);
headlessJsTaskContext.addTaskEventListener(this);
mActiveTaskContext = headlessJsTaskContext;

UiThreadUtil.runOnUiThread(() -> {
try {
final int taskId = headlessJsTaskContext.startTask(taskConfig);
Log.d(BackgroundFetch.TAG, "[HeadlessTask] start HeadlessJsTask: " + taskId);
// Add a BGTask.finish(taskId) listener. This is executed when the user runs BackgroundFetch.finish(taskId).
// We use this to finish the RN headless task.
mBGTask.setCompletionHandler(() -> {
Log.d(BackgroundFetch.TAG, "[HeadlessTask] end HeadlessJsTask: " + taskId);
headlessJsTaskContext.finishTask(taskId);
});
} catch (IllegalStateException exception) {
Log.e(BackgroundFetch.TAG, "[HeadlessTask] task attempted to run in the foreground. Task ignored.");
}
});
}

private ReactNativeHost getReactNativeHost(Context context) {
return ((ReactApplication) context.getApplicationContext()).getReactNativeHost();
}

/**
* Get the {ReactHost} used by this app. ure and returns null if not.
*/
private @Nullable Object getReactHost(Context context) {
context = context.getApplicationContext();
try {
UiThreadUtil.runOnUiThread(new Runnable() {
Method getReactHost = context.getClass().getMethod("getReactHost");
return getReactHost.invoke(context);
// Original non-reflection return:
//return ((ReactApplication) context.getApplicationContext()).getReactHost();
} catch (Exception e) {
Log.d(BackgroundFetch.TAG, "[HeadlessTask] Reflection error ReactHost: " + e);
return null;
}
}

private ReactContext getReactContext(Context context) {
if (isBridglessArchitectureEnabled()) {
Object reactHost = getReactHost(context);
Assertions.assertNotNull(reactHost, "getReactHost() is null in New Architecture");
try {
Method getCurrentReactContext = reactHost.getClass().getMethod("getCurrentReactContext");
return (ReactContext) getCurrentReactContext.invoke(reactHost);
} catch (Exception e) {
Log.e(BackgroundFetch.TAG, "[HeadlessTask] Reflection error getCurrentReactContext: " + e);
}
}
final ReactInstanceManager reactInstanceManager = getReactNativeHost(context).getReactInstanceManager();
return reactInstanceManager.getCurrentReactContext();
}

private void createReactContextAndScheduleTask(final HeadlessJsTaskConfig taskConfig, Context context) {
Log.d(BackgroundFetch.TAG, "[HeadlessTask] initializing ReactContext");

if (isBridglessArchitectureEnabled()) { // new arch
final Object reactHost = getReactHost(context);

ReactInstanceEventListener callback = new ReactInstanceEventListener() {
@Override
public void run() {
public void onReactContextInitialized(@NonNull ReactContext reactContext) {
mHandler.postDelayed(() -> invokeStartTask(reactContext, taskConfig), 500);
try {
int taskId = headlessJsTaskContext.startTask(taskConfig);
} catch (IllegalStateException exception) {
Log.e(BackgroundFetch.TAG, "Headless task attempted to run in the foreground. Task ignored.");
return; // <-- Do nothing. Just return
Method removeReactInstanceEventListener = reactHost.getClass().getMethod("removeReactInstanceEventListener", ReactInstanceEventListener.class);
removeReactInstanceEventListener.invoke(reactHost, this);
} catch (Exception e) {
Log.e(BackgroundFetch.TAG, "[HeadlessTask] reflection error removeReactInstanceEventListener" + e);
}
}
};

try {
Method addReactInstanceEventListener = reactHost.getClass().getMethod("addReactInstanceEventListener", ReactInstanceEventListener.class);
addReactInstanceEventListener.invoke(reactHost, callback);
Method startReactHost = reactHost.getClass().getMethod("start");
startReactHost.invoke(reactHost);
} catch (Exception e) {
Log.e(BackgroundFetch.TAG, "[HeadlessTask] reflection error addReactInstanceEventListener: " + e);
}
} else { // old arch
final ReactInstanceManager reactInstanceManager = getReactNativeHost(context).getReactInstanceManager();
reactInstanceManager.addReactInstanceEventListener(new ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(@NonNull ReactContext reactContext) {
mHandler.postDelayed(() -> invokeStartTask(reactContext, taskConfig), 500);
reactInstanceManager.removeReactInstanceEventListener(this);
}
});
} catch (IllegalStateException exception) {
Log.e(BackgroundFetch.TAG, "Headless task attempted to run in the foreground. Task ignored.");
return; // <-- Do nothing. Just return
reactInstanceManager.createReactContextInBackground();
}
}

// Returns true if the app has enabled bridgeless mode. Thanks to @mikehardy for this block.
private boolean isBridglessArchitectureEnabled() {
try {
Class<?> entryPoint = Class.forName("com.facebook.react.defaults.DefaultNewArchitectureEntryPoint");
Method bridgelessEnabled = entryPoint.getMethod("getBridgelessEnabled");
Object result = bridgelessEnabled.invoke(null);
return (result == Boolean.TRUE);
} catch (Exception e) {
return false;
}
}
}
12 changes: 0 additions & 12 deletions docs/INSTALL-AUTO-ANDROID.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,6 @@ allprojects {
}
```

## Configure __`proguard-rules.pro`__

If you're using __`minifyEnabled true`__ with your Android release build, the plugin's __`HeadlessTask`__ class will be mistakenly *removed* and you will have [this crash](https://github.com/transistorsoft/react-native-background-fetch/issues/261).

1. Edit `android/app/proguard-rules.pro`.
2. Add the following rule:

```bash
# [react-native-background-fetch]
-keep class com.transistorsoft.rnbackgroundfetch.HeadlessTask { *; }
```

## Precise event-scheduling with `forceAlarmManager: true`:

**Only** If you wish to use precise scheduling of events with __`forceAlarmManager: true`__, *Android 14 (SDK 34)*, has restricted usage of ["`AlarmManager` exact alarms"](https://developer.android.com/about/versions/14/changes/schedule-exact-alarms). To continue using precise timing of events with *Android 14*, you can manually add this permission to your __`AndroidManifest`__. Otherwise, the plugin will gracefully fall-back to "*in-exact* `AlarmManager` scheduling":
Expand Down
Loading

0 comments on commit a40287b

Please sign in to comment.