Android WebView causing RuntimeException at WebViewDelegate.getPackageId - android

I have a WebView in the layout xml of my MainActivity, to which I setWebViewClient(new WebViewClient()), followed by loadUrl(...) in onCreate.
Most of the time the app runs fine and the Web content is displayed correctly.
But in some cases, opening the app causes a crash. I've noticed that it happens when the app scheduled a PendingIntent broadcast with AlarmManager, which triggers a Notification whose contentIntent is a PendingIntent.getActivity set to launch MainActivity.
But it happens only in the case when the user has removed the app from the stack of active apps in the meantime (Notification is visible, not yet clicked, and stack if apps cleared. So, app process probably stopped?).
Seemingly no other system modifications in between (in particular no app/system update, no playing around with user profiles or Chrome app.)
Stack trace:
java.lang.RuntimeException:
at android.webkit.WebViewDelegate.getPackageId (WebViewDelegate.java:164)
at yj.a (PG:16)
at xH.run (PG:14)
at java.lang.Thread.run (Thread.java:764)
Occurs with Android 7.0 thru 9. Also, seems to have started to occur when I upgraded target SDK to 28.
I don't use explicitly a WebViewDelegate. It must be internal system code (hence the obfuscation).
By reading the source code of AOSP, it seems that the WebView fails to retrieve the package to which it belongs -- but why sometimes only!?
Any help appreciated! Thanks.

It has taken weeks of investigation on and off, but I've finally found why I'm seeing this issue. For me, it was just because I'd overridden the getResources() method in my application scope to use the current activity. Something like this:
public class MyApplication extends MultiDexApplication {
private static MyApplication sInstance = null;
private WeakReference<Activity> mCurrentActivity;
public static MyApplication getInstance() {
return sInstance;
}
public void setCurrentActivity(Activity activity) {
mCurrentActivity = new WeakReference<>(activity);
}
public Activity getCurrentActivity() {
return mCurrentActivity == null ? null : mCurrentActivity.get();
}
#Override
public Resources getResources() {
// This is a very BAD thing to do
Activity activity = getCurrentActivity();
if (activity != null) {
return activity.getResources();
}
return super.getResources();
}
}
This was done as a shortcut as I often wanted to get strings that were activity-specific, so I was calling MyApplication.getInstance().getResources().getString(). I now know this was a bad thing to do - removing my override of this method instantly fixed it.
So the key takeaway from this for me is that when the WebView is initialising, it MUST be able to get hold of the application context, so that the resources passed into WebViewDelegate.getPackageId() are at the application level - the activity context isn't enough, and causes this error.
As a side note - I wasn't even trying to add a WebView to my application. I was only actually using the following:
String userAgent = WebSettings.getDefaultUserAgent(this);
I was then passing this value into a custom media player that I'm using. Passing "this" as either application or activity scope always failed, due to my override.

Looking through documentation,you can see that error is thrown when package can't be found.Check your syntax ,package name and try again.
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/webkit/WebViewDelegate.java (Line 164)
/**
* Returns the package id of the given {#code packageName}.
*/
public int getPackageId(Resources resources, String packageName) {
SparseArray<String> packageIdentifiers =
resources.getAssets().getAssignedPackageIdentifiers();
for (int i = 0; i < packageIdentifiers.size(); i++) {
final String name = packageIdentifiers.valueAt(i);
if (packageName.equals(name)) {
return packageIdentifiers.keyAt(i);
}
}
throw new RuntimeException("Package not found: " + packageName);
}

Related

May be the Application not created?

I have some Apps where I use a static reference of the Application instance.
That's usefull when I need to use context in some singleton Class that has application lifetime.
Here an example:
public class MyApp extends Application
{
private static MyApp smApplication;
public MyApp()
{
MyApp.smApplication = this;
}
public void onCreate()
{
super.onCreate();
MyApp.smApplication = this;
}
public static MyApp getApp() {
return MyApp.smApplication;
}
}
class Settings // Example of Singleton class
{
...
public boolean loadSettings()
{
Context context = MyApp.getApp(); // NullPointerException on the next line
SharedPreferences pref = context.getSharedPreferences(SHAREDPREF_FILENAME, Context.MODE_PRIVATE);
...
}
}
This works like a charm in the majority of the cases.
But... I have a number of NullPointerException crashes reported in the Play Console about that Context. Usually called in the MainActivity.onCreate or MyService.onCreate.
Application should Always be created before any other Activiy or Service. So I can't understand what happens. I've never had a similar crash on any of my devices.
smApplication is initialized in the constructor and never set to null.
It seems that in that cases Application is not created. It's also weird that all these crashes are reported in the PlayConsole but I cannot find them in Firebase Crashlitics as if neither Crashlitics has yet initialized in that cases (and FC shoul be initialized by the Application)
This has been happening for years in a small part of my users.
Any suggestion?
PS. I Do not need suggestion on code refactoring, just to understand what happens and how to avoid it.

