9

In the last article Android Interview Must Ask Java Basics article, we introduced some common basic interview questions of Java interview, let us introduce some essential knowledge points of Android development.

1,Activity

1.1 Life cycle

Under normal circumstances, Activity will go through the following stages:

  • onCreate: Indicates that the Activity is being created.
  • onRestart: Indicates that the Activity is being restarted.
  • onStart: Indicates that the Activity is being started, it is already visible at this time, but it does not appear in the foreground and cannot be interacted with.
  • onResume: Indicates that the Activity is already visible and in the foreground.
  • onPause: Indicates that the Activity is stopping (you can do a non-time-consuming operation such as saving the state and stopping the animation).
  • onStop: Indicates that the Activity is about to stop (heavy-weight recycling can be performed)
  • onDestroy: Indicates that the Activity is about to be destroyed.

在这里插入图片描述
For the life cycle, the following questions are usually asked:

  • First start: onCreate->onStart->onResume;
  • Open a new Activity or return to the desktop: onPause->onStop. If opening a new Activity is a transparent theme, onStop will not be 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 Activity: Standard, SingleTop, SingleTask and SingleInstance.

  • Standard : Standard mode, which is also the default mode. A brand new instance is created every time it starts.
  • SingleTop : Stack top multiplexing mode. In this mode, if Activity is at the top of the stack, no new instance will be created. onNewIntent will be called to receive new request information, and onCreate and onStart will no longer be used.
  • SingleTask : In-stack multiplexing mode. In the upgraded version of singleTop, if there is an instance in the stack, it will be reused and all the activities on the instance will be cleared.
  • SingleInstance : The system will create a separate task stack for it, and this instance runs independently in a task. This task has only this instance and no other Activity is allowed (it can be understood as only one in the phone).

1.3 Start-up process

Before understanding the startup process of Activity, let's take a look at the startup process of the Android system. In general, the Android system startup process mainly goes through the init process -> Zygote process -> SystemServer process -> various system services -> application process and other stages.

  1. start power and system start : when the power is pressed, the boot chip starts execution from a predefined place (solidified in ROM), loads the bootloader BootLoader to RAM, and then executes.
  2. BootLoader : BootLoader is a small program before the Android system starts to run, mainly used to pull the system OS up and run.
  3. Linux kernel startup : When the kernel starts, set the cache, protected memory, plan list, and load the driver. When it completes the system settings, it will first look for the init.rc file in the system files and start the init process.
  4. init process starts : Initialize and start the attribute service, and start the Zygote process.
  5. Zygote process starts : Create a JVM and register the JNI method for it, create a server-side Socket, and start the SystemServer process.
  6. SystemServer process start : start the Binder thread pool and SystemServiceManager, and start various system services.
  7. Launcher starts : The AMS started by the SystemServer process will start the Launcher. After the Launcher starts, the shortcut icons of the installed applications will be displayed on the system desktop.

reference:
The init process of the Android system startup process starts
The Zygote process of the Android system startup process starts
The SystemServer process of the Android system startup process starts
Launcher process start of Android system startup process

After the Launcher process is started, it will call the start of Activity. First, Launcher will call ActivityTaskManagerService, then ActivityTaskManagerService will call ApplicationThread, and then ApplicationThread will start Activity through ActivityThread. For a complete analysis, please refer to Android’s Activity Start Process

2,Fragment

2.1 Introduction

Fragment is proposed by Android 3.0 (API 11). In order to be compatible with lower versions, a set of Fragment API is also developed in the support-v4 library, which is compatible with Android 1.6 at least. If you want to use Fragment in the latest version, you need to introduce the AndroidX package. .

Compared with Activity, Fragment has the following characteristics:

  • Modularity : We don't have to write all the code in the Activity, but write the code in their respective Fragments.
  • Reusability : Multiple Activities can reuse a Fragment.
  • : According to the screen size and orientation of the hardware, different layouts can be easily implemented, so that the user experience is better.

Fragment has the following core classes:

  • Fragment : The base class of Fragment. Any Fragment created needs to inherit this class.
  • FragmentManager : Manage and maintain Fragment. He is an abstract class, and the concrete implementation class is FragmentManagerImpl.
  • FragmentTransaction : Operations such as adding and deleting Fragments need to be performed in a transaction mode. It is an abstract class, and the concrete implementation class is BackStackRecord.

2.2 Life cycle

