Skip to content

Commit

Permalink
Move initialization steps to background (#681)
Browse files Browse the repository at this point in the history
* feat: create background task manager and rewrite status requester #676

* feat: move fetching of user agent and consents to background #676

* feat: add consents reading to background tasks #676

* fix: existing tests #676

* fix: sdk initialization timing #676

* test: add new unit tests #676

* feat: replace async mechanism to ExecutorsService #676

* feat: fix existing unit tests #676

* feat: add one more test #676

* test: cover background tasks with unit tests #676

* refactor: move logic #676
  • Loading branch information
ValentinPostindustria authored Sep 8, 2023
1 parent 017fb31 commit 798f566
Show file tree
Hide file tree
Showing 16 changed files with 663 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.prebid.mobile.core.BuildConfig;
import org.prebid.mobile.rendering.listeners.SdkInitializationListener;
import org.prebid.mobile.rendering.mraid.MraidEnv;
import org.prebid.mobile.rendering.sdk.InitializationNotifier;
import org.prebid.mobile.rendering.sdk.PrebidContextHolder;
import org.prebid.mobile.rendering.sdk.SdkInitializer;

Expand Down Expand Up @@ -302,7 +303,7 @@ public static void assignNativeAssetID(boolean assignNativeAssetID) {
* Return 'true' if Prebid Rendering SDK is initialized completely
*/
public static boolean isSdkInitialized() {
return PrebidContextHolder.getContext() != null;
return PrebidContextHolder.getContext() != null && InitializationNotifier.wereTasksCompletedSuccessfully();
}

public static LogLevel getLogLevel() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package org.prebid.mobile.rendering.sdk;

import android.os.Handler;
import android.os.Looper;

import androidx.annotation.Nullable;

import org.jetbrains.annotations.NotNull;
import org.prebid.mobile.LogUtil;
import org.prebid.mobile.PrebidMobile;
import org.prebid.mobile.api.data.InitializationStatus;
import org.prebid.mobile.api.exceptions.InitError;
import org.prebid.mobile.rendering.listeners.SdkInitializationListener;


public class InitializationNotifier {

private static final String TAG = "InitializationNotifier";

private static boolean tasksCompletedSuccessfully = false;
private static boolean initializationInProgress = false;

@Nullable
private SdkInitializationListener listener;

public InitializationNotifier(@Nullable SdkInitializationListener listener) {
this.listener = listener;
initializationInProgress = true;
}

/**
* @param statusRequesterError must be null, if status requester completed successfully.
*/
public void initializationCompleted(@Nullable String statusRequesterError) {
postOnMainThread(() -> {
boolean statusRequestSuccessful = statusRequesterError == null;
if (statusRequestSuccessful) {
LogUtil.debug(TAG, "Prebid SDK " + PrebidMobile.SDK_VERSION + " initialized");

if (listener != null) {
listener.onInitializationComplete(InitializationStatus.SUCCEEDED);

listener.onSdkInit();
}
} else {
LogUtil.error(TAG, statusRequesterError);

if (listener != null) {
InitializationStatus serverStatusWarning = InitializationStatus.SERVER_STATUS_WARNING;
serverStatusWarning.setDescription(statusRequesterError);
listener.onInitializationComplete(serverStatusWarning);

listener.onSdkFailedToInit(new InitError(statusRequesterError));
}
}

tasksCompletedSuccessfully = true;
initializationInProgress = false;
listener = null;
});
}

public void initializationFailed(@NotNull String error) {
postOnMainThread(() -> {
LogUtil.error(error);

if (listener != null) {
InitializationStatus status = InitializationStatus.FAILED;
status.setDescription(error);
listener.onInitializationComplete(status);

listener.onSdkFailedToInit(new InitError(error));
}

PrebidContextHolder.clearContext();
listener = null;
initializationInProgress = false;
});
}


private static void postOnMainThread(Runnable runnable) {
new Handler(Looper.getMainLooper()).post(runnable);
}

public static boolean wereTasksCompletedSuccessfully() {
return tasksCompletedSuccessfully;
}

public static boolean isInitializationInProgress() {
return initializationInProgress;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import org.prebid.mobile.rendering.sdk.scripts.JsScriptData;

import java.util.concurrent.atomic.AtomicBoolean;

/**
* Downloader and fetcher for JS scripts needed for the Prebid SDK (omsdk.js, mraid.js).
* Top level class for working with JS scripts.
Expand Down Expand Up @@ -49,8 +51,12 @@ public static JSLibraryManager getInstance(Context context) {

public boolean checkIfScriptsDownloadedAndStartDownloadingIfNot() {
if (scriptsDownloader.areScriptsDownloadedAlready()) {
initScriptVariables();
return true;
if (!OMSDKscirpt.isEmpty() && !MRAIDscript.isEmpty()) {
return true;
}

startScriptReadingTask();
return false;
}

scriptsDownloader.downloadScripts(
Expand All @@ -59,13 +65,15 @@ public boolean checkIfScriptsDownloadedAndStartDownloadingIfNot() {
return false;
}

public void initScriptVariables() {
public void startScriptReadingTask() {
if (scriptsDownloader.areScriptsDownloadedAlready()) {
if (OMSDKscirpt.isEmpty()) {
OMSDKscirpt = scriptsDownloader.readFile(JsScriptData.openMeasurementData);
}
if (MRAIDscript.isEmpty()) {
MRAIDscript = scriptsDownloader.readFile(JsScriptData.mraidData);
if (OMSDKscirpt.isEmpty() || MRAIDscript.isEmpty()) {

boolean isNotRunning = BackgroundScriptReader.alreadyRunning.compareAndSet(false, true);
if (isNotRunning) {
Thread thread = new Thread(new BackgroundScriptReader(scriptsDownloader, this));
thread.start();
}
}
}
}
Expand All @@ -78,4 +86,31 @@ public String getOMSDKScript() {
return OMSDKscirpt;
}

private static class BackgroundScriptReader implements Runnable {

private static final AtomicBoolean alreadyRunning = new AtomicBoolean(false);

private final JsScriptsDownloader scriptsDownloader;
private final JSLibraryManager jsLibraryManager;

public BackgroundScriptReader(
JsScriptsDownloader scriptsDownloader,
JSLibraryManager jsLibraryManager
) {
this.scriptsDownloader = scriptsDownloader;
this.jsLibraryManager = jsLibraryManager;
}

@Override
public void run() {
String openMeasurementScript = scriptsDownloader.readFile(JsScriptData.openMeasurementData);
String mraidScript = scriptsDownloader.readFile(JsScriptData.mraidData);

jsLibraryManager.OMSDKscirpt = openMeasurementScript;
jsLibraryManager.MRAIDscript = mraidScript;
alreadyRunning.set(false);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public void onFileDownloaded(String string) {

Context context = PrebidContextHolder.getContext();
if (context != null) {
JSLibraryManager.getInstance(context).initScriptVariables();
JSLibraryManager.getInstance(context).startScriptReadingTask();
}

inProgressKeys.remove(path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,41 @@

import android.app.Application;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import org.prebid.mobile.LogUtil;
import org.prebid.mobile.PrebidMobile;
import org.prebid.mobile.api.data.InitializationStatus;
import org.prebid.mobile.api.exceptions.InitError;
import org.prebid.mobile.api.rendering.PrebidRenderer;
import org.prebid.mobile.api.rendering.pluginrenderer.PrebidMobilePluginRegister;
import org.prebid.mobile.rendering.listeners.SdkInitializationListener;
import org.prebid.mobile.rendering.session.manager.OmAdSessionManager;
import org.prebid.mobile.rendering.utils.helpers.AppInfoManager;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class SdkInitializer {

private static final String TAG = SdkInitializer.class.getSimpleName();

public static void init(
@Nullable Context context,
@Nullable SdkInitializationListener listener
@Nullable Context context,
@Nullable SdkInitializationListener listener
) {
if (PrebidContextHolder.getContext() != null) {
if (PrebidMobile.isSdkInitialized() || InitializationNotifier.isInitializationInProgress()) {
return;
}

InitializationNotifier initializationNotifier = new InitializationNotifier(listener);

Context applicationContext = getApplicationContext(context);
if (applicationContext == null) {
onInitializationFailed("Context must be not null!", listener);
initializationNotifier.initializationFailed("Context must be not null!");
return;
}

Expand All @@ -43,11 +47,11 @@ public static void init(
LogUtil.setLogLevel(PrebidMobile.getLogLevel().getValue());
}

// todo using internal api until pluginrenderer feature is released
// PrebidMobile.registerPluginRenderer(new PrebidRenderer());
PrebidMobilePluginRegister.getInstance().registerPlugin(new PrebidRenderer());

try {
// todo using internal api until pluginrenderer feature is released
// PrebidMobile.registerPluginRenderer(new PrebidRenderer());
PrebidMobilePluginRegister.getInstance().registerPlugin(new PrebidRenderer());

AppInfoManager.init(applicationContext);

OmAdSessionManager.activateOmSdk(applicationContext);
Expand All @@ -56,16 +60,43 @@ public static void init(

JSLibraryManager.getInstance(applicationContext).checkIfScriptsDownloadedAndStartDownloadingIfNot();
} catch (Throwable throwable) {
onInitializationFailed("Exception during initialization: " + throwable.getMessage() + "\n" + Log.getStackTraceString(throwable), listener);
initializationNotifier.initializationFailed("Exception during initialization: " + throwable.getMessage() + "\n" + Log.getStackTraceString(throwable));
return;
}

StatusRequester.makeRequest(listener);
new Thread(() -> runBackgroundTasks(
initializationNotifier,
Executors.newFixedThreadPool(2))
).start();
}

@VisibleForTesting
public static void runBackgroundTasks(
InitializationNotifier initializationNotifier,
ExecutorService executor
) {
try {
Future<String> statusRequesterResult = executor.submit(new StatusRequester());
executor.execute(new UserConsentFetcherTask());
executor.execute(new UserAgentFetcherTask());
executor.shutdown();

boolean terminatedByTimeout = !executor.awaitTermination(10, TimeUnit.SECONDS);
if (terminatedByTimeout) {
initializationNotifier.initializationFailed("Terminated by timeout.");
return;
}

String statusRequesterError = statusRequesterResult.get();
initializationNotifier.initializationCompleted(statusRequesterError);
} catch (Exception exception) {
initializationNotifier.initializationFailed("Exception during initialization: " + Log.getStackTraceString(exception));
}
}

@Nullable
private static Context getApplicationContext(
@Nullable Context context
@Nullable Context context
) {
if (context instanceof Application) {
return context;
Expand All @@ -75,25 +106,13 @@ private static Context getApplicationContext(
return null;
}

private static void onInitializationFailed(
String error,
@Nullable SdkInitializationListener listener
) {
LogUtil.error(error);
if (listener != null) {
postOnMainThread(() -> {
InitializationStatus status = InitializationStatus.FAILED;
status.setDescription(error);
listener.onInitializationComplete(status);

listener.onSdkFailedToInit(new InitError(error));
});
protected static class UserConsentFetcherTask implements Runnable {

@Override
public void run() {
ManagersResolver.getInstance().getUserConsentManager().initConsentValues();
}
PrebidContextHolder.clearContext();
}

private static void postOnMainThread(Runnable runnable) {
new Handler(Looper.getMainLooper()).post(runnable);
}

}
Loading

0 comments on commit 798f566

Please sign in to comment.