Is it possible to get a FlutterView from BroadcastReceiver/Alarm Manager?

I am trying to build a Flutter app that does the following:
1) Run an alarm manager every minute (even when app is in background/closed).
2) When the alarm manager's onReceive method is called, get the users location.
3) Store this location in a SQL/SQF database.
Basically, I have all the code working. However, I'd prefer to do step 2 and 3 with two different plugins, as to create more modularity. But to do so, I need to have an instance of a FlutterView, such that I can do a "MethodChannel(flutterView, CHANNEL).invokeMethod(methods, args);" from Android.
(In android/Java, onReceive method) I have tried to get the FlutterView from the context, like so:
private FlutterView viewFromAppContext(Context context) {
Application app = (Application) context.getApplicationContext();
if (!(app instanceof FlutterApplication)) {
Log.i(TAG, "viewFromAppContext app not a FlutterApplication");
return null;
}
FlutterApplication flutterApp = (FlutterApplication) app;
Activity activity = flutterApp.getCurrentActivity();
if (activity == null) {
Log.i(TAG, "viewFromAppContext activity is null");
return null;
}
if (!(activity instanceof FlutterActivity)) {
Log.i(TAG, "viewFromAppContext activity is not a FlutterActivity");
return null;
}
FlutterActivity flutterActivity = (FlutterActivity) activity;
return flutterActivity.getFlutterView();
}
However, when I try to do this when the app is in the background, the activity is null.
Is it possible to create a new activity and/or flutterview in this scenario (which can direct to my 'setMethodCallHandler' method in dart)?
I think what you are looking for is a FlutterNativeView called with the second param set to true; it is not easy to follow, slightly divergent from the linked git repo and is engineered well in excess of your needs, but Ben Konyi has a working example of this. I think you might enjoy checking out some of the ways he kept the other plugins working and the service working, if not the methodchannel impl (with three callback domains).

Why does Carto Package Manager can't connect to it's database after resuming?