Fragment must be dependent on Activity and exist, so the life cycle of Activity will directly affect the life cycle of Fragment. Compared to the life cycle of Activity, the life cycle of Fragment is as follows.

  • onAttach() : Called when Fragment and Activity are associated. If you don't have to use a specific host Activity object, you can use this method or getContext() to get the Context object to solve the problem of Context context reference. At the same time, you can also get the parameters that need to be created when the Fragment is created through getArguments() in this method.
  • onCreate() : Called when the Fragment is created.
  • onCreateView() : Create the layout of the Fragment.
  • 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 not interactive but visible.
  • onStop() : Called when the Fragment is not visible.
  • onDestroyView() : Called when the UI of the Fragment is removed from the view structure.
  • onDestroy() : Called when the Fragment is destroyed.
  • onDetach() : Called when Fragment and Activity are disassociated.

As shown below.
在这里插入图片描述
The following is the corresponding relationship between the life cycle of Activity and the life cycle methods of Fragment.
在这里插入图片描述

2.3 Passing data with Activity

2.3.1 Fragment passes data to Activity

First, define the interface in Fragment and let the Activity implement the interface, as shown below.

public interface OnFragmentInteractionListener {
    void onItemClick(String str);  
}

Then, in the onAttach() of Fragment, the parameter Context is forced to be passed to the OnFragmentInteractionListener object.

public void onAttach(Context context) {
    super.onAttach(context);
    if (context instanceof OnFragmentInteractionListener) {
        mListener = (OnFragmentInteractionListener) context;
    } else {
        throw new RuntimeException(context.toString()
                + " must implement OnFragmentInteractionListener");
    }
}

2.3.2 Activity passes data to Fragment

When creating a Fragment, you can pass the value to the Activity through setArguments(Bundle bundle), as shown below.

 public static Fragment newInstance(String str) {
        FragmentTest fragment = new FragmentTest();
        Bundle bundle = new Bundle();
        bundle.putString(ARG_PARAM, str);
        fragment.setArguments(bundle);//设置参数
        return fragment;
    }

3, Service

3.1 Startup method

There are two main ways to start Service, namely startService and bindService.

Among them, StartService uses the same Service, so onStart() will be executed multiple times, onCreate() will be executed only once, and onStartCommand() will be executed multiple times. When using bindService to start, both onCreate() and onBind() will only be called once.

When starting with startService, a service is opened separately, which has nothing to do with the Activity. When the bindService method is started, the Service will be bound to the Activity. When the corresponding activity is destroyed, the corresponding Service will also be destroyed.

3.2 Life cycle

The following figure is a schematic diagram of starting Service in two ways: startService and bindService.
在这里插入图片描述

3.2.1 startService

  • onCreate() : If the service has not been created, the onCreate() callback will be executed after calling startService(); if the service is already running, calling startService() will not execute the onCreate() method.
  • onStartCommand() : If the startService() method of Context is executed multiple times, then the onStartCommand() method of Service will be called multiple times accordingly.
  • onBind( ): The onBind() method in Service is an abstract method, and the Service class itself is an abstract class, so the onBind() method must be rewritten, even if we don’t use it.

onDestory() : This method is used when destroying the Service.

public class TestOneService extends Service{

    @Override
    public void onCreate() {
        Log.i("Kathy","onCreate - Thread ID = " + Thread.currentThread().getId());
        super.onCreate();
    }

    @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() {
        Log.i("Kathy", "onDestroy - Thread ID = " + Thread.currentThread().getId());
        super.onDestroy();
    }
}

3.2.2 bindService

Between the service started by bindService and the caller is a typical Client-Server model. The caller is the client, and the service is the server. There is only one Service, but there can be one or many Clients bound to the Service. The life cycle of the bindService startup service is closely related to the client it is bound to.

1. First, return an instance of the IBinder type in the onBind() method of the Service.
2. The IBinder instance returned by the onBInd() method 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 ways of not being killed Service are outdated, for example, the following are some of the previous ways.

3.3.1, in onStartCommand mode, return to START_STICKY.

