Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Virtual
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h
FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm
Expand Down
101 changes: 100 additions & 1 deletion lib/ui/plugins/callback_cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,39 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/lib/ui/plugins/callback_cache.h"
#include <fstream>
#include <iterator>

#include "flutter/fml/build_config.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/paths.h"
#include "flutter/lib/ui/plugins/callback_cache.h"
#include "third_party/rapidjson/rapidjson/document.h"
#include "third_party/rapidjson/rapidjson/stringbuffer.h"
#include "third_party/rapidjson/rapidjson/writer.h"
#include "third_party/tonic/converter/dart_converter.h"

using rapidjson::Document;
using rapidjson::StringBuffer;
using rapidjson::Writer;
using tonic::ToDart;

namespace blink {

static const char* kHandleKey = "handle";
static const char* kRepresentationKey = "representation";
static const char* kNameKey = "name";
static const char* kClassNameKey = "class_name";
static const char* kLibraryPathKey = "library_path";
static const char* kCacheName = "flutter_callback_cache.json";
std::mutex DartCallbackCache::mutex_;
std::string DartCallbackCache::cache_path_;
std::map<int64_t, DartCallbackRepresentation> DartCallbackCache::cache_;

void DartCallbackCache::SetCachePath(const std::string& path) {
cache_path_ = fml::paths::JoinPaths({path, kCacheName});
}

Dart_Handle DartCallbackCache::GetCallback(int64_t handle) {
std::lock_guard<std::mutex> lock(mutex_);
auto iterator = cache_.find(handle);
Expand All @@ -34,6 +56,7 @@ int64_t DartCallbackCache::GetCallbackHandle(const std::string& name,

if (cache_.find(hash) == cache_.end()) {
cache_[hash] = {name, class_name, library_path};
SaveCacheToDisk();
Copy link
Member

Choose a reason for hiding this comment

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

What is the lifecycle of the cache file? Is the cache file or any of its entries ever deleted?

Would anything within the cache become stale if the application's code is updated but the cache is not purged?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently the cache is persistent and entries aren't deleted, although we could verify all the callbacks in the cache actually exist when we restore and remove the stale entries.

}
return hash;
}
Expand All @@ -48,6 +71,82 @@ DartCallbackCache::GetCallbackInformation(int64_t handle) {
return nullptr;
}

void DartCallbackCache::SaveCacheToDisk() {
// Cache JSON format
// [
// {
// "hash": 42,
// "representation": {
// "name": "...",
// "class_name": "...",
// "library_path": "..."
// }
// },
// {
// ...
// }
// ]
StringBuffer s;
Writer<StringBuffer> writer(s);
writer.StartArray();
for (auto iterator = cache_.begin(); iterator != cache_.end(); ++iterator) {
int64_t hash = iterator->first;
DartCallbackRepresentation cb = iterator->second;
writer.StartObject();
writer.Key(kHandleKey);
writer.Int64(hash);
writer.Key(kRepresentationKey);
writer.StartObject();
writer.Key(kNameKey);
writer.String(cb.name.c_str());
writer.Key(kClassNameKey);
writer.String(cb.class_name.c_str());
writer.Key(kLibraryPathKey);
writer.String(cb.library_path.c_str());
writer.EndObject();
writer.EndObject();
}
writer.EndArray();

std::ofstream output(cache_path_);
output << s.GetString();
output.close();
}

void DartCallbackCache::LoadCacheFromDisk() {
std::lock_guard<std::mutex> lock(mutex_);

// Don't reload the cache if it's already populated.
if (!cache_.empty()) {
return;
}
std::ifstream input(cache_path_);
if (!input) {
return;
}
std::string cache_contents{std::istreambuf_iterator<char>(input),
std::istreambuf_iterator<char>()};
Document d;
d.Parse(cache_contents.c_str());
if (d.HasParseError() || !d.IsArray()) {
FML_LOG(WARNING) << "Could not parse callback cache, aborting restore";
// TODO(bkonyi): log and bail (delete cache?)
return;
}
const auto entries = d.GetArray();
for (auto it = entries.begin(); it != entries.end(); ++it) {
const auto root_obj = it->GetObject();
const auto representation = root_obj[kRepresentationKey].GetObject();

const int64_t hash = root_obj[kHandleKey].GetInt64();
DartCallbackRepresentation cb;
cb.name = representation[kNameKey].GetString();
cb.class_name = representation[kClassNameKey].GetString();
cb.library_path = representation[kLibraryPathKey].GetString();
cache_[hash] = cb;
}
}

Dart_Handle DartCallbackCache::LookupDartClosure(
const std::string& name,
const std::string& class_name,
Expand Down
10 changes: 8 additions & 2 deletions lib/ui/plugins/callback_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
#include "flutter/fml/synchronization/thread_annotations.h"
#include "third_party/dart/runtime/include/dart_api.h"

#define DART_CALLBACK_INVALID_HANDLE -1

namespace blink {

typedef struct {
Expand All @@ -26,6 +24,9 @@ typedef struct {

class DartCallbackCache {
public:
static void SetCachePath(const std::string& path);
static std::string GetCachePath() { return cache_path_; }

static int64_t GetCallbackHandle(const std::string& name,
const std::string& class_name,
const std::string& library_path)
Expand All @@ -36,12 +37,17 @@ class DartCallbackCache {
static std::unique_ptr<DartCallbackRepresentation> GetCallbackInformation(
int64_t handle) FML_LOCKS_EXCLUDED(mutex_);

static void LoadCacheFromDisk() FML_LOCKS_EXCLUDED(mutex_);

private:
static Dart_Handle LookupDartClosure(const std::string& name,
const std::string& class_name,
const std::string& library_path);

static void SaveCacheToDisk() FML_EXCLUSIVE_LOCKS_REQUIRED(mutex_);

static std::mutex mutex_;
static std::string cache_path_;

static std::map<int64_t, DartCallbackRepresentation> cache_
FML_GUARDED_BY(mutex_);
Expand Down
11 changes: 9 additions & 2 deletions shell/platform/android/flutter_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "flutter/fml/message_loop.h"
#include "flutter/fml/paths.h"
#include "flutter/fml/platform/android/jni_util.h"
#include "flutter/lib/ui/plugins/callback_cache.h"
#include "flutter/runtime/dart_vm.h"
#include "flutter/runtime/start_up.h"
#include "flutter/shell/common/shell.h"
Expand Down Expand Up @@ -44,7 +45,8 @@ void FlutterMain::Init(JNIEnv* env,
jclass clazz,
jobject context,
jobjectArray jargs,
jstring bundlePath) {
jstring bundlePath,
jstring appStoragePath) {
std::vector<std::string> args;
args.push_back("flutter");
for (auto& arg : fml::jni::StringArrayToVector(env, jargs)) {
Expand All @@ -56,6 +58,11 @@ void FlutterMain::Init(JNIEnv* env,

settings.assets_path = fml::jni::JavaStringToString(env, bundlePath);

// Restore the callback cache.
blink::DartCallbackCache::SetCachePath(
fml::jni::JavaStringToString(env, appStoragePath));
blink::DartCallbackCache::LoadCacheFromDisk();

if (!blink::DartVM::IsRunningPrecompiledCode()) {
// Check to see if the appropriate kernel files are present and configure
// settings accordingly.
Expand Down Expand Up @@ -97,7 +104,7 @@ bool FlutterMain::Register(JNIEnv* env) {
{
.name = "nativeInit",
.signature = "(Landroid/content/Context;[Ljava/lang/String;Ljava/"
"lang/String;)V",
"lang/String;Ljava/lang/String;)V",
.fnPtr = reinterpret_cast<void*>(&Init),
},
{
Expand Down
3 changes: 2 additions & 1 deletion shell/platform/android/flutter_main.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class FlutterMain {
jclass clazz,
jobject context,
jobjectArray jargs,
jstring bundlePath);
jstring bundlePath,
jstring appRootPath);

FML_DISALLOW_COPY_AND_ASSIGN(FlutterMain);
};
Expand Down
4 changes: 4 additions & 0 deletions shell/platform/android/io/flutter/util/PathUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import android.content.Context;

public final class PathUtils {
public static String getFilesDir(Context applicationContext) {
return applicationContext.getFilesDir().getPath();
}

public static String getDataDirectory(Context applicationContext) {
return applicationContext.getDir("flutter", Context.MODE_PRIVATE).getPath();
}
Expand Down
5 changes: 3 additions & 2 deletions shell/platform/android/io/flutter/view/FlutterMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,9 @@ public static void ensureInitializationComplete(Context applicationContext, Stri
}

String appBundlePath = findAppBundlePath(applicationContext);
String appStoragePath = PathUtils.getFilesDir(applicationContext);
nativeInit(applicationContext, shellArgs.toArray(new String[0]),
appBundlePath);
appBundlePath, appStoragePath);

sInitialized = true;
} catch (Exception e) {
Expand All @@ -229,7 +230,7 @@ public static void ensureInitializationComplete(Context applicationContext, Stri
}
}

private static native void nativeInit(Context context, String[] args, String bundlePath);
private static native void nativeInit(Context context, String[] args, String bundlePath, String appStoragePath);
private static native void nativeRecordStartTimestamp(long initTimeMillis);

/**
Expand Down
1 change: 1 addition & 0 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ shared_library("create_flutter_framework_dylib") {
"framework/Source/FlutterAppDelegate.mm",
"framework/Source/FlutterAppDelegate_Internal.h",
"framework/Source/FlutterCallbackCache.mm",
"framework/Source/FlutterCallbackCache_Internal.h",
"framework/Source/FlutterChannels.mm",
"framework/Source/FlutterCodecs.mm",
"framework/Source/FlutterDartProject.mm",
Expand Down
13 changes: 11 additions & 2 deletions shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions;

/**
Called if this plugin has been registered for `UIApplicationDelegate` callbacks.

- Returns: `NO` if this plugin vetoes application launch.
*/
- (BOOL)application:(UIApplication*)application
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions;

/**
Called if this plugin has been registered for `UIApplicationDelegate` callbacks.
*/
Expand Down Expand Up @@ -293,8 +302,8 @@ NS_ASSUME_NONNULL_BEGIN
@end

/**
Implement this in the `UIAppDelegate` of your app to enable Flutter plugins to register themselves to the application
life cycle events.
Implement this in the `UIAppDelegate` of your app to enable Flutter plugins to register themselves
to the application life cycle events.
*/
@protocol FlutterAppLifeCycleProvider
- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ NS_ASSUME_NONNULL_BEGIN
FLUTTER_EXPORT
@interface FlutterPluginAppLifeCycleDelegate : NSObject
/**
Registers `delegate` to receive life cycle callbacks via this FlutterPluginAppLifecycleDelegate as long as it is alive.
Registers `delegate` to receive life cycle callbacks via this FlutterPluginAppLifecycleDelegate as
long as it is alive.

`delegate` will only referenced weakly.
*/
Expand All @@ -29,6 +30,14 @@ FLUTTER_EXPORT
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions;

/**
Calls all plugins registered for `UIApplicationDelegate` callbacks.

- Returns: `NO` if any plugin vetoes application launch.
*/
- (BOOL)application:(UIApplication*)application
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions;

/**
Calls all plugins registered for `UIApplicationDelegate` callbacks.
*/
Expand Down Expand Up @@ -74,8 +83,8 @@ FLUTTER_EXPORT
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler;

/**
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
the request.
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
some plugin handles the request.

- Returns: `YES` if any plugin handles the request.
*/
Expand All @@ -84,16 +93,16 @@ FLUTTER_EXPORT
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options;

/**
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
the request.
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
some plugin handles the request.

- Returns: `YES` if any plugin handles the request.
*/
- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url;

/**
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
the request.
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
some plugin handles the request.

- Returns: `YES` if any plugin handles the request.
*/
Expand All @@ -111,8 +120,8 @@ FLUTTER_EXPORT
API_AVAILABLE(ios(9.0));

/**
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
the request.
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
some plugin handles the request.

- Returns: `YES` if any plugin handles the request.
*/
Expand All @@ -121,17 +130,17 @@ FLUTTER_EXPORT
completionHandler:(nonnull void (^)(void))completionHandler;

/**
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
the request.
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
some plugin handles the request.

- Returns: `YES` if any plugin handles the request.
*/
- (BOOL)application:(UIApplication*)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler;

/**
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles
the request.
Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until
some plugin handles the request.
- Returns: `YES` if any plugin handles the request.
*/
- (BOOL)application:(UIApplication*)application
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ - (void)dealloc {
[super dealloc];
}

- (BOOL)application:(UIApplication*)application
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
return [_lifeCycleDelegate application:application willFinishLaunchingWithOptions:launchOptions];
}

- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
Expand Down
Loading