We are using Carto Xamarin Mobile SDK to develop app with offline maps support. It works fine until user does not try to resume application after it has been disposed. Activity creation (activity uses carto package manager) crashes with exception:
Carto.PackageManager.CartoPackageManager.CartoPackageManager(string source, string dataFolder)<01980a2dae7148abb92e6b982667f448>:0
App.Droid.OfflineMaps.AndroidMapPackageManager.AndroidMapPackageManager()<76ae23b9271c407d865ebb6162639870>:0
App.Droid.Plugins.MapDownloader.Plugin.<>c.<Load>b__0_0()<76ae23b9271c407d865ebb6162639870>:0
MvvmCross.Platform.IoC.MvxSimpleIoCContainer.<>c__DisplayClass33_0<TInterface>.<RegisterSingleton>b__0()<4ddde23419c5494288c799fcdbb0f189>:0
MvvmCross.Platform.IoC.MvxSimpleIoCContainer.ConstructingSingletonResolver.Resolve()<4ddde23419c5494288c799fcdbb0f189>:0
MvvmCross.Platform.IoC.MvxSimpleIoCContainer.InternalTryResolve(Type type, MvxSimpleIoCContainer.IResolver resolver, ref object resolved
App.Core.ViewModels.Blocks.Maps.Offline.MapDownloaderOwnerViewModel.EnableMapDownloader(City city)<d6cca792b401420d922bc024
Here on line 105 you can see where exception is originally thrown.
In the regular entering page it works fine.
PackageManager request is happening in the ctor of the page ViewModel via:
Mvx.Resolve<MapDownloadsManager>(); // It receivs as injection platform-dependent packageManager
It seems that the problem is in the database path.
We are generating the folder name that then passed to the CartoPackageManager constructor in the following way:
private static string CreateFolder(string folderPath)
{
var folder = GetDocumentDirectory(folderPath);
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
return folder;
}
private static string GetDocumentDirectory(string withFolder = null)
{
var documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
return withFolder == null ? documents : Path.Combine(documents, withFolder + "/");
}
Do you have any ideas where the source of the problem could be?
P.S.: Carto SDK v 4.0.2, MvvmCross 4.4.0, unfortunately can't update to the newer now.
After some research I have spotted that task queue is reset, now supposing that app actually crashes here in source code:
std::string taskDbFileName = "tasks_v1.sqlite";
try {
_taskQueue = std::make_shared<PersistentTaskQueue>(createLocalFilePath(taskDbFileName));
}
catch (const std::exception& ex) {
//This looks like: Error while constructing PackageManager: Package encryption keys do not match, trying to remove
Log::Errorf("PackageManager: Error while constructing PackageManager::PersistentTaskQueue: %s, trying to remove...", ex.what());
_taskQueue.reset();
utf8_filesystem::unlink(taskDbFileName.c_str());
try {
_taskQueue = std::make_shared<PersistentTaskQueue>(createLocalFilePath(taskDbFileName));
}
catch (const std::exception& ex) {
**throw FileException("Failed to create/open package manager task queue database", taskDbFileName); //App gets here.**
}
}
Initialization of package manager:
public class AndroidMapPackageManager : PackageManagerListener, IPackageManger
{
private readonly CartoPackageManager _packageManager;
public AndroidMapPackageManager()
{
var folder = CreateFolder(OfflineMapStrings.MapsFolderName); // /data/user/0/com.app.cityguide/files/mapmap2/
_packageManager =
new CartoPackageManager(OfflineMapsStrings.PackageManagerSource, folder)
{
PackageManagerListener = this
};
}
}
I am starting it just after the constructor worked after subscription to the listener events by other class. And, frankly saying, I never stop it. Do I need it?
With FIllRam Repoductivity is 100%. I used debug mode with no background allowed and now I tryed Ram Filler and result is same.
License issues:
05-25 12:41:41.091 26101 26101 E carto-mobile-sdk: CartoPackageManager: RegisterLicense not called, using random key for package encryption!
05-25 12:41:41.094 26101 26101 E carto-mobile-sdk: PackageManager: Error while constructing PackageManager: Package encryption keys do not match, trying to remove
The problem was that I was registering license key in SplashActivity. But, after resuming splash activity is not shown and, obviously, Registering of the license key was not performed. I have moved it to the custom application class and it worked:
public class CustomApplication : Application
{
public override void OnCreate()
{
base.OnCreate();
MapView.RegisterLicense(CartoApiKey, this);
}
}

How to restart an Activity automatically after it crashes?

Is there a way for me to create a service to track my activity class and restart it after a crash? Note that i CANNOT use uncaughthandlers thread method to restart my app. My app is supposed to crash, don't worry about that part. My app is something simple, like this
private class AudioRenderer extends Activity {
private MediaPlayer AudioRenderer(String filePath) {
File location = new File(filePath);
Uri path = Uri.fromFile(location);
mp= MediaPlayer.create(this, path);
}
return mp
}
Once this crashes, the service listening in the background will restart my app automatically. Anybody knows how this is possible? Thanks!
You can do that, yes, as explained below. But if such techniques may make sense for experiments, they are definitely not suitable for production. That would be awfully ugly and unefficient.
This said, here is a way to go:
Make your Service sticky or redelivered to ensure it will always be running after having been started once and not explicitely stopped.
in your Activity class, statically store WeakReferences pointing to all its running instances and provide a way to statically check whether at least one of them is currently allocated:
public class MyActivity extends Activity {
private static ArrayList<WeakReference<MyActivity >> sMyInstances = new ArrayList<WeakReference<MyActivity >>();
public MyActivity() {
sMyInstances.add(new WeakReference<MyActivity >(this));
}
private static int nbInstances() {
int ret = 0;
final int size = sMyInstances.size();
for (int ctr = 0; ctr < size; ++ctr) {
if (sMyInstances.get(ctr).get() != null) {
ret++;
}
}
return ret;
}
}
(WeakReference are references to objects that do not prevent these objects to be garbage-collected, more details here)
Then, from your Service, call MyActivity.nbInstances() from time to time. It will return 0 a (usually short but theoretically unpredictable) while after the crash of the last running MyActivity instance. Warning: it will do so unless you have a memory leak concerning this Activity or its underlying Context as this leak would prevent the garbage collection of the instance that crashed.
Then you just have to start a new instance of your Activity from your Service, using startActivity(Intent)

Google Analytics in Android app - dealing with multiple activities

I was pretty excited to see how easy it is to set up Google Analytics with my app, but the lack of documentation has me sitting with a few questions. The only information that I can find is right from the documentation here, which only looks at reporting PageViews and Events from one Activity. I want to report PageViews and Events across multiple Activities in my app.
Right now in the onCreate() of all of my activities, I am calling:
tracker = GoogleAnalyticsTracker.getInstance();
tracker.start("UA-xxxxxxxxx", this);
And in the onDestroy() of all of my activities:
tracker.stop();
I then track PageViews and Events as needed, and Dispatch them along with another HTTP request I am performing. But I'm not so sure this is the best way. Should I be calling start() and stop() in each activity, or should I only call start() and stop() in my main launcher activity?
The problem with calling start()/stop() in every activity (as suggested by Christian) is that it results in a new "visit" for every activity your user navigates to. If this is okay for your usage, then that's fine, however, it's not the way most people expect visits to work. For example, this would make comparing android numbers to web or iphone numbers very difficult, since a "visit" on the web and iphone maps to a session, not a page/activity.
The problem with calling start()/stop() in your Application is that it results in unexpectedly long visits, since Android makes no guarantees to terminate the application after your last activity closes. In addition, if your app does anything with notifications or services, these background tasks can start up your app and result in "phantom" visits. UPDATE: stefano properly points out that onTerminate() is never called on a real device, so there's no obvious place to put the call to stop().
The problem with calling start()/stop() in a single "main" activity (as suggested by Aurora) is that there's no guarantee that the activity will stick around for the duration that your user is using your app. If the "main" activity is destroyed (say to free up memory), your subsequent attempts to write events to GA in other activities will fail because the session has been stopped.
In addition, there's a bug in Google Analytics up through at least version 1.2 that causes it to keep a strong reference to the context you pass in to start(), preventing it from ever getting garbage collected after its destroyed. Depending on the size of your context, this can be a sizable memory leak.
The memory leak is easy enough to fix, it can be solved by calling start() using the Application instead of the activity instance itself. The docs should probably be updated to reflect this.
eg. from inside your Activity:
// Start the tracker in manual dispatch mode...
tracker.start("UA-YOUR-ACCOUNT-HERE", getApplication() );
instead of
// Start the tracker in manual dispatch mode...
tracker.start("UA-YOUR-ACCOUNT-HERE", this ); // BAD
Regarding when to call start()/stop(), you can implement a sort of manual reference counting, incrementing a count for each call to Activity.onCreate() and decrementing for each onDestroy(), then calling GoogleAnalyticsTracker.stop() when the count reaches zero.
The new EasyTracker library from Google will take care of this for you.
Alternately, if you can't subclass the EasyTracker activities, you can implement this manually yourself in your own activity base class:
public abstract class GoogleAnalyticsActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Need to do this for every activity that uses google analytics
GoogleAnalyticsSessionManager.getInstance(getApplication()).incrementActivityCount();
}
#Override
protected void onResume() {
super.onResume();
// Example of how to track a pageview event
GoogleAnalyticsTracker.getInstance().trackPageView(getClass().getSimpleName());
}
#Override
protected void onDestroy() {
super.onDestroy();
// Purge analytics so they don't hold references to this activity
GoogleAnalyticsTracker.getInstance().dispatch();
// Need to do this for every activity that uses google analytics
GoogleAnalyticsSessionManager.getInstance().decrementActivityCount();
}
}
public class GoogleAnalyticsSessionManager {
protected static GoogleAnalyticsSessionManager INSTANCE;
protected int activityCount = 0;
protected Integer dispatchIntervalSecs;
protected String apiKey;
protected Context context;
/**
* NOTE: you should use your Application context, not your Activity context, in order to avoid memory leaks.
*/
protected GoogleAnalyticsSessionManager( String apiKey, Application context ) {
this.apiKey = apiKey;
this.context = context;
}
/**
* NOTE: you should use your Application context, not your Activity context, in order to avoid memory leaks.
*/
protected GoogleAnalyticsSessionManager( String apiKey, int dispatchIntervalSecs, Application context ) {
this.apiKey = apiKey;
this.dispatchIntervalSecs = dispatchIntervalSecs;
this.context = context;
}
/**
* This should be called once in onCreate() for each of your activities that use GoogleAnalytics.
* These methods are not synchronized and don't generally need to be, so if you want to do anything
* unusual you should synchronize them yourself.
*/
public void incrementActivityCount() {
if( activityCount==0 )
if( dispatchIntervalSecs==null )
GoogleAnalyticsTracker.getInstance().start(apiKey,context);
else
GoogleAnalyticsTracker.getInstance().start(apiKey,dispatchIntervalSecs,context);
++activityCount;
}
/**
* This should be called once in onDestrkg() for each of your activities that use GoogleAnalytics.
* These methods are not synchronized and don't generally need to be, so if you want to do anything
* unusual you should synchronize them yourself.
*/
public void decrementActivityCount() {
activityCount = Math.max(activityCount-1, 0);
if( activityCount==0 )
GoogleAnalyticsTracker.getInstance().stop();
}
/**
* Get or create an instance of GoogleAnalyticsSessionManager
*/
public static GoogleAnalyticsSessionManager getInstance( Application application ) {
if( INSTANCE == null )
INSTANCE = new GoogleAnalyticsSessionManager( ... ,application);
return INSTANCE;
}
/**
* Only call this if you're sure an instance has been previously created using #getInstance(Application)
*/
public static GoogleAnalyticsSessionManager getInstance() {
return INSTANCE;
}
}
The SDK now has a external library which takes care of all of this. Its called EasyTracker. You can just import it and extend the provided Activity or ListActivity, create a string resource with your code and you are done.
The tracker will only track the activity where it's executed. So, why don't you subclass an Activity which start it every time on onCreate:
public class GAnalyticsActivity extends Activity{
public void onCreate(Bundle icicle){
super.onCreate(icile);
tracker = GoogleAnalyticsTracker.getInstance();
tracker.start("UA-xxxxxxxxx", this);
}
// same for on destroy
}
Then, you extends that class for every activity you use:
public class YourActivity extends GAnalyticsActivity{
public void onCreate(Bundle icicle){
super.onCreate(icile);
// whatever you do here you can be sure
// that the tracker has already been started
}
}
The approach I am using is to use a Bound Service (I happen to be using one already so was spared the creation of extra boiler plate code.)
A Bound Service will only last as long as there are Activities bound to it. All the activities in my app bind to this service, so it lasts only as long as the user is actively using my application - therefore very much a real 'session'.
I start the tracker with a singleton instance of Application which I have extended and added a static getInstance() method to retrieve the instance:
// Non-relevant code removed
public IBinder onBind(Intent intent) {
tracker = GoogleAnalyticsTracker.getInstance();
tracker.startNewSession(PROPERTY_ID, MyApp.getInstance());
}
public boolean onUnbind(Intent intent) {
tracker.stopSession();
}
See: http://developer.android.com/guide/topics/fundamentals/bound-services.html
I did a time based split between visits in my app, working like this:
I've build a wrapper singleton Tracker object for the GoogleAnalyticsTracker where i keep the last time something got tracked. If that time's more then x seconds i treat it as a new visit.
Of course this is only useful if you track everything in your app, and may not be the best solution in every situation, works well for my app though.
It only supports trackPageView, but setCustomVar and trackEvent should be easily implemented..
Anywhere you need to track something just add the line:
Tracker.getInstance(getApplicationContext()).trackPageView("/HelloPage");
I usually do it in the onResume of an activity
Tracker gist
You will need something like this: http://mufumbo.wordpress.com/2011/06/13/google-analytics-lags-on-android-how-to-make-it-responsive/
That's on the previous version and used to work very well. Now I'm in the same struggle as you, as V2 doesn't seems to be very consistent.
I wonder if this is something that could be done using AOP.
Android can only use compile-time AOP methods so maybe something like AspectJ?
There's a little more info on using AspectJ in Android in this thread. The main issue being that you would still need to declare on classes you own.

Categories

Resources