When calling the Context.startService method to start a Service, if Android faces a memory shortage, it may destroy the currently running Service, and rebuild the Service when the memory is sufficient. The behavior of the Service being forcibly destroyed and rebuilt again by the Android system 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, it means that the service will not be recreated after the process running by the Service is forcibly killed by the Android system.
START_STICKY : If it returns START_STICKY, it means that after the process running by the Service is forcibly killed by the Android system, the Android system will still set the Service to the started state (that is, the running state), but no longer save the intent object passed by the onStartCommand method. That is, no information about the intent can be obtained.
START_REDELIVER_INTENT : If START_REDELIVER_INTENT is returned, it means that the process running by the Service is forcibly killed by the Android system. Similar to the case of returning to START_STICKY, the Android system will recreate the Service again and execute the onStartCommand callback method, but the difference is Android The system will again retain the Intent that was passed into the onStartCommand method for the last time before the Service was killed and pass it into the onStartCommand method of the recreated Service again, so that we can read the intent parameters.

4, BroadcastReceiver

4.1 What is BroadcastReceiver

BroadcastReceiver, broadcast receiver, it is a system-wide listener, used to monitor the system-wide Broadcast messages, so it can easily communicate between system components. BroadcastReceiver is a system-level listener. It has its own process. As long as there is a matching Broadcast that is sent out in the form of Intent, BroadcastReceiver will be activated.

Like the other four major components, BroadcastReceiver also has its own independent life cycle, but it is different from Activity and Service. After registering a BroadcastReceiver in the system, every time the system publishes Broadcast in the form of an Intent, the system will create a corresponding BroadcastReceiver broadcast receiver instance and automatically trigger its onReceive() method. When the onReceive() method is After the execution is complete, the instance of BroadcastReceiver will be destroyed.

Distinguish from different latitudes, BroadcastReceiver can be divided into different categories.

  • System broadcast/non-system broadcast
  • Global broadcast/local broadcast
  • Out of order broadcast/ordered broadcast/sticky broadcast

4.2 Basic use

4.2.1 Register Broadcast

Broadcast registration is divided into static registration and dynamic registration. Static registration is to register 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>

Dynamic registration is in the code, use the registerReceiver method code to register, for example.

val br: BroadcastReceiver = MyBroadcastReceiver()
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
    addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
}
registerReceiver(br, filter)

4.2.2 Send broadcast

Then, we use the sendBroadcast method to send a broadcast.

Intent().also { intent ->
    intent.setAction("com.example.broadcast.MY_NOTIFICATION")
    intent.putExtra("data", "Notice me senpai!")
    sendBroadcast(intent)
}

4.2.3 Receiving broadcast

When sending a broadcast, we will add a sending logo, then use this logo to receive when receiving. 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()
            }
        }
    }
}

5, ContentProvider

ContentProvider is one of the four major components of Android, but there are few opportunities to use it in normal times. If you have seen its underlying source code, then you should know that ContentProvider uses Binder for data sharing. Therefore, if we need to provide data to third-party applications, we can consider using ContentProvider implementation.

6, Android View knowledge points

The View system of Android itself is very large. It is very difficult to fully understand the principle of View. Here we pick some of the more important concepts to explain to you.

6.1 Measurement process

The drawing process of Android View itself needs to go through the three processes of measurement, layout, and draw before it can be drawn and displayed in front of the user.

First, let's take a look at Android's MeasureSpec. Android's MeasureSpec is divided into 3 modes, namely EXACTLY, AT_MOST and UNSPECIFIED, which have the following meanings.

  • MeasureSpec.EXACTLY : Exact mode, in this mode, the value of the size is the length or width of the component.
  • MeasureSpec.AT_MOST : Maximum mode, determined by the maximum space that the parent component can give.
  • MeasureSpec.UNSPECIFIED : No mode specified, the current component can use the space freely without restriction.

Related articles: Talk about the understanding of MeasureSpec

6.2 Event distribution

Android event distribution consists of three methods: dispatchTouchEvent, onInterceptTouchEvent and onTouchEvent.

  • dispatchTouchEvent : The return value of the method is true, which means that the event is consumed by the current view; the return value is super.dispatchTouchEvent which means that the event will continue to be dispatched, and the return value is false which means it will be handed over to the parent class’s onTouchEvent for processing.
  • onInterceptTouchEvent : The return value of the method is true, which means that the event is intercepted and passed to its own onTouchEvent method for consumption; false means that it is not intercepted and needs to be passed to the subview. If you return super.onInterceptTouchEvent(ev), there are two cases of event interception: one is the case with child views, and the other is the case without child views.

