Android Context is completely parsed, and you do not know the details of Context.
In the previous articles, I wrote a trilogy of the ListView series. Although it can be said that it is an absolute essence of the content, many of my friends said they could not understand it. Well, this series not only gave everyone a hard time, but also gave me a hard time. I spent more than half a month writing the Demo of the waterfall stream ListView. In this article, we will talk about something simple. Instead of analyzing the complex source code, we will talk about the Context that everyone is familiar.
Context is believed to be common to all Android Developers every day. However, this does not mean that Context has nothing to talk about. In fact, Context has too many small details that are not noticed by everyone, so today we will learn the details you do not know.
Context type
We know that Android applications are all written in Java, so you can think about the biggest difference between an Android program and a Java program? What is the division of boundaries? In fact, it is easy to analyze. Unlike Java programs, the Android program creates a class and writes a main () method to run it. Instead, it must have a complete Android engineering environment, in this environment, we have system components such as Activity, Service, BroadcastReceiver, and these components can not be used to create instances in a new way like a common Java object, instead, we need to have their own Context, that is, the Context discussed here. In this case, Context is a core function class that maintains that all components in the Android program can work normally.
Let's take a look at the inheritance structure of Context:
The inheritance structure of Context is a little complicated. As you can see, there are two directly affiliated sub-classes: ContextWrapper and ContextImpl. From the name, we can see that ContextWrapper is the encapsulation class of context functions, while ContextImpl is the implementation class of context functions. ContextWrapper has three direct subclasses: ContextThemeWrapper, Service, and Application. ContextThemeWrapper is an encapsulation class with themes, and its direct subclass is Activity.
Here we can see at least a few familiar faces, such as Activity, Service, and Application. As a result, we can conclude that there are three types of Context: Application, Activity, and Service. Although these three classes play different roles, they all belong to one type of Context, and their specific Context functions are implemented by the ContextImpl class.
So what functions can Context implement? This is really too much. Context is used to bring up Toast, start Activity, start Service, send broadcast, operate database, and so on. Because the specific capabilities of Context are implemented by the ContextImpl class, in most scenarios, the three types of Context, Activity, Service, and Application, can be universal. However, there are several special scenarios, such as starting the Activity and displaying the Dialog. For security reasons, Android does not allow the appearance of Activity or Dialog. the startup of an Activity must be based on another Activity, that is, the returned stack. Dialog must be displayed on an Activity (unless it is a System Alert type diert). Therefore, in this scenario, we can only use the Context of the Activity type, otherwise an error will occur.
Number of Context
How many Context exists in an application? In fact, we can get the answer based on the Context Type above. Context has three types: Application, Activity, and Service. Therefore, the formula for calculating the number of Context in an Application can be written as follows:
Context COUNT = Activity count + Service count + 1
The above 1 represents the number of applications, because one Application can have multiple activities and multiple services, but only one Application.
Application Context Design
Basically, each Application has its own Application, which inherits from the system's Application class, and then encapsulates some common operations in its own Application class. In fact, this is not a method recommended by Google, because we only use Application as a common tool class, in fact, using a simple Singleton class can also implement the same function. However, according to my observations, too many projects use the Application in this way. Of course, this practice does not have any side effects, but it just shows that many people still lack understanding of the Application. Here we will first analyze the Application design, introduce some details that you do not know, and then look at the problem of using the Application at ordinary times.
Create a new MyApplication and let it inherit from the Application. Then, specify MyApplication in the AndroidManifest. xml file, as shown below:
......
After this parameter is specified, the Android system will create a MyApplication instance when our program starts. If this parameter is not specified, an Application instance will be created by default.
As mentioned above, many applications are currently used as common tool classes. How can we obtain its instances as a common tool class? As follows:
public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);MyApplication myApp = (MyApplication) getApplication();Log.d(TAG, getApplication is + myApp);}}
As you can see, the code is very simple. You only need to call the getApplication () method to get the instance of our custom Application. The print result is as follows:
In addition to the getApplication () method, there is actually a getApplicationContext () method. The two methods seem to be somewhat correlated. What are their differences? Let's modify the code:
public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);MyApplication myApp = (MyApplication) getApplication();Log.d(TAG, getApplication is + myApp);Context appContext = getApplicationContext();Log.d(TAG, getApplicationContext is + appContext);}}
Similarly, we printed the getApplicationContext () result and re-run the code, as shown in:
Why? It seems that the printed results are the same, and the memory addresses after the connection are the same. It seems that they are the same object. In fact, this result is also very understandable, because as we have mentioned earlier, the Application itself is a Context, so the result obtained from getApplicationContext () is the instance of MyApplication itself.
Some may ask, since the results of these two methods are the same, why does Android provide two methods with repeated functions? In fact, these two methods have a big difference in scope. The getApplication () method has very strong semantics. It can be used to obtain the Application instance at first glance, but this method can be called only in Activity and Service. In most cases, we may use the Application in the Activity or Service. However, if we want to obtain the Application instance in some other scenarios, such as BroadcastReceiver, you can use the getApplicationContext () method as follows:
public class MyReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {MyApplication myApp = (MyApplication) context.getApplicationContext();Log.d(TAG, myApp is + myApp);}}
That is to say, the getApplicationContext () method has a wider scope. For any Context instance, we can obtain our Application object by calling the getApplicationContext () method.
So more careful friends will find that in addition to the two methods, there is actually a getBaseContext () method. What is this baseContext? We can verify it by printing:
Oh? This time we get different objects. The getBaseContext () method gets a ContextImpl object. Does this ContextImpl seem familiar? Let's take a look at the inheritance structure of Context. ContextImpl is the implementation class of Context functions. That is to say, classes such as Application and Activity do not actually implement the Context function, but only implement an interface encapsulation. The specific functions of Context are completed by the ContextImpl class. So how is this design implemented? Let's take a look at the source code. Because Application, Activity, and Service are directly or indirectly inherited from ContextWrapper, we can directly look at the source code of ContextWrapper, as shown below:
/** * Proxying implementation of Context that simply delegates all of its calls to * another Context. Can be subclassed to modify behavior without changing * the original Context. */public class ContextWrapper extends Context { Context mBase; /** * Set the base context for this ContextWrapper. All calls will then be * delegated to the base context. Throws * IllegalStateException if a base context has already been set. * * @param base The new base context for this wrapper. */ protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException(Base context already set); } mBase = base; } /** * @return the base context as set by the constructor or setBaseContext */ public Context getBaseContext() { return mBase; } @Override public AssetManager getAssets() { return mBase.getAssets(); } @Override public Resources getResources() { return mBase.getResources(); } @Override public ContentResolver getContentResolver() { return mBase.getContentResolver(); } @Override public Looper getMainLooper() { return mBase.getMainLooper(); } @Override public Context getApplicationContext() { return mBase.getApplicationContext(); } @Override public String getPackageName() { return mBase.getPackageName(); } @Override public void startActivity(Intent intent) { mBase.startActivity(intent); } @Override public void sendBroadcast(Intent intent) { mBase.sendBroadcast(intent); } @Override public Intent registerReceiver( BroadcastReceiver receiver, IntentFilter filter) { return mBase.registerReceiver(receiver, filter); } @Override public void unregisterReceiver(BroadcastReceiver receiver) { mBase.unregisterReceiver(receiver); } @Override public ComponentName startService(Intent service) { return mBase.startService(service); } @Override public boolean stopService(Intent name) { return mBase.stopService(name); } @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { return mBase.bindService(service, conn, flags); } @Override public void unbindService(ServiceConnection conn) { mBase.unbindService(conn); } @Override public Object getSystemService(String name) { return mBase.getSystemService(name); } ......}
Since there are still many methods in ContextWrapper, I conducted some filtering and only pasted some methods. The above methods are familiar to everyone. getResources (), getPackageName (), getSystemService (), and so on are all frequently used methods. So what are the implementations of all these methods? In fact, all methods in ContextWrapper are implemented in a uniform way, that is, the method corresponding to the current method name in the mBase object is called.
So what is this mBase object? Let's take a look at the attachBaseContext () method of Line 1. A base parameter is passed in this method and this parameter is assigned to the mBase object. The attachBaseContext () method is actually called by the system. It will pass the ContextImpl object as a parameter to the attachBaseContext () method and assign the value to the mBase object, after that, all the methods in ContextWrapper are actually implemented by ContextImpl through this delegated mechanism. Therefore, ContextImpl is the implementation class of context functions, which is very accurate.
In addition, let's take a look at the printed getBaseContext () method, which is in row 26th. This method only has one line of code, that is, it returns the mBase object, and the mBase object is actually the ContextImpl object. Therefore, the printed result just now has been confirmed.
Application Problems
Although the Application usage is indeed very simple, there are many misuse scenarios in our daily development work, let's take a look at what is more likely to make mistakes today.
Application is one of the Context types. Does it mean that, as long as it is an Application instance, various methods of Context can be used at any time? Let's try this experiment:
public class MyApplication extends Application {public MyApplication() {String packageName = getPackageName();Log.d(TAG, package name is + packageName);}}
This is a very simple custom Application. In the MyApplication constructor, we obtain the package name of the current Application and print it out. The getPackageName () method is used to obtain the package name. This method is provided by Context. Can the above Code run normally? You will see the following results after you run it:
The application crashes immediately upon startup, and a null pointer exception is reported. It seems like a simple piece of code. How can it become a null pointer? However, if you try to change the code to the following statement, you will find everything is normal:
public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();String packageName = getPackageName();Log.d(TAG, package name is + packageName);}}
The running result is as follows:
The method that calls Context in the constructor will crash. It is normal to call the Context method in the onCreate () method. What happened between the two methods? Let's review the source code of the ContextWrapper class. There is an attachBaseContext () method in ContextWrapper. This method will assign a value to the incoming Context parameter to the mBase object, and then the mBase object will have a value. We know that all the Context Methods call the same name method of this mBase object. That is to say, if the mBase object has not been assigned a value, when any method in the Context is called, a null pointer exception occurs. This is the case with the above Code. Shows the execution sequence of methods in Application:
It is recommended that you initialize global variable data in the onCreate () method in the Application, but if you want to advance the initialization time to the extreme, you can also rewrite the attachBaseContext () method as follows:
Public class MyApplication extends Application {@ Overrideprotected void attachBaseContext (Context base) {// calling the Context method here will crash super. attachBaseContext (base); // The Context method can be called normally here }}
The above is a point we need to pay attention to when using the Application. Next we will introduce another very common misuse of the Application.
In fact, Android officially does not recommend that you use custom applications. Basically, you only need to perform global initialization to use custom applications. The official documentation is as follows:
However, as far as my observations are concerned, the usage of the custom Application can basically reach 100%, that is, we may not use it when writing the test demo by ourselves, almost all formal projects use custom applications. However, many projects do not properly use custom applications. As stated in the official document, most projects only regard custom applications as a common tool class, however, this function does not need to be implemented using the Application. The use of Singleton may be a more standard method.
However, the custom Application does not have any side effects. It can achieve the same functionality as the singleton mode, but I have seen some projects, the user-defined Application and Singleton mode will be mixed for use, which will make people stunned. A typical example is as follows:
public class MyApplication extends Application {private static MyApplication app;public static MyApplication getInstance() {if (app == null) {app = new MyApplication();}return app;}}
Like the singleton mode, a getInstance () method is provided here to obtain the MyApplication instance. With this instance, you can call various tool methods in MyApplication.
But is this correct? This is a big mistake! Because we know that the Application belongs to the system component, and the instance of the system component needs to be created by the system. If we create a new MyApplication instance by ourselves, it is just a common Java object and does not have any Context capabilities. Many people tell me how to use it.LitePalThe NULL pointer error occurs because it is a common Java object that you provide to LitePal. It cannot be used to perform Context operations.
So if you really want to provide a method to get the MyApplication instance, what is the standard method? In fact, we only need to keep in mind that there is only one Application in the world, and it is already a singleton. You do not need to use Singleton mode to protect multiple instances. The Code is as follows:
public class MyApplication extends Application {private static MyApplication app;public static MyApplication getInstance() {return app;}@Overridepublic void onCreate() {super.onCreate();app = this;}}
The getInstance () method can be provided as usual, but no logical judgment is required in it. You can directly return the app object. What is the app object? In the onCreate () method, we assign the app object to this. this is the current Application instance, and the app is the current Application instance.
Well, here is the introduction of Context. The content is easy to understand. I hope you can understand more details about Context through this article, do not make some low-level errors when using Context.