Trouble warming up Chrome CustomTabs with latest androidx browser dependency - android

I'm looking for an example of how to use the CustomTabs "warm up" functionality to preload Chrome when the host application is initialized. Documentation around using Chrome CustomTabs for loading URLs is woefully incomplete, and all the examples out there are from 5 years ago and use ugly callback mechanisms to accomplish this.
In the latest version (2.2.0), I was able to find this method that looked promising:
* Connects to the Custom Tabs warmup service, and initializes the browser.
*
* This convenience method connects to the service, and immediately warms up the Custom Tabs
* implementation. Since service connection is asynchronous, the return code is not the return
* code of warmup.
* This call is optional, and clients are encouraged to connect to the service, call
* <code>warmup()</code> and create a session. In this case, calling this method is not
* necessary.
*
* #param context {#link Context} to use to connect to the remote service.
* #param packageName Package name of the target implementation.
* #return Whether the binding was successful.
*/
public static boolean connectAndInitialize(#NonNull Context context,
#NonNull String packageName) {
if (packageName == null) return false;
final Context applicationContext = context.getApplicationContext();
CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
#Override
public final void onCustomTabsServiceConnected(
#NonNull ComponentName name, #NonNull CustomTabsClient client) {
client.warmup(0);
// Unbinding immediately makes the target process "Empty", provided that it is
// not used by anyone else, and doesn't contain any Activity. This makes it
// likely to get killed, but is preferable to keeping the connection around.
applicationContext.unbindService(this);
}
#Override
public void onServiceDisconnected(ComponentName componentName) { }
};
try {
return bindCustomTabsService(applicationContext, packageName, connection);
} catch (SecurityException e) {
return false;
}
}
But the warmup method doesn't ever get called and performance doesn't seem to be improved. I tried calling this in the Application class as well as the Activity classes that I'd be loading URLs from.
I'm loading URLs using CustomTabsClient.Builder()..build().launchUrl(url)

Related

What happen if return false in OnCreate of ContentProvider?

The document states that we should return true if the provider was successfully loaded, false otherwise. In my implementation, I would return false if DatabaseHelper == null.
Suppose now DatabaseHelper == null and false is returned in onCreate, and query the provider somewhere in the code later, the provider is still being queried and of coz it would crash.
My question is what is the use to return false in OnCreate of ContentProvider?
And how should I handle for the query after a fail onCreate? just run again the onCreate in query?
what is the use to return false in OnCreate of ContentProvider?
By quickly navigating through the Android source I found that, as for now, it really does not matter what you return, it just get ignored, again as for now.
On tests and ActivityThread, attachInfo is called right after newInstance so if you look at ContentProvider source at line 1058 is where onCreate is called and looks like:
/**
* After being instantiated, this is called to tell the content provider
* about itself.
*
* #param context The context this provider is running in
* #param info Registered information about this content provider
*/
public void attachInfo(Context context, ProviderInfo info) {
/*
* We may be using AsyncTask from binder threads. Make it init here
* so its static handler is on the main thread.
*/
AsyncTask.init();
/*
* Only allow it to be set once, so after the content service gives
* this to us clients can't change it.
*/
if (mContext == null) {
mContext = context;
mMyUid = Process.myUid();
if (info != null) {
setReadPermission(info.readPermission);
setWritePermission(info.writePermission);
setPathPermissions(info.pathPermissions);
mExported = info.exported;
}
ContentProvider.this.onCreate();
}
}
Keep in mind that if documentation says so who knows, maybe this will be used/fixed in future releases.
how should I handle for the query after a fail onCreate? just run again the onCreate in query?
I would say yes, not necessarily onCreate but your very own method that initializes once and ensures your DatabaseHelper or so, that would be your best effort, I mean according to documentation of onCreate
You should defer nontrivial initialization (such as opening, upgrading, and scanning databases) until the content provider is used
So technically you would be doing as intended, yet it is wild out there so be safe.

What does "Hidden constructor called more than once per process" mean?