If the View has a sub-View and the sub-View is clicked, it will not be intercepted and continue to be distributed
Treat the child View, which is equivalent to return false at this time. If the View has no child View or child View but does not click the neutron View, it will be handed over to the onTouchEvent response of the View, which is equivalent to return true.

  • onTouchEvent : The return value of the method means that the current view can handle the corresponding event; the return value of false means that the current view does not handle this event, and it will be passed to the onTouchEvent method of the parent view for processing. If return super.onTouchEvent(ev), event processing is divided into two situations, namely, self-consumption or upward transmission.

In the Android system, there are the following three classes with event delivery processing capabilities:

  • Activity: Has two methods of distribution and consumption.
  • ViewGroup: Has three methods of distribution, interception and consumption.
  • View: There are two methods of distribution and consumption.

In event distribution, sometimes I will ask: will 1609508a17c196 ACTION_CANCEL be triggered, will touching the button and then sliding to the outside to lift up will trigger the click event, and then sliding back to lift it up?

For this problem, we need to understand the following:

  • Generally, ACTION_CANCEL and ACTION_UP are regarded as the end of View event processing. If you intercept ACTION_UP or ACTION_MOVE in the parent View, at the moment when the parent view intercepts the message for the first time, the parent view specifies that the child view does not accept subsequent messages, and the child view will receive the ACTION_CANCEL event.
  • If you touch a control, but it is not lifted on the area of this control, ACTION_CANCEL will also appear.

