In our last article, Java Basics, we covered some common basic questions for Java interviews. Here are some essential questions for Android development.
1, the Activity,
1.1 Life Cycle
Normally, an Activity goes through the following phases:
- OnCreate: Indicates that the Activity is being created.
- OnRestart: Indicates that the Activity is being restarted.
- OnStart: Indicates that the Activity is being started and is visible but not in the foreground.
- OnResume: Indicates that the Activity is visible and in the foreground.
- OnPause: Indicates that the Activity is being stopped (you can perform non-time-consuming operations, such as stopping animation in the save state).
- OnStop: Indicates that the Activity is about to stop (heavyweight recycling can be done).
- OnDestroy: Indicates that the Activity is about to be destroyed.
The following questions are also commonly asked about the lifecycle:
- First start: onCreate->onStart->onResume;
- Start a new Activity or return to the desktop: onPause->onStop. If the new Activity is opened with a transparent theme, onStop is not called;
- When returning to the original Activity: onRestart->onStart->onResume;
- When the return key is pressed: onPause->onStop->onDestroy
1.2 Startup Mode
There are four startup modes for an Activity: Standard, SingleTop, SingleTask, and SingleInstance.
- Standard: the Standard mode is also the default mode. A brand new instance is created for each startup.
- SingleTop: Top of stack reuse mode. In this mode, no new instance is created if the Activity is at the top of the stack. OnNewIntent will be called to receive the new request, no lower than onCreate and onStart.
- SingleTask: In-stack reuse mode. The upgraded version of singleTop, if there is an instance in the stack, is reused and all activities on that instance are cleared.
- SingleInstance: The system creates a separate stack of tasks for it, and this instance runs independently in a task that has only this instance and does not allow other activities.
1.3 Startup Process
Before we understand the Activity startup process, let’s take a look at the Android startup process. In general, the Android system startup process mainly goes through init process -> Zygote process -> SystemServer process -> various system services -> application process and so on.
- Boot power and system boot: boot chip starts execution from a predefined place (solidified in ROM) when power is pressed, loads boot program BootLoader into RAM, and then executes.
- BootLoader: BootLoader is a small program used to pull up and run the Android operating system before it starts running.
- Linux kernel startup: When the kernel starts, set the cache, protected storage, schedule list, and load drivers. When it finishes setting up the system, it looks for init.rc in the system files and starts the init process.
- Init process start: Initializes and starts the properties service, and starts the Zygote process.
- Zygote process start: Create JVM and register JNI method for it, create server side Socket, start SystemServer process.
- Start SystemServer process: Start Binder thread pools and SystemServiceManager, and start various system services.
- Launcher: AMS started by the SystemServer process starts the Launcher, which displays the shortcut icon of the installed application on the system desktop.
Reference: Android system startup process init process Start the Android system startup process Zygote process start the Android system startup process SystemServer process Start the Android system startup process start the Android system Launcher process
When the process Launcher starts, it calls the start of the Activity. First of all, the Launcher will call ActivityTaskManagerService, then ActivityTaskManagerService invokes ApplicationThread, The ApplicationThread then starts the Activity with the ActivityThread. For a complete analysis, see the Android Activity startup process
2, the fragments
2.1 introduction
Android 3.0 (API 11) contains a Fragment API that is compatible with Android 1.6. If you want to use the Fragment API in the latest version, you can use the Fragment API in Android 3.0. Need to introduce AndroidX packages.
Compared with activities, Fragments have the following characteristics:
- Modularity: Instead of writing all code in an Activity, we write code in separate fragments.
- Reusability: Multiple activities can reuse one Fragment.
- Adaptable (Adaptability) : Different layouts can be easily realized according to the screen size and screen direction of the hardware, so that the user experience is better.
Fragment has the following core classes:
- Fragment: The base class of a Fragment. Any created Fragment needs to inherit from this class.
- FragmentManager: Manages and maintains fragments. It is an abstract class, and the concrete implementation class is FragmentManagerImpl.
- FragmentTransaction: Operations such as adding or deleting fragments are performed in transaction mode. It is an abstract class, and the concrete implementation class is BackStackRecord.
2.2 Life Cycle
A Fragment must exist around an Activity, so the Activity life cycle directly affects the Fragment life cycle. The Fragment life cycle is shown below compared to the Activity life cycle.
- OnAttach () : Called when Fragment is associated with Activity. If you do not have to use a specific host Activity object, you can use this method or getContext() to get the Context object, which can be used to solve the Context reference problem. Also in this method you can get the parameters you need to create fragments using getArguments().
- OnCreate () : called when the Fragment is created.
- OnCreateView () : Creates the Fragment layout.
- OnActivityCreated () : called when the Activity completes onCreate().
- OnStart () : called when the Fragment is visible.
- OnResume () : called when the Fragment is visible and interactive.
- OnPause () : called when the Fragment is visible but not interactive.
- OnStop () : called when Fragment is not visible.
- OnDestroyView () : Called when the Fragment’s UI is removed from the view structure.
- OnDestroy () : called when the Fragment is destroyed.
- OnDetach () : called when the Fragment and Activity are unassociated.
As shown in the figure below.Here’s how the Activity lifecycle corresponds to the Fragment lifecycle methods.
2.3 Pass data to the Activity
2.3.1 Fragment Sends data to the Activity
First, define the interface in the Fragment and have the Activity implement it, as shown below.
public interface OnFragmentInteractionListener {
void onItemClick(String str);
}
Copy the code
Then, in the fragments of onAttach (), the parameter strong Context into OnFragmentInteractionListener object in the past.
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener"); }}Copy the code
2.3.2 Activity Sends data to the Fragment
When you create a Fragment, you can pass values to your Activity using setArguments(Bundle Bundle), as shown below.
public static Fragment newInstance(String str) {
FragmentTest fragment = new FragmentTest(a); Bundle bundle =new Bundle(a); bundle.putString(ARG_PARAM, str);
fragment.setArguments(bundle);// Set parameters
return fragment;
}
Copy the code
3, Service
3.1 Startup Mode
There are two ways to start a Service: startService and bindService.
StartService uses the same Service, so onStart() is executed multiple times, onCreate() is executed only once, and onStartCommand() is executed multiple times. When started with bindService, onCreate() and onBind() are called only once.
StartService is a separate Service that has nothing to do with the Activity. When bindService is started, the Service is bound to the Activity. When the corresponding Activity is destroyed, the corresponding Service is also destroyed.
3.2 Life Cycle
The following figure shows how to start a Service using startService and bindService.
3.2.1 startService
- OnCreate () : If the service has not been created, the onCreate() callback is executed after startService() is called; If the service is already running, calling startService() does not execute the onCreate() method.
- OnStartCommand () : If the startService() method of the Context is executed several times, the onStartCommand() method of the Service will be called several times.
- OnBind () : The onBind() method of a Service is abstract. The Service class is abstract, so the onBind() method must be overridden, even if we don’t need it.
OnDestory () : This method when the Service is destroyed.
public class TestOneService extends Service{
@Override
public void onCreate(a) {
Log.i("Kathy"."onCreate - Thread ID = " + Thread.currentThread().getId());
super.onCreate(a); } @Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Kathy"."onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().getId());
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i("Kathy"."onBind - Thread ID = " + Thread.currentThread().getId());
return null;
}
@Override
public void onDestroy(a) {
Log.i("Kathy"."onDestroy - Thread ID = " + Thread.currentThread().getId());
super.onDestroy();
}
}
Copy the code
3.2.2 bindService
The typical client-server pattern is between the service started by bindService and the caller. The caller is the client and the Service is the Server. There is only one Service, but there can be one or more clients bound to the Service. BindService starts a service whose life cycle is closely related to its bound client.
1. First, return an instance of type IBinder in the onBind() method of the Service. 2. The IBinder instance returned by onBInd() needs to be able to return the Service instance itself.
3.3 Service Is Not Killed
Now, due to the limitations of the system API, some common methods of not killing services are obsolete, such as the following methods.
3.3.1, in onStartCommand mode, START_STICKY is returned.
When you call context. startService to start a Service, if Android is running out of memory, it may destroy the current Service and rebuild the Service when the memory is sufficient. The behavior of a Service to be destroyed and rebuilt depends on the return value of the Service’s onStartCommand() method. Common return values are as follows.
START_NOT_STICKY: If START_NOT_STICKY is returned, the Service will not be created after the process running on it is forcibly killed by the Android system. START_STICKY: If START_STICKY is returned, the Android system will set the Service to the started state (running state) after the process running the Service is forcibly killed. However, the intent object passed in by the onStartCommand method is no longer saved, that is, information about the intent is not obtained. START_REDELIVER_INTENT: If START_REDELIVER_INTENT is returned, the Process running the Service is forcibly killed by the Android system. If START_REDELIVER_INTENT is returned, the Android system creates the Service again. And execute the onStartCommand callback method, but the difference is, The Android system saves the Intent that the Service last passed to the onStartCommand method before it was killed and passes it back to the onStartCommand method of the newly created Service so that we can read the Intent parameters.
4, BroadcastReceiver
4.1 What is a BroadcastReceiver
BroadcastReceiver Is a global system listener that listens for Broadcast messages in the system. Therefore, it is convenient for communication between system components. BroadcastReceiver is a system-level listener. It has its own process and is activated when a Broadcast matching it is sent as an Intent.
Like the other four components, BroadcastReceiver has its own separate declaration cycle, but it is different from Activities and services. When a BroadcastReceiver is released as an Intent, the system automatically creates an instance of the BroadcastReceiver and triggers its onReceive() method. When the onReceive() method is executed, the instance of the BroadcastReceiver is destroyed.
According to different latitudes, BroadcastReceivers can be divided into different categories.
- System broadcast/non-system broadcast
- Global broadcast/local broadcast
- Disordered broadcast/ordered broadcast/sticky broadcast
4.2 Basic Usage
4.2.1 Registering broadcast
The registration of broadcast is divided into static registration and dynamic registration. Static registration is done in the Mainfest manifest file, for example.
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
Copy the code
Dynamic registration is done in code, using the registerReceiver method code, for example.
val br: BroadcastReceiver = MyBroadcastReceiver()
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
}
registerReceiver(br, filter)
Copy the code
4.2.2 Sending Broadcasts
We then send the broadcast using the sendBroadcast method.
Intent().also { intent ->
intent.setAction("com.example.broadcast.MY_NOTIFICATION")
intent.putExtra("data"."Notice me senpai!")
sendBroadcast(intent)
}
Copy the code
4.2.3 Receiving Broadcasts
When sending a broadcast, we will add a send identifier, so when receiving, we can use this identifier to receive. To receive broadcasts, you need to inherit BroadcastReceiver and override the onReceive callback method to receive broadcast data.
private const val TAG = "MyBroadcastReceiver"
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
StringBuilder().apply {
append("Action: ${intent.action}\n")
append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
toString().also { log ->
Log.d(TAG, log)
Toast.makeText(context, log, Toast.LENGTH_LONG).show()}}}}Copy the code
5, ContentProvider
ContentProvider is one of the four components of Android, but it is rarely used. If you’ve seen the underlying source code, you know that ContentProviders share data with Binder. Therefore, if we need to provide data to third-party applications, we can consider using a ContentProvider implementation.
6, Android View knowledge
Android View system itself is very large, if you want to fully understand the principle of View is very difficult, we pick up some important concepts to explain to you.
6.1 Measurement Process
The drawing process of Android View itself needs to go through three processes: measure measurement, layout layout and draw drawing. Finally, it can be drawn and displayed in front of users.
Android’s MeasureSpec is split into three modes, which are EXACTLY, AT_MOST, and UNSPECIFIED.
- MeasureSpec.EXACTLY: EXACTLY, in which the value of dimension is EXACTLY how long or wide the component is.
- MeasureSpec.AT_MOST: Maximum mode, determined by the maximum space that the parent component can give.
- MeasureSpec.UNSPECIFIED: The mode is UNSPECIFIED, and the current component can use UNSPECIFIED space.
Related article: Understanding MeasureSpec
6.2 Event Distribution
Android event distribution is composed of dispatchTouchEvent, onInterceptTouchEvent and onTouchEvent.
- DispatchTouchEvent: method returns true, indicating that the event was consumed by the current view; A return of super.dispatchTouchEvent means that the event continues to be distributed, and a return of false means that onTouchEvent processing is handed to the parent class.
- OnInterceptTouchEvent: This method returns true, which means it intercepts the event and sends it to its own onTouchEvent method for consumption. Returning false means no interception and needs to continue to be passed to the subview. If return super. OnInterceptTouchEvent (ev), event interceptor in two situations: which one is the son View, without the other is a child View.
If the View has a child View and the child View is clicked, it will not be intercepted and continue to be distributed to the child View for processing, equivalent to return false. If the View has no children or children but does not click on the subview, the onTouchEvent response is sent to the View, which is equivalent to return true.
- OnTouchEvent: method returns true to indicate that the current view can process the corresponding event; A return value of false means that the event is not handled by the current view and is passed to the parent view’s onTouchEvent method for processing. If return super.onTouchEvent(EV), the event is handled in one of two ways: consume yourself or pass up.
In The Android system, there are three classes that have the ability to pass events:
- Activity: Has both distribution and consumption methods.
- ViewGroup: has distribute, intercept, and consume methods.
- View: Has distribution and consumption methods.
In event distribution, it’s sometimes asked: when does ACTION_CANCEL trigger, does touching the button and sliding outside to lift trigger the click event, or does sliding back lift?
Here’s what we need to understand about this question:
- Generally ACTION_CANCEL and ACTION_UP are used as the end of a View event. If an ACTION_UP or ACTION_MOVE is blocked in the parent View, the parent View specifies that the child View will not accept subsequent messages and will receive an ACTION_CANCEL event as soon as the parent View intercepts the message for the first time.
- ACTION_CANCEL also appears if a control is touched but not lifted over the area of the control.
- ViewGroup does not intercept any events by default. The onInterceptTouchEvent method of ViewGroup returns false by default.
- The View does not have an onInterceptTouchEvent method; onTouchEvent is called whenever a hit event is passed to it.
- OnTouchEvent consumes events by default when the View is clickable.
- ACTION_DOWN is intercepted, onInterceptTouchEvent is executed once, leaving a mark (mFirstTouchTarget == NULL) so that all subsequent ACTION_MOVE and ACTION_UP will be intercepted. `
6.3 MotionEvent
Android motionEvents mainly include the following:
- ACTION_DOWN finger just touched the screen
- ACTION_MOVE Indicates finger movement on the screen
- ACTION_UP the moment the phone is released from the screen
- ACTION_CANCEL Cancel the touch event
Here are some examples of events: Click the screen to release, the sequence of events is DOWN -> UP, click the screen to slide to release, the sequence of events is DOWN -> MOVE ->… > MOVE -> UP. Meanwhile, getX/getY returns coordinates relative to the top left corner of the current View, and getRawX/getRawY returns coordinates relative to the top left corner of the screen. TouchSlop is the minimum distance that the system recognizes as sliding. The value may vary from device to device, and can be obtained from viewConfiguration.get (getContext()).getScaledTouchSlop().
6.4 Relationship between Activity, Window, and DecorView
First, take a look at the source code for the setContentView in your Activity.
public void setContentView(@LayoutRes int layoutResID) {
// Pass the XML layout to the Window
getWindow().setContentView(layoutResID);
initWindowDecorActionBar(a); }Copy the code
As you can see, the Activity’s setContentView essentially passes the View to the Window’s setContentView() method, Windows’ setContenView creates the DecorView internally by calling the installDecor() method, as shown below.
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// Initialize the DecorView and its internal content
installDecor(a); }else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews(a); }if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...............
} else {
// Load the contentView into the DecorVoew
mLayoutInflater.inflate(layoutResID, mContentParent); }... }private void installDecor(a) {...if (mDecor == null) {
// Instantiate the DecorView
mDecor = generateDecor(- 1); . }}else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
/ / get the Content
mContentParent = generateLayout(mDecor); }... }protected DecorView generateDecor(int featureId) {...return new DecorView(context, featureId, this.getAttributes());
}
Copy the code
Call generateLayout() to get the content in the DecorView from the generateDecor() new DecorView. The Activity view is eventually added to the content in the DecorView through the inflate, but the DecorView has not yet been added to the Window. The add operation requires ViewRootImpl.
The ViewRootImpl is used to interface with Windows Manager and DecorView, After the Activity is created, the DecorView is added to the PhoneWindow via WindowManager and an instance of ViewRootImpl is created. The DecorView is then associated with ViewRootImpl. Finally, render the entire View tree by performing performTraversals() of ViewRootImpl.
6.5 Draw drawing process
The Android Draw process can be broken down into six steps:
- First, draw the background of the View.
- If necessary, keep the Canvas layer ready for FADING;
- Next, draw the contents of the View;
- Next, draw the child views of the View;
- If necessary, paint the View’s fading edges and restore the layers;
- Finally, draw the View’s decorations (such as scroll bars, etc.).
The code involved is as follows:
public void draw(Canvas canvas) {...// Step 1: Draw the View background
drawBackground(canvas); .// Step two: If necessary, keep the Canvas layer ready for FADING
saveCount = canvas.getSaveCount(a); . canvas.saveLayer(left, top, right, top + length, null, flags); .// Step 3: Draw the contents of the View
onDraw(canvas); .// Step 4: Draw a child View of the View
dispatchDraw(canvas); .// Step 5: If necessary, paint the View's fading edges and restore the layers
canvas.drawRect(left, top, right, top + length, p); . canvas.restoreToCount(saveCount); .// Step 6: Draw the View decorations (such as scroll bars, etc.)
onDrawForeground(canvas)
}
Copy the code
6.6 Requestlayout, OnLayout, onDraw, DrawChild
- RequestLayout () : Causes the measure() procedure and layout() procedure to be called, which will determine if ondraw is needed based on flag bits.
- OnLayout () : If the View is a ViewGroup object, implement this method to lay out each subview.
- OnDraw () : Draws the View itself (this method needs to be overridden for each View, ViewGroup does not need to implement this method).
- DrawChild () : To re-call the draw() method of each subview.
6.7 Invalidate() and postInvalidate(
Invalidate() and postInvalidate() are both used to refresh views. The main difference is that invalidate() is called in the main thread, and if used in child threads, a handler is required. PostInvalidate () can be called directly from a child thread.
7. Android processes
7.1 concept
Process is a running activity of a program on a data set in a computer. It is the basic unit of resource allocation and scheduling in the system and the basis of operating system structure.
When an application is first started, Android starts a LINUX process and a main thread. By default, all components of the program will run in the process and thread. At the same time, Android assigns a separate LINUX user to each application. Android tries to keep one running process as long as possible. It only tries to stop some processes when memory resources run low to free up enough resources for new processes and to ensure that the current process the user is accessing has enough resources to respond to the user’s events in a timely manner.
We can run some components in other processes, and we can add threads to any process. The process in which the component is running is set in the manifest file, where,, and have a process attribute that specifies the process in which the component is running. We can set this property so that each component runs in its own process, or several components share a process, or not. The element also has a process property that specifies the default properties for all components.
All components in Android are instantiated in the main thread of the specified process, and system calls to components are also made by the main thread. No new threads are created per instance. Methods that respond to system calls — such as view.onkeyDown (), which performs user actions, and component lifecycle functions — run in this main thread. This means that this component cannot block the main thread for long periods of time when the system calls it. For example, if network operations or UI updates take a long time to run, they cannot be run directly in the main thread, because this will block other components in the process, which can be allocated to run in a new thread or another thread.
7.2 Process Life Cycle
According to the different life cycle, The Android process can be divided into foreground process, background process, visible process, service process and empty process.
Foreground process
Foreground processes are the processes that the user is currently using. Some foreground processes can exist at any time, but they can also be destroyed when memory is low. In this case, the device performs memory scheduling, aborting some foreground processes to remain responsive to user interactions.
It is a foreground process if:
- Hosting the Activity the user is interacting with (the Activity’s onResume() method has been called)
- Hosts a Service that is bound to the Activity the user is interacting with
- Host Service running “foreground” (Service called startForeground())
- Hosting a Service (onCreate(), onStart(), or onDestroy()) that is performing a lifecycle callback
- Host the BroadcastReceiver that is executing its onReceive() method
Visible process
A visible process is one that does not contain foreground components but displays a visible process on the screen.
A visible process is one of the following:
- Hosts an Activity (whose onPause() method has been called) that is not in the foreground but is still visible to the user. For example, this might happen if the RE foreground Activity starts a dialog box that allows the previous Activity to be displayed after it.
- Hosts a Service bound to a visible (or foreground) Activity.
Service process
A Service started by the startService() method. This Service is less important than the above two processes and generally follows the application life cycle.
In general, a service that is started using the startService() method and does not fall into either of the higher categories is a service process.
Background processes
The process that contains an Activity that is currently invisible to the user (the Activity’s onStop() method has been called). There are usually many background processes running, so they are saved in the LRU (Least Recently used) list to ensure that the process containing the Activity the user recently viewed is the last to terminate.
An empty process
A process that does not contain any active application components. The sole purpose of keeping such processes is to be used as a cache to reduce the startup time needed to run components in it the next time. To balance overall system resources between the process cache and the underlying kernel cache, systems often kill these processes.
7.3 multiple processes
First of all, a process generally refers to a unit of execution, which on mobile devices is a program or application, and what we call multi-process (IPC) in Android generally refers to an application containing multiple processes. The reason for using multiple processes is twofold: some modules need to run in a separate process because of special requirements; Increase the memory available to your application.
There is only one way to enable multiple processes in Android, which is to specify the Android: Process property when registering Service, Activity, Receiver, and ContentProvider in androidmanifest.xml, as shown below.
<service
android:name=".MyService"
android:process=":remote">
</service>
<activity
android:name=".MyActivity"
android:process="com.shh.ipctest.remote2">
</activity>
Copy the code
As you can see, the Android: Process attribute values specified by MyService and MyActivity are different. The differences are as follows:
- :remote: The system will attach the package name to the name of the current process. The complete process name is com.shh.ipctest:remote. The process that starts with: belongs to the private process of the current application.
- Remote2: Com.shh.ipctest. remote2: This is a complete naming method without any package name attached. Other applications that share the same process ShareUID and signature can run in the same process to implement data sharing.
However, starting multiple processes can cause the following problems, which must be noted:
- Static member and singleton patterns fail
- The thread synchronization mechanism fails
- SharedPreferences reliability deteriorates
- Application is created multiple times
For the first two problems, it can be understood that in Android, the system allocates independent virtual machines for each application or process, and different virtual machines naturally occupy different memory address Spaces. Therefore, objects of the same class will produce different copies, which leads to the failure of data sharing and the inevitable failure of thread synchronization.
Because SharedPreferences are based on reading and writing XML files, concurrent reading and writing of multiple processes may lead to data anomalies.
Application created multiple times Similar to the previous two problems, when the system allocates multiple VMS, the same Application is restarted multiple times, which inevitably causes the Application to be created multiple times. To prevent unnecessary repeated initialization in the Application, you can use the process name to filter. Let only the specified process perform global initialization, as shown below.
public class MyApplication extends Application{
@Override
public void onCreate(a) {
super.onCreate(a); String processName ="com.xzh.ipctest";
if (getPackageName().equals(processName)){
// do some init}}}Copy the code
7.4 Multi-process Communication Mode
Currently, Android supports the following multi-process communication modes:
- AIDL: Powerful, supports one-to-many real-time concurrent communication between processes, and implements RPC (remote procedure call).
- Messenger: Supports one-to-many serial real-time communication, a simplified version of AIDL.
- Bundle: The process communication method of the four components. Only the data types supported by the Bundle can be transmitted.
- ContentProvider: Powerful data source access support, mainly support CRUD operations, one-to-many inter-process data sharing, such as our application access system address book data.
- BroadcastReceiver: a system that broadcasts only one-way messages, while the receiver passively receives messages.
File sharing: Simple data is shared without high concurrency.
- Socket: Transmits data over the network.
8. Serialization
8.1 Parcelable and Serializable
- Serializable uses I/O read and write storage on the hard disk, while Parcelable is directly read and write in memory.
- Serializable will use reflection, serialization and deserialization process requires a lot of I/O operations, Parcelable own implementation of marshalling and unmarshalling (reflection) operation, data is also stored in Native memory, It’s much faster.
8.2 the sample
The Serializable instances:
import java.io.Serializable;
class serializableObject implements Serializable {
String name;
public serializableObject(String name) {
this.name = name;
}
public String getName(a) {
returnname; }}Copy the code
Parcelable instances:
import android.os.Parcel;
import android.os.Parcelable;
class parcleObject implements Parcelable {
private String name;
protected parcleObject(Parcel in) {
this.name = in.readString(a); }public parcleObject(String name) {
this.name = name;
}
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
public static final Creator<parcleObject> CREATOR = new Creator<parcleObject>() {
@Override
public parcleObject createFromParcel(Parcel in) {
return new parcleObject(in);
}
@Override
public parcleObject[] newArray(int size) {
return newparcleObject[size]; }}; @Override
public int describeContents(a) {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name); }}Copy the code
When using Parcelable, the following methods are commonly used:
- CreateFromParcel (Parcel in) : Creates the original object from the serialized object.
- NewArray (int size) : Creates an array of raw objects of specified length.
- User(Parcel in) creates the original object from the serialized object.
- WriteToParcel (Parcel Dest, int flags) : writes the current object to a serialization structure where flags identifies either 0 or 1. A value of 1 indicates that the current object needs to be returned as a return value and that resources cannot be released immediately. In almost all cases, it is 0.
- DescribeContents: Returns the description of the current object. Returns 1 if there is a file descriptor, 0 otherwise, and 0 in almost all cases.
9, Windows
9.1 Basic Concepts
Window is an abstract class whose concrete implementation is PhoneWindow. WindowManager is the entrance for the outside world to access Windows. The concrete implementation of Windows is located in WindowManagerService. The interaction between WindowManager and WindowManagerService is an IPC process. All views in Android are rendered through Windows, so Windows is actually the direct manager of the View.
According to different functions, Windows can be divided into the following types:
- Application Window: corresponds to an Activity;
- Sub Window: cannot exist alone, can only be attached to the parent Window, such as Dialog, etc.
- System Window: requires permission declarations, such as Toast and System status bars;
9.2 Internal Mechanism
Window is an abstract concept, and each Window corresponds to a View and a ViewRootImpl. The Window doesn’t really exist; it exists as a View. Access to Windows must be through WindowManager, WindowManager implementation class is WindowManagerImpl, source code as follows:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
Copy the code
WindowManagerImpl does not directly implement the three operations of Window. Instead, it is all handled by WindowManagerGlobal. WindowManagerGlobal provides its own instance in the form of a factory, involving the following code:
/ / add
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {...// Some layout parameters need to be adjusted for the child Window
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if(parentWindow ! = null) { parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else{···} ViewRootImpl root; View panelParentView = null;synchronized (mLock) {
// Create a new Viewrotimpl and update the Window with its setView... the root =new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throwe; }}}/ / delete
@UnsupportedAppUsage
public void removeView(View view, boolean immediate) {...synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView(a);removeViewLocked(index, immediate); ...}}private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView(a);if(view ! = null) { InputMethodManager imm = InputMethodManager.getInstance(a);if(imm ! = null) { imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if(view ! = null) { view.assignParent(null);
if (deferred) {
mDyingViews.add(view); }}}/ / update
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {...final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false); }}Copy the code
10. Message mechanism
Android messaging is mainly about the Handler mechanism.
10.1 Handler mechanism
Handler has two main uses:
- Schedule messages and runnables to be executed at some point in the future;
- Queue operations to be performed on a thread other than your own. (Keep the UI safe while updating it concurrently in multiple threads.)
Android states that UI access can only be done in the main thread, because Android UI controls are not thread-safe and concurrent access by multiple threads can cause UI controls to be in an unexpected state. Why doesn’t the system lock access to UI controls? There are two disadvantages: locking complicates UI access logic; Secondly, locking mechanism will reduce the efficiency of UI access. If a child thread accesses the UI, the program throws an exception. To ensure thread-safety, the ViewRootImpl validates the UI operation, which is done by the ViewRootImpl checkThread method.
void checkThread(a) {
if(mThread ! = Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views."); }}Copy the code
When you talk about the Handler mechanism, you usually include the following three objects:
- Message: The Message object received and processed by the Handler.
- MessageQueue: queue of messages, first in, first out, each thread can have a maximum of one.
- Looper: The message pump is the manager of the MessageQueue. It continuously retrieves messages from the MessageQueue and distributes the messages to the corresponding Handler. Each thread has only one Looper.
Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Handler Since the default UI main thread is an ActivityThread, Looper is initialized when an ActivityThread is created, which is why we can use handlers in the main thread by default.
10.2 Working Principles
ThreadLocal A ThreadLocal is an internal data store class that allows you to store data in a specified thread that cannot be accessed by other threads. ThreadLocal is used in Looper, ActivityThread, and AMS. When different threads access the same ThreadLocal get method, the internal ThreadLocal will fetch an array from each thread, and then search the corresponding value from the array according to the current ThreadLcoal index, source code is as follows:
public void set(T value) {
Thread t = Thread.currentThread(a); ThreadLocalMap map =getMap(t);
if(map ! = null) map.set(this, value);
else
createMap(t, value); }...public T get(a) {
Thread t = Thread.currentThread(a); ThreadLocalMap map =getMap(t);
if(map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! = null) { @SuppressWarnings("unchecked")
T result = (T)e.value;
returnresult; }}return setInitialValue(a); }Copy the code
MessageQueue MessageQueue consists of two operations: insert and read. The read operation itself is accompanied by a delete operation, and the insert and read methods are enqueueMessage and Next, respectively. The internal implementation of MessageQueue is not a queue, but actually maintains a list of messages through a single linked list data structure. The next method is an infinite loop and blocks if there are no messages in the message queue. When a new message arrives, the next method puts it back and removes it from the singly linked list.
boolean enqueueMessage(Message msg, long when) {...synchronized (this) {... MSG.markInUse(a); msg.when = when; Message p = mMessages; boolean needWake;if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous(a); Message prev;for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr ! = 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr); }}return true; }...Message next(a) {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported....for(;;) {...synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis(a); Message prevMsg = null; Message msg = mMessages;if(msg ! = null && msg.target == null) {// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while(msg ! = null && ! msg.isAsynchronous());
}
if(msg ! = null) {if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if(prevMsg ! = null) { prevMsg.next = msg.next; }else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse(a);returnmsg; }}else {
// No more messages.
nextPollTimeoutMillis = - 1; }...}// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle(a); }catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if(! keep) {synchronized (this) {
mIdleHandlers.remove(idler); }}}// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0; }}Copy the code
Looper Looper will constantly check MessageQueue to see if there is a new message, if there is a new message will be processed immediately, otherwise it will always block, Looper source code.
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread(a); }Copy the code
You can create a Looper for the current thread with looper.prepare (). By default, the Activity creates a Looper object.
In addition to the prepare method, Looper also provides a prepareMainLooper method, which is used to create loOpers for activityThreads. Since the main thread Looper is special, Looper provides a getMainLooper method to get the main thread Looper.
Meanwhile, Looper provides the quit and quitSafely command to exit a Looper. The difference between quit and quitSafly is that quit directly exits the Looper, whereas quitSafly only sets an exit flag and processes the existing messages in the message queue before safely exiting. After Looper exits, messages sent by Handler fail. In this case, Handler’s send method returns false. Therefore, Looper needs to be terminated when it is not needed.
Handler The Handler sends and receives messages. Messages can be sent through a series of post/send methods, and post is ultimately implemented through send.
RecyclerView optimization
In Android development, often encounter the problem of long lists, so many times, will involve the optimization of RecyclerView. For the list of reasons, there are usually the following:
11.1 Stuck scenario
NotifyDataSetChanged If data needs to be refreshed globally, you can use notifyDataSetChanged. For adding or subtracting data, you can use a local flush, as shown below.
void onNewDataArrived(List<News> news) {
List<News> oldNews = myAdapter.getItems(a); DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news));
myAdapter.setNews(news);
result.dispatchUpdatesTo(myAdapter);
}
Copy the code
RecycleView nesting In the actual development, often see the vertical rolling RecycleView nesting a horizontal rolling RecycleView scene. Since each RecycleView has an independent itemView object pool, the shared object pool can be set for nesting, as follows.
class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool(a); . @Override
public void onCreateViewHolder(ViewGroup parent, int viewType) {
// inflate inner item, find innerRecyclerView by ID...
LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(),
LinearLayoutManager.HORIZONTAL);
innerRv.setLayoutManager(innerLLM);
innerRv.setRecycledViewPool(mSharedPool);
return new OuterAdapter.ViewHolder(innerRv);
}
Copy the code
You can use the Systemtrace tool to check Layout performance. If it takes too long or is called too many times, you need to check whether you are using too many RelativeLayout or nesting multiple layers of LinearLayout. Each layer Layout causes its child to measure/ Layout multiple times.
Object allocation and garbage collection Although ART is used on Android 5.0 to reduce GC pauses, it still causes stalling. Try to avoid creating objects in loops that lead to GC. Remember, memory is allocated to create objects, and the time machine checks to see if there is enough memory to determine if GC is necessary.
11.2 Other Optimization Strategies
In addition to the above typical scenarios, the optimization of RecyclerView also needs to pay attention to the following points:
- Upgrade RecycleView version to 25.1.0 and above to use Prefetch function;
- Through rewriting RecyclerView. OnViewRecycled (holder) to recycle resources;
- If the Item is fixed, you can use RecyclerView. SetHasFixedSize (true); To prevent requestLayout from wasting resources;
- Set a listener for ItemView. Instead of calling addXxListener for each Item, we should use a common XxListener to perform different operations according to the ID. This will optimize the resource consumption caused by frequent object creation.
- Try not to use opacity changes on the ViewHolder;
- Added a preloading policy.