In my LogCat while debugging my app, I often get:
E/TelephonyManager(5382): Hidden constructor called more than once per process!
I've been Googling around a bit, and while I noticed other mentions of the error (in other logs), I cannot identify what it means.
So what is this error? Why am I getting it? And what is its significance?
This is from the Android source code:
/**
* Provides access to information about the telephony services on
* the device. Applications can use the methods in this class to
* determine telephony services and states, as well as to access some
* types of subscriber information. Applications can also register
* a listener to receive notification of telephony state changes.
*
* You do not instantiate this class directly; instead, you retrieve
* a reference to an instance through
* {#link android.content.Context#getSystemService
* Context.getSystemService(Context.TELEPHONY_SERVICE)}.
*
* Note that access to some telephony information is
* permission-protected. Your application cannot access the protected
* information unless it has the appropriate permissions declared in
* its manifest file. Where permissions apply, they are noted in the
* the methods through which you access the protected information.
*/
public class TelephonyManager {
private static final String TAG = "TelephonyManager";
private static Context sContext;
private static ITelephonyRegistry sRegistry;
/** #hide */
public TelephonyManager(Context context) {
context = context.getApplicationContext();
if (sContext == null) {
sContext = context;
sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
} else if (sContext != context) {
Log.e(TAG, "Hidden constructor called more than once per process!");
Log.e(TAG, "Original: " + sContext.getPackageName() + ", new: " +
context.getPackageName());
}
}
The TelephonyManager seems to put the "Hidden constructor called more than once per process!" into the Log when your application calls the constructor more than once, as the message suggests. The constructor is called using the getSystemService as per the comments on the constructor.
Do you have more than one instance of:
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
or something similar in your code? This could possibly be causing the error.
EDIT: If it's not your code causing the message then it's the program running with PID 5382 I think.

Returning an AIDL interface implementation by reference across processes

I have a little Android project going on which involves some IPC where client Activities bind to my service.
I'm using AIDL for IPC and RPC which works pretty good, but I'm having trouble returning a service-side instantiated AIDL interface implementation to the clients:
When the client is running in the same process as the service -- meaning running the service locally -- everything works just fine.
But when client and service are seperated in different processes the method startLogSession, which is defined in ILogDroidBinder.aidl always returns null.
The other method implemented in this interface -- getSessionIds -- which returns a List containing ints, always works (locally and cross-process).
I'm taking a wild guess and suppose my ILogDroidSession implementation should also implement Parcelable, but that wouldn't work, because I can't parcel an object containg a reference to an SQLiteDatabase (or can I?).
Here is the relevant code.
I'd really be glad if someone could help me out here. Maybe I'm just missing a point somewhere, since this is my first Android project and I'm not quite involved yet.
ILogDroidSession.aidl (An implementation of this is what I want to return to the client):
package net.sourceforge.projects.logdroid;
interface ILogDroidSession {
/**
* Logs the given text to the error message channel of the current logging
* session.
* #param text Text to log.
*/
void logError(in String text);
}
ILogDroidBinder.aidl (The IBinder interface passed to the client's onServiceConnected):
package net.sourceforge.projects.logdroid;
import net.sourceforge.projects.logdroid.ILogDroidSession;
interface ILogDroidBinder {
/**
* Starts a new LogDroid session which handles all logging events.
* #param sessionName The name of the session.
* #return An instance of ILogDroidSession.
*/
ILogDroidSession startLogSession(in String sessionName);
/**
* Gets a list with all available LogSession ids.
*/
List getSessionIds();
}
LogDroidService.java (Relevant code from my service):
public class LogDroidService extends Service {
/**
* The binder interface needed for Activities to bind to the
* {#code LogDroidService}.
*/
private final ILogDroidBinder.Stub binder = new ILogDroidBinder.Stub() {
/**
* Starts a new LogDroidSession.
*/
public ILogDroidSession startLogSession(String sessionName) {
return LogDroidService.this.createSession(sessionName);
}
/**
* Gets all available session ids.
*/
public List<Integer> getSessionIds() {
return LogDroidService.this.getSessionIds();
}
};
/**
* The database connection to be used for storing and retrieving log entries.
*/
private LogDroidDb database;
#Override
public void onCreate() {
super.onCreate();
database = new LogDroidDb(getApplicationContext());
try {
database.open(); // opens as writable database
} catch ( SQLException ignorefornow ) {
}
}
#Override
public IBinder onBind(Intent ignore) {
return binder;
}
/**
* Creates a new LogDroidSession which will be returned to the user as a
* AIDL remote object.
* #param sessionName Name of the session.
* #return A new instance of ILogDroidSession
*/
ILogDroidSession createSession(String sessionName) {
LogDroidSession session = new LogDroidSession(database, sessionName);
session.addLoggingOccurredListener(this);
return session;
}
/**
* Retrieves all session ids.
* #return Array containing all LogDroidSession ids.
*/
ArrayList<Integer> getSessionIds() {
return database.getSessionIds();
}
}
MainActivity.java (Relevant client code):
public class MainActivity extends Activity {
private ILogDroidSession session;
private ILogDroidBinder binder;
private ServiceConnection con = new ServiceConnection() {
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
binder = ILogDroidBinder.Stub.asInterface(arg1); // always works
try {
// works locally but always returns null when cross-process
session = binder.startLogSession("TestSession");
// always works
List<Integer> ids = binder.getSessionIds();
} catch ( Exception ex) {
// no exceptions are thrown either when running locally or cross-process
Toast.makeText(getApplicationContext(), ex.getMessage(),
Toast.LENGTH_LONG).show();
}
}
public void onServiceDisconnected(ComponentName arg0) {
}
};
}
ILogDroidSession can be defined as just interface in java file, it shouldn't be in AIDL.
If the client and LogDroidService are running in different processes, LogDroidSession should be parcelable to send/receive over IPC.
Data that is exchanged across the processes should just be stream of bytes that both sender and receiver understands through a protocol.
I'm taking a wild guess and suppose my ILogDroidSession implementation should also implement Parcelable, but that wouldn't work, because I can't parcel an object containg a reference to an SQLiteDatabase (or can I?).
LogDroidSession can't be parceled here, add new functions to ILogDroidBinder that returns session related information (in the form of plain data types).

Share a WebView between activities

I've written an iOS app that I want to port to android. This app is a front-end for a web-based timekeeping system at work. Unfortunately, the system doesn't have an API, so I'm screen-scraping it using xpath queries in javascript. In iOS, I only have to load this page once because I get full control over when instances of my UIWebView get destroyed.
The iOS app only has 3 use cases, each which break into separate Android
activities:
Login
View a list of all reported times
Report a new time.
Using a naive approach, my android views (including the WebView I need to use to interact with the timekeeping system) will be destroyed and recreated when I switch between views. If I have to reload the WebView every time I switch activities, my app will appear very slow.
Is it possible to share a WebView instance between multiple
activities? I know I can set the launchMode to singleInstance, but ideally, it would be nice to allow separate instances so that the back button would function normally.
Per a suggestion on from Eric Burke at STL Mobile Dev:
See if your Android Application can create the WebView. You won't actually display the WebView anywhere, it's just created for interaction with the web site. The WebView instance's lifecycle is then managed by your Application rather than an Activity.
I was able to share my WebView between my Activity instances. I specified a custom Application in my manifest, and created the WebView inside the Application. The Application is a Context and it lives as long as the app does, so all Activity objects can share the WebView that way.
The way I solved this problem is little different. It pivots around couple of things.
WebViews can be created with application context.
There is something called MutableContextWrapper, we can flip the underlying context inside it.
So, we create the web view in the pool with mutable context wrapper with appContext and when an activity context asks for it, we flip the context and return it. We flip the context to appContext when we return the webView back.
Something like this
// Has a single web view, and crashes if a request to obtain comes in and the
// cached instance is already allocated.
public class SingularWebViewPool implements WebViewPool {
private WebView mCachedInstance;
private volatile int mBorrower;
private final int mSignature;
private final Context mAppCtx;
public SingularWebViewPool(Context appCtx) {
this.mAppCtx = appCtx;
this.mCachedInstance = new WebView(appCtx);
// We will match this every time, we flip to app context to ensure a different web view is
// not handed over to us.
this.mSignature = mCachedInstance.hashCode();
}
#Override
#NonNull
public WebView obtain(#NonNull Context activity) {
if (mCachedInstance != null) {
Context ctx = mCachedInstance.getContext();
if (ctx instanceof MutableContextWrapper) {
((MutableContextWrapper) ctx).setBaseContext(activity);
} else
// We should not reach here!
throw new IllegalStateException("Cached web view stored without a mutable context wrapper.");
WebView temp = mCachedInstance;
mCachedInstance = null;
this.mBorrower = activity.hashCode();
return temp;
} else
throw new IllegalStateException("Pool not having a cached web view instance when obtain() was called.");
}
#Override
public boolean release(#NonNull WebView webView, #NonNull Context borrower) {
// Validate the last borrower.
if (borrower.hashCode() != this.mBorrower) {
return false;
}
Context ctx = webView.getContext();
if (ctx instanceof MutableContextWrapper) {
((MutableContextWrapper) ctx).setBaseContext(mAppCtx);
} else
throw new IllegalStateException("Cached web view stored without a mutable context wrapper.");
// match the signature.
if (mSignature != mCachedInstance.hashCode()) {
throw new IllegalStateException("A different web view is released other than what we have given out.");
}
mCachedInstance = webView;
mBorrower = 0;
return true;
}
}

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