在这里插入图片描述
在这里插入图片描述

  • ViewGroup does not intercept any events by default. The onInterceptTouchEvent method of ViewGroup returns false by default.
  • View has no onInterceptTouchEvent method. Once a click event is passed to it, the onTouchEvent method will be called.
  • When the View is in a clickable state, onTouchEvent consumes events by default.
  • ACTION_DOWN is intercepted. After the onInterceptTouchEvent method is executed once, a mark (mFirstTouchTarget == null) will be left, then subsequent ACTION_MOVE and ACTION_UP will be intercepted. `

6.3 MotionEvent

Android's MotionEvent events mainly include the following:

  • ACTION_DOWN The finger just touched the screen
  • ACTION_MOVE finger moves on the screen
  • ACTION_UP The moment the phone is released from the screen
  • ACTION_CANCEL touch event cancel

The following is an example of an event: Tap the screen and release, the event sequence is DOWN -> UP, tap the screen to slide to release, the event sequence is DOWN -> MOVE -> ...> MOVE -> UP. At the same time, getX/getY returns the coordinates relative to the upper left corner of the current View, and getRawX/getRawY returns the coordinates relative to the upper left corner of the screen. TouchSlop is the minimum distance that the system can recognize as sliding. The value of different devices may be different. It can be obtained through ViewConfiguration.get(getContext()).getScaledTouchSlop().

6.4 The relationship between Activity, Window and DecorView

First, take a look at the source code of setContentView in Activity.

 public void setContentView(@LayoutRes int layoutResID) {
        //将xml布局传递到Window当中
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

As you can see, the activity’s setContentView essentially passes the View to the Window’s setContentView() method, and the Window’s setContenView will internally call the installDecor() method to create the DecorView. The code is as follows.

 public void setContentView(int layoutResID) { 
        if (mContentParent == null) {
            //初始化DecorView以及其内部的content
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ...............
        } else {
            //将contentView加载到DecorVoew当中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...............
    }
  private void installDecor() {
        ...............
        if (mDecor == null) {
            //实例化DecorView
            mDecor = generateDecor(-1);
            ...............
            }
        } else {
            mDecor.setWindow(this);
        }
       if (mContentParent == null) {
            //获取Content
            mContentParent = generateLayout(mDecor);
       }  
        ...............
 }
 protected DecorView generateDecor(int featureId) {
        ...............
        return new DecorView(context, featureId, this, getAttributes());
 }

Pass a new DecorView of generateDecor(), then call generateLayout() to get the content in the DecorView, and finally add the Activity view to the content in the DecorView through inflate, but at this time, the DecorView has not been added to the Window. Add operation requires the help of ViewRootImpl.

The role of ViewRootImpl is to connect WindowManager and DecorView. After the Activity is created, DecorView will be added to PhoneWindow through WindowManager and an instance of ViewRootImpl will be created, then DecorView and ViewRootImpl will be associated, and finally the entire View tree will be opened by executing the performTraversals() of ViewRootImpl Drawing.

6.5 Draw drawing process

The Android Draw process can be divided into six steps:

  1. First, draw the background of the View;
  2. If necessary, keep the canvas layer to prepare for fading;
  3. Then, draw the content of the View;
  4. Next, draw the child View of View;
  5. If necessary, draw the fading edge of the View and restore the layer;
  6. Finally, draw the decoration of the View (such as scroll bars, etc.).

The code involved is as follows:

public void draw(Canvas canvas) {
    ...
    // 步骤一:绘制View的背景
    drawBackground(canvas);
    ...
    // 步骤二:如果需要的话,保持canvas的图层,为fading做准备
    saveCount = canvas.getSaveCount();
    ...
    canvas.saveLayer(left, top, right, top + length, null, flags);
    ...
    // 步骤三:绘制View的内容
    onDraw(canvas);
    ...
    // 步骤四:绘制View的子View
    dispatchDraw(canvas);
    ...
    // 步骤五:如果需要的话,绘制View的fading边缘并恢复图层
    canvas.drawRect(left, top, right, top + length, p);
    ...
    canvas.restoreToCount(saveCount);
    ...
    // 步骤六:绘制View的装饰(例如滚动条等等)
    onDrawForeground(canvas)
}

6.6 The difference and connection of Requestlayout, onlayout, onDraw, DrawChild

  • requestLayout() : will cause the measure() process and layout() process to be called, and it will be judged whether ondraw is required according to the flag bit.
  • onLayout() : If the View is a ViewGroup object, this method needs to be implemented to lay out each subview.
  • onDraw() : Draw the view itself (each View needs to override this method, and ViewGroup does not need to implement this method).
  • drawChild() : to call back the draw() method of each child view again.

6.7 The difference between invalidate() and postInvalidate()

Both invalidate() and postInvalidate() are used to refresh the View. The main difference is that invalidate() is called in the main thread. If it is used in the child thread, it needs to cooperate with the handler; while postInvalidate() can be called directly in the child thread.

7, Android process

7.1 Concept

Process is a running activity of a program on a certain data set in a computer. It is the basic unit of the system for resource allocation and scheduling, and is the foundation of the operating system structure.

When a program is started for the first time, Android will start a LINUX process and a main thread. By default, all the components of the program will run in the process and thread. At the same time, Android will assign a separate LINUX user for each application. Android will try to keep a running process, only when the memory resources are insufficient, Android will try to stop some processes to release enough resources for other new processes to use, and it can also ensure that the current process that the user is accessing has enough resources to go in time. Respond to user events.

We can run some components in other processes, and we can add threads to any process. The process in which the component runs is set in the manifest file, where <Activity>, <Service>, <receiver> and <provider> all have a process attribute to specify which process the component runs in. We can set this property so that each component runs in its own process, or several components share a process, or not share it. The <application> element also has a process attribute, which is used to specify the default attributes of all components.

All components in Android are instantiated in the main thread in the specified process, and the system calls to the components are also issued by the main thread. Each instance will not create a new thread. The methods that respond to system calls—for example, View.onKeyDown(), which is responsible for executing user actions, and component lifecycle functions—are all running in this main thread. This means that when the system calls this component, this component cannot block the main thread for a long time. For example, when performing network operations or updating the UI, if the running time is long, you cannot run directly in the main thread, because this will block other components in the process. We can assign such components to the newly created thread or It runs in other threads.

7.2 Process life cycle

According to different life cycles, Android processes can be divided into foreground processes, background processes, visible processes, service processes, and empty processes.

Foreground process

The foreground process is the process currently being used by the user. Some foreground processes can exist at any time. The foreground process may also be destroyed when the memory is low. In this case, the device will perform memory scheduling and suspend some foreground processes to keep responding to user interactions.

If there are the following conditions, then it is the foreground process:

  1. Host the Activity that the user is interacting with (Activity's onResume() method has been called)
  2. Host a Service, which is bound to the Activity that the user is interacting with
  3. Host the Service that is running in the "foreground" (the service has called startForeground())
  4. Hosting the Service (onCreate(), onStart(), or onDestroy()) that is executing a lifecycle callback
  5. Host BroadcastReceiver that is executing its onReceive() method
Visible process

Visible process refers to a process that does not contain foreground components, but will display a visible process on the screen.

If there is one of the following situations, it is a visible process:

  1. Host an Activity that is not in the foreground but is still visible to the user (its onPause() method has been called). For example, if the re foreground Activity starts a dialog box that allows the previous Activity to be displayed afterwards, this may happen.
  2. Manage the Service bound to the visible (or foreground) Activity.
Service process

The Service started by the startService() method is not as important as the above two processes, and generally follows the life cycle of the application.

Generally speaking, the service that is started using the startService() method and does not belong to the two higher-class processes is the service process.

backstage process

Contains the process of the Activity that is currently invisible to the user (Activity's onStop() method has been called). There are usually many background processes running, so they will be saved in the LRU (least recently used) list to ensure that the process containing the most recently viewed activity by the user is terminated last.

Empty process

Process without any active application components. The only purpose of keeping this kind of process is to be used as a cache to shorten the startup time required to run the component in it next time. In order to balance the overall system resources between the process cache and the underlying kernel cache, the system often terminates these processes.

7.3 Multi-process

First of all, a process generally refers to an execution unit, which is a program or application on a mobile device. What we call multi-process (IPC) in Android generally refers to an application containing multiple processes. There are two reasons for using multiple processes: some modules need to run in a separate process due to special requirements; increase the memory space available to the application.

There is only one way to open multiple processes in Android, which is to specify the android:process attribute 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>

As you can see, the android:process attribute values specified by MyService and MyActivity are different, and their differences are as follows:

  • :remote: beginning with: is a shorthand, the system will append the current package name before the current process name, the complete process name is: com.shh.ipctest:remote, and the process beginning with: belongs to the private process of the current application, others The application component cannot run in the same process as it.
  • com.shh.ipctest.remote2: This is a complete naming method, no package name will be appended. If other applications have the same ShareUID and signature as the process, they can run in the same process to realize data sharing.

However, opening multiple processes will cause the following problems, which must be paid attention to:

  • Static member and singleton mode failure
  • Thread synchronization mechanism fails
  • Reduced reliability of SharedPreferences
  • Application was created multiple times

For the first two questions, it can be understood that in Android, the system allocates independent virtual machines for each application or process. Different virtual machines naturally occupy different memory address spaces, so objects of the same class will have different The copy, which leads to the failure of shared data, will inevitably fail to achieve thread synchronization.

Since the bottom layer of SharedPreferences is implemented by reading and writing XML files, concurrent reading and writing by multiple processes is likely to cause data exceptions.

Application being created multiple times is similar to the previous two problems. When the system allocates multiple virtual machines, it is equivalent to restarting the same application multiple times, which will inevitably cause the Application to be created multiple times.
If there is useless repeated initialization in the process, the process name can be used to filter, and only the specified process will be initialized globally, as shown below.

public class MyApplication extends Application{
    @Override
    public void onCreate() {
        super.onCreate();
        String processName = "com.xzh.ipctest";
        if (getPackageName().equals(processName)){
            // do some init
        }
    }
}

7.4 Multi-process communication mode

At present, the main multi-process communication methods supported in Android are as follows:

  • AIDL: Powerful, supports one-to-many real-time concurrent communication between processes, and can implement RPC (Remote Procedure Call).
  • Messenger: Supports one-to-many serial real-time communication, a simplified version of AIDL.
  • Bundle: The process communication mode of the four major components, which can only transmit data types supported by Bundle.
  • ContentProvider: Powerful data source access support, mainly supporting CRUD operations, one-to-many data sharing between processes, such as the address book data of our application access system.
  • BroadcastReceiver: Broadcast, but only one-way communication, receivers can only passively receive messages.

File sharing: Sharing simple data in the case of non-high concurrency.

  • Socket: transmit data through the network.

8, serialization

8.1 Parcelable and Serializable

  • Serializable uses I/O to read and write and store on the hard disk, while Parcelable is to read and write directly in memory.
  • Serializable uses reflection. The serialization and deserialization process requires a lot of I/O operations. Parcelable's own implementation of marshalled & unmarshalled operations does not require reflection. The data is also stored in Native memory, which is much faster.

8.2 Example

Serializable example:

import java.io.Serializable;
class serializableObject implements Serializable {
   String name;
   public serializableObject(String name) {
      this.name = name;
   }
   public String getName() {
      return name;
   }
}

Parcelable example:

import android.os.Parcel;
import android.os.Parcelable;
class parcleObject implements Parcelable {
   private String name;
   protected parcleObject(Parcel in) {
      this.name = in.readString();
   }
   public parcleObject(String name) {
      this.name = name;
   }
   public String getName() {
      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 new parcleObject[size];
      }
   };
   @Override
   public int describeContents() {
      return 0;
   }
   @Override
   public void writeToParcel(Parcel dest, int flags) {
      dest.writeString(this.name);
   }
}

When using Parcelable, you generally need to use the following methods:

  • createFromParcel(Parcel in): Create the original object from the serialized object.
  • newArray(int size): Create an original object array of the specified length.
  • User(Parcel in) creates the original object from the serialized object.
  • writeToParcel(Parcel dest, int flags): Write the current object into the serialization structure, where flags has two values: 0 or 1. When it is 1, it indicates that the current object needs to be returned as the return value, and the resource cannot be released immediately. It is 0 in almost all cases.
  • describeContents: Returns the content description of the current object. If it contains a file descriptor, it returns 1, otherwise it returns 0, and it returns 0 in almost all cases.

9,Window

9.1 Basic concepts

Window is an abstract class, and its concrete implementation is PhoneWindow. WindowManager is the entrance to the outside world to access Window. The specific implementation of Window is located in WindowManagerService. The interaction between WindowManager and WindowManagerService is an IPC process. All views in Android are presented through Window, so Window is actually the direct manager of View.

According to different functions, Window can be divided into the following types:

  • Application Window : corresponds to an Activity;
  • Sub Window : Can not exist alone, can only be attached to the parent Window, such as Dialog, etc.;
  • System Window : requires permission statement, such as Toast and system status bar;

9.2 Internal mechanism

Window is an abstract concept, and each Window corresponds to a View and a ViewRootImpl. Window does not actually exist, it exists in the form of View. Access to Window must go through WindowManager. The implementation class of WindowManager is WindowManagerImpl. The source code is 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);
}

WindowManagerImpl does not directly implement the three major operations of Window. Instead, they are all handled by WindowManagerGlobal. WindowManagerGlobal provides its own instance in the form of a factory. The code involved is as follows:

// 添加
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ···
    // 子 Window 的话需要调整一些布局参数
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        ···
    }
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        // 新建一个 ViewRootImpl,并通过其 setView 来更新界面完成 Window 的添加过程
        ···
        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);
            }
            throw e;
        }
    }
}

// 删除
@UnsupportedAppUsage
public void removeView(View view, boolean immediate) {
    ···
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        View curView = mRoots.get(index).getView();
        removeViewLocked(index, immediate);
        ···
    }
}

private void removeViewLocked(int index, boolean immediate) {
    ViewRootImpl root = mRoots.get(index);
    View view = root.getView();
    if (view != null) {
        InputMethodManager imm = InputMethodManager.getInstance();
        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);
        }
    }
}

// 更新
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);
    }
}

10. Message mechanism

Talking about Android's message mechanism is mainly the Handler mechanism.

10.1 Handler mechanism

Handler has two main purposes:

  • Arrange for Message and runnables to be executed at some point in the future;
  • Enqueue operations to be performed on threads different from their own. (Ensure thread safety while concurrently updating the UI in multiple threads.)

Android stipulates that access to the UI can only be done in the main thread, because Android's UI controls are not thread-safe, and multi-threaded concurrent access will cause the UI controls to be in an unexpected state. Why doesn't the system add a lock mechanism to the access of UI controls? There are two disadvantages: locking will complicate the logic of UI access; secondly, the locking mechanism will reduce the efficiency of UI access. If the child thread accesses the UI, the program will throw an exception. In order to ensure thread safety, ViewRootImpl verifies the UI operation. This verification is done by the checkThread method of ViewRootImpl.

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

When talking about the Handler mechanism, it usually contains the following three objects:

  • Message: The message object received and processed by the Handler.
  • MessageQueue: Message queue, first in, first out, each thread can have at most one.
  • Looper: The message pump is the manager of the MessageQueue. It will continuously take out messages from the MessageQueue and distribute the messages to the corresponding Handler for processing. Each thread has only one Looper.

When the Handler is created, the Looper of the current thread is used to construct the message loop system. It should be noted that the thread does not have a Looper by default. If you use the Handler directly, an error will be reported. If you need to use the Handler, you must create a Looper for the thread, because the default UI main Thread, that is, ActivityThread, Looper is initialized when ActivityThread is created, which is why Handler can be used by default in the main thread.

10.2 Working principle

ThreadLocal
ThreadLocal is a data storage class inside a thread, through which data can be stored in a specified thread, but other threads cannot obtain it. ThreadLocal is used in Looper, ActivityThread, and AMS. When different threads access the get method of the same ThreadLocal, ThreadLocal will internally take out an array from their respective threads, and then find the corresponding value from the array according to the current ThreadLcoal index. The source code is as follows:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

···
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

MessageQueue
MessageQueue mainly contains two operations: insert and read. The read operation itself will be accompanied by a delete operation, and the corresponding methods for inserting and reading are enqueueMessage and next. The internal implementation of MessageQueue is not a queue used. In fact, the message list is maintained through a data structure of a singly linked list. The next method is an infinite loop. If there are no messages in the message queue, the next method will always block. When a new message arrives, the next method will put this message back and remove it from the singly linked list. The source code is as follows.

boolean enqueueMessage(Message msg, long when) {
    ···
    synchronized (this) {
        ···
        msg.markInUse();
        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();
            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() {
    // 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();
            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();
                    return msg;
                }
            } 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();
            } 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;
    }
}

Looper
Looper will constantly check whether there are new messages from the MessageQueue. If there are new messages, it will be processed immediately, otherwise it will keep blocking. Looper source code.

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

You can create a Looper for the current thread through Looper.prepare(). By default, Activity will create a Looper object.

In addition to the prepare method, Looper also provides the prepareMainLooper method, which is mainly used to create the Looper for ActivityThread, and the essence is also realized by the prepare method. Because the Looper of the main thread is special, Looper provides a getMainLooper method to get the Looper of the main thread.

At the same time, Looper provides quit and quitSafely to exit a Looper. The difference between the two is: quit will exit Looper directly, while quitSafly just sets an exit flag, and then exits safely after processing the existing messages in the message queue. . After the Looper exits, the message sent through the Handler will fail. At this time, the send method of the Handler will return false, so the Looper needs to be terminated in time when it is not needed.

Handler
Handler's work mainly includes the process of sending and receiving messages. Message sending can be realized through a series of post/send methods, and post is finally realized through send.

11. RecyclerView optimization

In Android development, the problem of long lists is often encountered, so in many cases, the optimization problem of RecyclerView is involved. The reasons for the list freeze are usually as follows:

11.1 Caton scene

notifyDataSetChanged
If the data needs to be refreshed globally, you can use notifyDataSetChanged; for increasing or decreasing data, you can use partial refresh, as shown below.

void onNewDataArrived(List<News> news) {
    List<News> oldNews = myAdapter.getItems();
    DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news));
    myAdapter.setNews(news);
    result.dispatchUpdatesTo(myAdapter);
}

RecycleView nesting
In actual development, we often see a scene where a vertically scrolling RecycleView is nested with a horizontal scrolling RecycleView. Since a single RecycleView has a separate itemView object pool, for nested situations, a shared object pool can be set as follows.

class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
    RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();

    ...

    @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);

    }

Nesting level is too deep
The Layout performance can be detected through the Systemtrace tool. If it takes too long or calls too many times, you need to check whether you use RelativeLayout too much or nest multiple LinearLayouts. Each layer of Layout will cause its child to measure/layout multiple times.

Object allocation and garbage collection
Although ART is used on Android 5.0 to reduce the GC pause time, it still causes lag. Try to avoid creating objects in the loop to cause GC. You must know that to create an object, memory needs to be allocated, and this is the opportunity to check whether the memory is sufficient to determine whether or not to perform GC.

11.2 Other optimization strategies

In addition to the typical scenarios above, 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;
  • Recycle resources by rewriting RecyclerView.onViewRecycled(holder);
  • If the Item height is fixed, you can use RecyclerView.setHasFixedSize(true); to avoid
    requestLayout wastes resources;
  • Set up a listener for ItemView, don't call addXxListener for each Item, you should share an XxListener, and perform different operations according to the ID, which optimizes the resource consumption caused by the frequent creation of objects;
  • Try not to use a drawing with a change in transparency for the ViewHolder;
  • Increase the preloading strategy.

xiangzhihong
5.9k 声望15.3k 粉丝

著有《React Native移动开发实战》1,2,3、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》1,2和《Android应用开发实战》