Skip to content

Commit

Permalink
Timing: Fixes timer when app get into background (#24649)
Browse files Browse the repository at this point in the history
Summary:
Related #23674, in that PR, we imported background timer support, but it's not sufficient, I think the reason that works is because it enable the `Background Modes` and do some background tasks, for the users who don't enable it, timer would pause immediately before goes into background.

To fix it, we can mark a background task when goes into background, it can keep app active for minutes, try best to support timing when in background.

cc. cpojer .

## Changelog

[iOS] [Fixed] - Timing: Fixes timer when app get into background
Pull Request resolved: #24649

Differential Revision: D15554451

Pulled By: cpojer

fbshipit-source-id: a33f7afe6b63d1a4fefcb7098459aee0c09145da
  • Loading branch information
zhongwuzw authored and facebook-github-bot committed May 30, 2019
1 parent e8037cb commit 3382984
Showing 1 changed file with 30 additions and 0 deletions.
30 changes: 30 additions & 0 deletions React/Modules/RCTTiming.m
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ @implementation RCTTiming
NSTimer *_sleepTimer;
BOOL _sendIdleEvents;
BOOL _inBackground;
UIBackgroundTaskIdentifier _backgroundTaskIdentifier;
}

@synthesize bridge = _bridge;
Expand All @@ -112,6 +113,7 @@ - (void)setBridge:(RCTBridge *)bridge
_paused = YES;
_timers = [NSMutableDictionary new];
_inBackground = NO;
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;

for (NSString *name in @[UIApplicationWillResignActiveNotification,
UIApplicationDidEnterBackgroundNotification,
Expand All @@ -135,10 +137,35 @@ - (void)setBridge:(RCTBridge *)bridge

- (void)dealloc
{
[self markEndOfBackgroundTaskIfNeeded];
[_sleepTimer invalidate];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)markStartOfBackgroundTaskIfNeeded
{
if (_backgroundTaskIdentifier == UIBackgroundTaskInvalid) {
__weak typeof(self) weakSelf = self;
// Marks the beginning of a new long-running background task. We can run the timer in the background.
_backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
// Mark the end of background task
[strongSelf markEndOfBackgroundTaskIfNeeded];
}];
}
}

- (void)markEndOfBackgroundTaskIfNeeded
{
if (_backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTaskIdentifier];
_backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
}

- (dispatch_queue_t)methodQueue
{
return RCTJSThread;
Expand All @@ -163,6 +190,7 @@ - (void)appDidMoveToBackground

- (void)appDidMoveToForeground
{
[self markEndOfBackgroundTaskIfNeeded];
_inBackground = NO;
[self startTimers];
}
Expand Down Expand Up @@ -260,6 +288,7 @@ - (void)didUpdateFrame:(RCTFrameUpdate *)update
}
if (_inBackground) {
if (timerCount) {
[self markStartOfBackgroundTaskIfNeeded];
[self scheduleSleepTimer:nextScheduledTarget];
}
} else if (!_sendIdleEvents && timersToCall.count == 0) {
Expand Down Expand Up @@ -338,6 +367,7 @@ - (void)timerDidFire
}

if (_inBackground) {
[self markStartOfBackgroundTaskIfNeeded];
[self scheduleSleepTimer:timer.target];
} else if (_paused) {
if ([timer.target timeIntervalSinceNow] > kMinimumSleepInterval) {
Expand Down

3 comments on commit 3382984

@radko93
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhongwuzw hey, please take a look #26696 (comment)

@zhongwuzw
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@radko93 Comment below you revert PR, please check that~

@bashen1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhongwuzw this PR will cause the app to exit when it is in the background
Just steps

  1. react-native init demo
  2. modfiy App.js just like this
/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow
 */

import React, {PureComponent} from 'react';
import {
    SafeAreaView,
    ScrollView,
    View,
    Text,
    StatusBar,
} from 'react-native';

class App extends PureComponent {
    constructor(props) {
        super(props);
        this.state = {
            num: 0
        }
    }

    componentDidMount(): * {
        this.timer = setInterval(() => {
            this.setState({
                num: this.state.num + 1
            })
        }, 1000)
    }

    render(): boolean | number | string | React$Element<*> | React$Portal | Iterable | null {
        return (
            <>
                <StatusBar barStyle="dark-content"/>
                <SafeAreaView>
                    <ScrollView
                        contentInsetAdjustmentBehavior="automatic"
                       >
                      <View>
                        <Text>{this.state.num}</Text>
                      </View>
                    </ScrollView>
                </SafeAreaView>
            </>
        )
    }
}

export default App;
  1. Keep the application background. Device lock screen, then unlock for a few minutes, app crash,sometimes will show redscreen「Timed out waiting for modules to be invalidated」

#26995

Please sign in to comment.