How Picasso gets the Context without parameter passing

How Picasso gets the Context without parameter passing

sample.png?w=2924

What’s new?

I’ve updated the Picasso library recently and mainly see if anything new and surprising. The second goal was to see if the latest version has some bug fixes. I didn’t realize that, on the latest version 2.7828, it changes the way to instantiate the Picasso instance by using the With(Context context). Instead, it uses a Get() function to get the instance:

1
2
3
4
5
Picasso.get()
.load(url)
.resize(50, 50)
.centerCrop()
.into(imageView)

How does it do?

That’s interesting. Here comes a question: how does the Get() create a Picasso instance without getting any Application/Activity context? Any magic inside? So, I dive into the source code a little bit to reveal the secrete. Here is the source code of the Picasso Get():

1
2
3
4
5
6
7
8
9
10
11
12
13
public static Picasso get() {
if (singleton == null) {
synchronized (Picasso.class) {
if (singleton == null) {
if (PicassoProvider.context == null) {
throw new IllegalStateException("context == null");
}
singleton = new Builder(PicassoProvider.context).build();
}
}
}
return singleton;
}

Alright, you can find this code piece:

1
2
3
if (PicassoProvider.context == null) {
throw new IllegalStateException("context == null");
}

Is this PicassoProvider the same thing as ContentProvider? Let’s take a look at the source code of picasso/PicassoProvider.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@RestrictTo(LIBRARY)
public final class PicassoProvider extends ContentProvider {

@SuppressLint("StaticFieldLeak") static Context context;

@Override public boolean onCreate() {
context = getContext();
return true;
}

@Nullable @Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}

@Nullable @Override public String getType(@NonNull Uri uri) {
return null;
}

@Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}

@Override public int delete(@NonNull Uri uri, @Nullable String selection,
@Nullable String[] selectionArgs) {
return 0;
}

@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
@Nullable String[] selectionArgs) {
return 0;
}
}

That’s right. It’s a content provider. But you can see this content provider doesn’t function. Just several overridden functions. Let’s check the AndroidManifest.xml:

1
2
3
4
5
6
7
8
9
10
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.picasso">

<application>
<provider
android:name=".PicassoProvider"
android:authorities="${applicationId}.com.squareup.picasso"
android:exported="false"/>
</application>
</manifest>

It’s there but doesn’t use any keyword like export to outside users.

The tricky part.

There’s a tricky part here. If you notice the authorities’ declaration of the content provider, you can see that it uses the ${applicationId} this way to set up a sole authority for different Apps. That’s because if you set up a unique authority, then that’s only one application running on the device can use this library at the same time. So, by using this way, you can create a general 3rd library for different Apps. You should also remember to remove the ApplicationId setting in the library module, or the Gradle will automatically use this ApplicationId from build.gradle file.

Another question is: how can we make sure the content provider would be the first component to initialize? That’s because when an Android application has launched, there is a well-defined order of initializing components:

  • Each content provider declared in the manifest would be initialized first in this order

  • The application would be the second part to initialize.

  • The final one would be the component that’s invoked by some intent.

That’s the reason why Picasso can use this way to get the context first at the very beginning of the App launching.

But I think Picasso will use another way to instantiate the instance cause it shows the Builder class in the latest master branch.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/** Fluent API for creating {@link Picasso} instances. */
@SuppressWarnings("UnusedDeclaration") // Public API.
public static class Builder {
private final Context context;
@Nullable private Call.Factory callFactory;
@Nullable private ExecutorService service;
@Nullable private PlatformLruCache cache;
@Nullable private Listener listener;
private final List<RequestTransformer> requestTransformers = new ArrayList<>();
private final List<RequestHandler> requestHandlers = new ArrayList<>();
@Nullable private Bitmap.Config defaultBitmapConfig;

private boolean indicatorsEnabled;
private boolean loggingEnabled;

/** Start building a new {@link Picasso} instance. */
public Builder(@NonNull Context context) {
checkNotNull(context, "context == null");
this.context = context.getApplicationContext();
}

....

You will need to use the Builder class and pass in the Application/Activity context to initialize the Builder. And then it also removes the declaration of PicassoProvider in the AndroidManifest.xml:

1
<manifest package="com.squareup.picasso3"/>

The future

Why Picasso wants to deprecate this way? In the Picasso.java, it shows this:

1
2
3
4
5
6
7
8
9
/**
* Image downloading, transformation, and caching manager.
* <p>
* Use {@see PicassoProvider#get()} for a global singleton instance
* or construct your own instance with {@link Builder}.
*/
public class Picasso implements LifecycleObserver {
...
}

It should be reasonable to have an Activity context with Lifecycle management. That might be the reason why it obsoletes the way it’s using now.

[Reference]

  1. The Firebase Blog: How does Firebase initialize on Android?