Android interview must ask Android Foundation

1,Activity

1.1 life cycle

Normally, an 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 interact.
  • onResume: indicates that the Activity is already visible and in the foreground.
  • onPause: indicates that the Activity is stopping (you can save the state once, stop animation and other non time-consuming operations).
  • onStop: indicates that the Activity is about to stop (heavy recycling is available).
  • onDestroy: indicates that the Activity is about to be destroyed.

For the life cycle, the following questions are usually asked:

  • First startup: oncreate - > OnStart - > onresume;
  • Open a new Activity or return to the desktop: onpause - > onStop. If a new Activity is opened as a transparent theme, onStop will not be called;
  • When returning to the original Activity: onrestart - > OnStart - > onresume;
  • When pressing the return key: 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. Each startup creates a new instance.
  • SingleTop: stack top reuse mode. In this mode, if the Activity is at the top of the stack, no new instance will be created. onNewIntent will be called to receive new request information, instead of using onCreate and onStart.
  • SingleTask: in stack reuse mode. For the upgraded singleTop, if there is an instance in the stack, it will be reused and all 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 activities are allowed (it can be understood that there is only one Activity in the mobile phone).

1.3 startup process

Before understanding the startup process of Activity, let's take a look at the startup process of Android system. Generally speaking, the Android system startup process mainly goes through init process - > zygote process – > systemserver process – > various system services – > application process and other stages.

  1. Power on and system startup: when the power is pressed, the boot chip starts to execute from the predefined place (solidified in ROM), loads the boot program BootLoader to RAM, and then executes.
  2. BootLoader: BootLoader is a small program before the Android system starts running. It is mainly used to pull up and run the system OS.
  3. Linux kernel startup: when the kernel starts, set cache, protected memory, plan list and load driver. When it completes the system setup, it will first look for init in the system file RC file and start the init process.
  4. init process startup: initialize and start the attribute service, and start the Zygote process.
  5. Zygote process startup: create a JVM and register JNI methods for it, create a server-side Socket, and start the SystemServer process.
  6. System server process startup: start Binder thread pool and system service manager, and start various system services.
  7. Launcher start: AMS started by the SystemServer process will start the launcher, and the shortcut icon of the installed application will be displayed on the system desktop after the launcher is started.

After the Launcher process starts, it will call the startup of Activity. First, the Launcher will call the ActivityTaskManagerService, then the ActivityTaskManagerService will call the ApplicationThread, and then the ApplicationThread will start the Activity through the ActivityThread.

2,Fragment

2.1 introduction

Fragment is proposed by Android 3.0 (API 11). In order to be compatible with the lower version, a set of Fragment API is also developed in the support-v4 library, which is at least compatible with Android 1.6. If fragment is to be used in the latest version, AndroidX package needs to be introduced.

Compared with Activity, Fragment has the following characteristics:

  • Modularity: we don't have to write all the code in the Activity, but in their own fragments.
  • Reusability: multiple activities can reuse a Fragment.
  • 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 fragment. Any fragment created needs to inherit this class.
  • Fragment Manager: manages and maintains fragments. It is an abstract class, and the specific implementation class is FragmentManagerImpl.
  • Fragment transaction: all operations such as adding and deleting fragments need to be carried out in transaction mode. It is an abstract class, and the concrete implementation class is BackStackRecord.

2.2 life cycle

Fragments must exist depending on activities, so the life cycle of activities will directly affect the life cycle of fragments. Compared with the life cycle of Activity, the life cycle of Fragment is as follows.

  • onAttach(): called when a Fragment is associated with an Activity. If you don't have to use a specific host Activity object, you can use this method or getContext() to obtain the Context object to solve the problem of Context reference. At the same time, in this method, you can get the parameters required when creating the Fragment through getArguments().
  • onCreate(): called when a Fragment is created.
  • onCreateView(): creates 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 in the figure below.

The following is the corresponding relationship between the life cycle of Activity and each life cycle method of Fragment.

2.3 data transfer with Activity

2.3.1 Fragment pass data to Activity

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

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

Then, in the onAttach() of the Fragment, the parameter Context is strongly converted to the OnFragmentInteractionListener object and passed in.

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. Transfer data from activity 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);//Set parameters
        return fragment;
    }

3, Service

3.1 startup mode

There are two main ways to start a Service: startService and bindService.

Among them, StartService uses the same Service, so onStart() will execute multiple times, onCreate() will execute only once, and onStartCommand() will execute multiple times. When starting with bindService, onCreate() and onBind() are called only once.

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

3.2 life cycle

The following figure is the schematic diagram of starting Service by 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 service is already in operation, calling startService () does not execute the onCreate () method.
  • onStartCommand(): if the startService() method of Context is executed multiple times, the onStartCommand() method of Service will be called multiple times accordingly.
  • onBind(): the onBind() method in the Service is an abstract method, and the Service class itself is an abstract class, so the onBind() method must be overridden, even if we can't use it.

onDestory(): this method is used when destroying a 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

bindService starts a typical client Server mode between the Service 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. The life cycle of the bindService startup Service is closely related to its bound client.

1. First, return the instance of IBinder type in the onbind () method of Service. 2. The instance of IBinder returned by the onbind () method needs to be able to return the Service instance itself.

3.3 Service will not be killed

Now, due to the limitations of the system API, some common Service methods that are not killed are outdated. For example, the following are some previous methods.

3.3. 1. In onStartCommand mode, return START\_STICKY.

Call context When starting a Service in startservice mode, if Android is faced with a lack of memory, it may destroy the currently running Service and rebuild the Service when the memory is sufficient. The forced destruction and re reconstruction of a Service by the Android system depends on the return value of the onStartCommand() method of the Service. The common return values are as follows.

START\_NOT\_STICKY: if return to START\_NOT\_STICKY means that the Service will not be re created after the running process of the Service is forcibly killed by the Android system. START\_STICKY: if return to START\_STICKY, which means that after the process running the Service is forcibly killed by the Android system, The Android system will still set the Service to started (i.e. running status), but the intent object passed in by the onStartCommand method is no longer saved, that is, the relevant information about intent cannot be obtained. Start \ _redeliverer \ _int: if start \ _redeliverer \ _intis returned, it means that after the running process of the Service is forcibly killed by the Android system, the Android system will re create the Service again, which is similar to the case when start \ _stickyis returned, And execute the onStartCommand callback method, but the difference is that the Android system will again retain the intent last passed into the onStartCommand method before the Service is killed and pass it into the onStartCommand method of the recreated Service again, so that we can read the intent parameter.

4, BroadcastReceiver

4.1 what is a broadcastreceiver

BroadcastReceiver, Broadcast receiver, is a system global listener, which is used to listen to the system global Broadcast messages, so it can facilitate the communication between system components. BroadcastReceiver is a system level listener. It has its own process. As long as there is a matched Broadcast sent in the form of Intent, the BroadcastReceiver will be activated.

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

From different latitudes, broadcastreceivers can be divided into different categories.

  • System broadcast / non system broadcast
  • Global broadcast / local broadcast
  • Unordered broadcast / ordered broadcast / sticky broadcast

4.2 basic use

4.2. 1 register broadcast

The registration of broadcasting is divided into static registration and dynamic registration. Static registration is registered 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 registered in the code using the registerReceiver method, 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 the broadcast.

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

4.2. 3 receive broadcast

When sending a broadcast, we will add a sending ID, so when receiving, we can use this ID to receive. To receive broadcast, you need to inherit BroadcastReceiver and override 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 it is rarely used. If you have seen its underlying source code, you should know that ContentProvider shares data through Binder. Therefore, if we need to provide data to third-party applications, we can consider using the ContentProvider implementation.

6. Android View knowledge points

The View system of Android itself is very huge. It is difficult to fully understand the principle of View. Here 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 and draw before it can be drawn and displayed in front of users.

First, let's take a look at the MeasureSpec of Android. The MeasureSpec of Android is divided into three modes, namely, actual and AT\_MOST and UNSPECIFIED have the following meanings.

  • MeasureSpec. Exctly: precise mode. In this mode, the value of the dimension is the length or width of the component.
  • MeasureSpec.AT\_MOST: maximum mode, which is determined by the maximum space that the parent component can give.
  • MeasureSpec.UNSPECIFIED: no mode is specified. The current component can use any space without restriction.

6.2 event distribution

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

  • dispatchTouchEvent: the return value of the method is true, indicating that the event is consumed by the current view; Return to super dispatchTouchEvent indicates that the event will continue to be distributed. If false is returned, it means that it will be handed over to the onTouchEvent of the parent class for processing.
  • onInterceptTouchEvent: the return value of the method is true, which means that this event is intercepted and handed over to its own onTouchEvent method for consumption; Returning false means that it is not intercepted and needs to be passed to the child View. If return super onInterceptTouchEvent (EV). There are two kinds of event interception: one is when there are child views, and the other is when there are no child views.

If a child View exists in the View and the child View is clicked, it will not be intercepted and will continue to be distributed to the child View for processing. At this time, it is equivalent to return false. If the View does not have a child View or has a child View but does not click a child View, the onTouchEvent of the View will respond. At this time, it is equivalent to return true.

  • onTouchEvent: the return value of the method is true, indicating that the current view can handle the corresponding event; The return value of false indicates that the current view does not process this event, which will be passed to the onTouchEvent method of the parent view for processing. If return super onTouchEvent (EV), event processing can be divided into two cases: self consumption or upward delivery.

In the Android system, there are three classes with the ability of event delivery and processing:

  • Activity: it has two methods: distribution and consumption.
  • ViewGroup: it has three methods: distribution, interception and consumption.
  • View: it has two methods: distribution and consumption.

In event distribution, you sometimes ask: action\_ When will cancel be triggered? Touch button and then slide to the outside. Will the click event be triggered when lifting, and then slide back to lift?

For this problem, we need to understand the following:

  • General ACTION\_CANCEL and ACTION\_UP is regarded as the end of a period of event processing. If the action is intercepted in the parent View\_ Up or ACTION\_MOVE: at the moment when the parent View intercepts the message for the first time, the parent View specifies that the child View will not accept subsequent messages, and the child View will receive ACTION\_CANCEL event.
  • If you touch a control but do not lift it on the area of the control, action will also appear\_ CANCEL.

  • 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.
  • In the clickable state of View, onTouchEvent consumes events by default.
  • ACTION\_DOWN is intercepted. After the onInterceptTouchEvent method is executed once, it will leave a mark (mFirstTouchTarget == null). Then the subsequent action \ _moveand action \ _upwill be intercepted\`

6.3 MotionEvent

The MotionEvent events of Android mainly include the following:

  • ACTION\_DOWN finger just touched the screen
  • ACTION\_MOVE finger moves on the screen
  • ACTION\_ The moment when the up phone is released from the screen
  • ACTION\_CANCEL touch event cancel

The following is an example of an event: click the screen and release, the event sequence is down - > up, click the screen to slide and release, and the event sequence is down - > move - >... > MOVE -> UP. Meanwhile, 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. Touchslope is the minimum distance recognized by the system and considered to slide. The values of different devices may be different. You can use viewconfiguration get(getContext()). Getscaledtouchslop() gets.

6.4 relationship among activity, Window and DecorView

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

 public void setContentView(@LayoutRes int layoutResID) {
        //Pass xml layout to Window
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

It can be seen that the setContentView of an Activity essentially passes the View to the setContentView() method of Window. The setContentView of Window will internally call the installDecor() method to create a DecorView. The code is as follows.

 public void setContentView(int layoutResID) { 
        if (mContentParent == null) {
            //Initialize DecorView and its internal content
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ...............
        } else {
            //Load contentView into DecorVoew
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...............
    }
  private void installDecor() {
        ...............
        if (mDecor == null) {
            //Instantiate DecorView
            mDecor = generateDecor(-1);
            ...............
            }
        } else {
            mDecor.setWindow(this);
        }
       if (mContentParent == null) {
            //Get Content
            mContentParent = generateLayout(mDecor);
       }  
        ...............
 }
 protected DecorView generateDecor(int featureId) {
        ...............
        return new DecorView(context, featureId, this, getAttributes());
 }

Through generateDecor() new's DecorView, then generateLayout() gets content in DecorView, and finally adds Activity view to DecorView's content through inflate, but at this time, DecorView has not been added to the DecorView. The addition operation requires the help of ViewRootImpl.

ViewRootImpl is used to connect WindowManager and DecorView. After the Activity is created, DecorView will be added to PhoneWindow through WindowManager and the instance of ViewRootImpl will be created. Then DecorView will be associated with ViewRootImpl. Finally, the drawing of the whole View tree will be started by executing performTraversals() of ViewRootImpl.

6.5 Draw drawing process

The Draw process of Android can be divided into six steps:

  1. First, draw the background of View;
  2. If necessary, keep the canvas layer and prepare for fading;
  3. Then, draw the contents of the View;
  4. Then, draw the sub 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 bar, etc.).

The codes involved are as follows:

public void draw(Canvas canvas) {
    ...
    // Step 1: draw the background of View
    drawBackground(canvas);
    ...
    // Step 2: if necessary, keep the canvas layer and prepare for fading
    saveCount = canvas.getSaveCount();
    ...
    canvas.saveLayer(left, top, right, top + length, null, flags);
    ...
    // Step 3: draw the contents of the View
    onDraw(canvas);
    ...
    // Step 4: draw sub View of View
    dispatchDraw(canvas);
    ...
    // Step 5: if necessary, draw the fading edge of the View and restore the layer
    canvas.drawRect(left, top, right, top + length, p);
    ...
    canvas.restoreToCount(saveCount);
    ...
    // Step 6: draw the decoration of View (such as scroll bar, etc.)
    onDrawForeground(canvas)
}

6.6 differences and relations among requestlayout, onlayout, onDraw and DrawChild

  • requestLayout(): it will call measure() process and layout() process. It will judge whether ondraw is required according to the flag bit.
  • onLayout(): if the View is a ViewGroup object, you need to implement this method to layout each sub View.
  • onDraw(): draw the View itself (each View needs to overload this method, and ViewGroup does not need to implement this method).
  • drawChild(): to recall the draw() method of each child view.

6.7 difference between invalidate() and postInvalidate()

invalidate() and postInvalidate() are used to refresh View. The main difference is that invalidate() is invoked in the main thread. If it is used in sub threads, it needs to match handler. postInvalidate() can be called directly in the child thread.

7. Android process

7.1 concept

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

When a program starts for the first time, 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 will try to keep a running process. Only when memory resources are insufficient, Android will try to stop some processes, so as to release enough resources for other new processes. It can also ensure that the current process that users are accessing has enough resources to respond to users' events in time.

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. Both,, and 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. The element also has a process attribute that specifies the default attributes of all components.

All components in Android are instantiated in the main thread of the specified process, and system calls to components are also issued by the main thread. Each instance does not create a new thread. A method that responds to a system call -- for example, view, which is responsible for performing user actions Onkeydown () and the component's lifecycle function -- both run in this main thread. This means that when the system calls this component, the component cannot block the main thread for a long time. For example, during network operation or UI update, if the running time is long, it cannot be run directly in the main thread, because it will block other components in the process. We can assign such components to new threads or 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 used by the user. Some foreground processes can exist at any time. When the memory is low, the foreground process may also be destroyed. In this case, the device will perform memory scheduling and abort some foreground processes to maintain the response to user interaction.

It is a foreground process if:

  1. The Activity that the managed user is interacting with (the onResume() method of the Activity has been called)
  2. Hosting a Service, which is bound to the Activity that the user is interacting with
  3. Hosting a Service running in the foreground (Service called startforegroup())
  4. Hosting a Service (onCreate(), onStart(), or onDestroy()) that is executing a lifecycle callback
  5. Manages the BroadcastReceiver whose onReceive() method is executing
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 the visible process:

  1. Manages an Activity that is not in the foreground but is still visible to the user (its onPause() method has been called). For example, this might happen if the re foreground Activity launches a dialog box that allows the previous Activity to be displayed after it.
  2. Hosting services bound to visible (or foreground) activities.
Service process

The Service started through the startService() method is not as important as the above two processes, and generally changes with the application life cycle.

Generally speaking, the service started with the startService() method and not belonging to the above two higher categories of processes is the service process.

Background process

A process that contains an Activity that is not currently visible to the user (the onStop() method of the Activity 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 recently viewed by the user is terminated last.

Empty process

A process that does not contain any active application components. The only purpose of keeping this process is to use it as a cache to reduce the startup time required to run the component in it the 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 that an application contains multiple processes. There are two reasons for using multi processes: some modules need to run in a separate process due to special requirements; and increasing the available memory space of the application.

There is only one way to start multiple processes in Android, that is, in Android manifest When registering Service, Activity, Receiver and ContentProvider in XML, specify the android:process attribute, as shown below.

<service
    android:name=".MyService"
    android:process=":remote">
</service>

<activity
    android:name=".MyActivity"
    android:process="com.shh.ipctest.remote2">
</activity>

You can see that the android:process attribute values specified by MyService and MyActivity are different. Their differences are as follows:

  • : remote: it starts with: and is a short form. The system will attach the current package name before the current process name. The complete process name is: com shh. Ipctest: remote. At the same time, the process starting with: belongs to the private process of the current application. Components of other applications cannot run in the same process as it.
  • com.shh.ipctest.remote2: This is a complete naming method without attaching the package name. If other applications are the same as the ShareUID and signature of the process, they can run in the same process with it to realize data sharing.

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

  • Static member and singleton mode failures
  • Thread synchronization mechanism failure
  • SharedPreferences reduced reliability
  • Application was created more than once

For the first two problems, it can be understood that in Android, the system will allocate independent virtual machines for each application or process. Different virtual machines naturally occupy different memory address spaces. Therefore, objects of the same class will produce different copies, resulting in shared data failure and thread synchronization.

Because the bottom layer of shared preferences is implemented by reading and writing XML files, concurrent reading and writing of multiple processes is likely to lead to data exceptions.

The Application is created many times, which is similar to the previous two problems. When the system allocates multiple virtual machines, it is equivalent to restarting the same Application many times, which will inevitably lead to the Application being created many times. In order to prevent useless repeated initialization in the Application, the process name can be used for filtering, and only the specified process can be used for global initialization, 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 multi process communication modes supported in Android mainly include the following:

  • AIDL: powerful, supports one to many real-time concurrent communication between processes, and can realize 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 components, which can only transmit the data types supported by bundle.
  • ContentProvider: powerful data source access support, mainly supporting CRUD operation and one to many inter process data sharing, for example, our application accesses the address book data of the system.
  • Broadcast receiver: broadcast, but only one-way communication, and the receiver can only receive messages passively.

File sharing: share simple data without high concurrency.

  • Socket: transmits data through the network.

8. Serialization

8.1 Parcelable and Serializable

  • Serializable uses I/O to read and write on the hard disk, while Parcelable reads and writes directly in memory.
  • Serializable uses reflection. The serialization and deserialization process requires a lot of I/O operations. The marshaled & unmarshalled operations implemented by parcel do not need reflection, and the data is also stored in Native memory, which is much faster.

8.2 example

Serializable instance:

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

Parcelable instance:

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): creates the original object from the serialized object.
  • newArray(int size): creates an array of original objects of the specified length.
  • User(Parcel in) creates the original object from the serialized object.
  • writeToParcel(Parcel dest, int flags): writes the current object into the serialization structure, where the flags ID has two values: 0 or 1. When it is 1, the identification of the current object needs to be returned as a return value. Resources cannot be released immediately. In almost all cases, it is 0.
  • describeContents: returns the content description of the current object. If it contains a file descriptor, it returns 1. Otherwise, it returns 0. In almost all cases, it returns 0.

9,Window

9.1 basic concepts

Window is an abstract class, and its concrete implementation is PhoneWindow. WindowManager is the entrance for 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: cannot exist alone. It can only be attached to the parent Window, such as Dialog;
  • System Window: permission declaration is required, such as Toast and system status bar;

9.2 internal mechanism

Window is an abstract concept. 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 be 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, but all of them are handled by WindowManagerGlobal. WindowManagerGlobal provides its own examples in the form of factories. The codes involved are as follows:

// add to
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ···
    // For sub windows, some layout parameters need to be adjusted
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        ···
    }
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        // Create a new ViewRootImpl and update the interface through its setView to complete the process of adding 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;
        }
    }
}

// delete
@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);
        }
    }
}

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

10. Message mechanism

The message mechanism of Android is mainly the Handler mechanism.

10.1 Handler mechanism

Handler has two main purposes:

  • Arrange Message and runnables to be executed at some time in the future;
  • Queue operations to be performed on threads different from your own. (ensure thread safety while multiple threads update UI concurrently.)

Android stipulates that the UI can only be accessed in the main thread, because the UI control of Android is not thread safe, and multi-threaded concurrent access will cause the UI control to be in an unexpected state. Why does the system not lock the access to 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 a child thread accesses the UI, the program throws an exception. In order to ensure thread safety, ViewRootImpl verifies the UI operation, which is completed 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 includes 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 constantly take messages from the MessageQueue and distribute them to the corresponding Handler for processing. Each thread has only one looper.

When the Handler is created, the Looper of the current thread will be used to construct the message loop system. It should be noted that the thread has no Looper by default, and an error will be reported if the Handler is used directly. If the Handler needs to be used, the Looper must be created for the thread, because the default UI main thread, that is, ActivityThread, will initialize the Looper when the ActivityThread is created, This is why Handler can be used by default in the main thread.

10.2 working principle

ThreadLocal ThreadLocal is a data storage class within a thread. It can store data in the 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 take an array from their respective threads, and then find the corresponding value value from the array according to the index of the current ThreadLcoal. 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 the delete operation. The corresponding methods of insert and read are enqueueMessage and next respectively. The internal implementation of MessageQueue is not a queue. In fact, the message list is maintained through a single linked list data structure. The next method is an infinite loop method. If there is no message in the message queue, the next method will always block. When a new message arrives, the next method will put back the message and remove it from the single 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, they will be processed immediately, otherwise they will be blocked all the time. Looper source code.

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

Available through Looper Prepare() creates a Looper for the current thread. 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 loopers for ActivityThread. In essence, it is also implemented through the prepare method. Because the Looper of the main thread is special, Looper provides a getMainLooper method to obtain the Looper of the main thread.

Meanwhile, Looper provides quit and quitSafely to exit a Looper. The difference between them is that quit will directly exit Looper, while quitSafely only sets an exit flag, and then safely exits 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 Handler's send method will return false. Therefore, the Looper needs to be terminated in time when it is not needed.

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

11. RecyclerView optimization

In Android development, we often encounter the problem of long list, so we often involve the optimization of RecyclerView. The reasons for the list jam are usually as follows:

11.1 Caton scene

notifyDataSetChanged if the data needs global refresh, you can use notifyDataSetChanged; For increasing or decreasing data, you can use local 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);
}

The RecycleView is nested in the actual development. We often see that the vertical scrolling RecycleView is nested with a horizontal scrolling RecycleView. Since a single RecycleView has an independent itemView object pool, for nested cases, you can set the shared object pool, as shown below.

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

    }

If the nesting level is too deep, you can check the performance of Layout through Systemtrace tool. If it takes too long or calls too many times, you need to check whether to overuse RelativeLayout or nest multi-layer LinearLayout. Each layer of Layout will lead to multiple child measure/layout.

Object allocation and garbage collection although ART is used on Android 5.0 to reduce GC pause time, it will still cause jamming. Try to avoid creating objects within a loop that cause GC. You should know that creating an object requires allocating memory, and this opportunity checks whether the memory is enough to decide whether GC is needed or not.

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 0 and above use Prefetch function;
  • By overriding recyclerview OnView recycled (holder) to recycle resources;
  • If the Item height is fixed, you can use recyclerview setHasFixedSize(true); To avoid waste of resources in requestLayout;
  • Set a listener for ItemView. Instead of calling addXxListener for each Item, we should share an XxListener to perform different operations according to the ID, so as to optimize the resource consumption caused by frequent object creation;
  • Try not to use rendering with transparency change for ViewHolder;
  • Add preload policy.

Video:
Zero foundation of Android development from entry to mastery: Activity
Zero foundation of Android Development: from entry to mastery: Fragment
Zero foundation of Android Development: from entry to mastery: Service

Original text: https://juejin.cn/post/6959472535108861959

Keywords: Android

Added by Scutterman on Wed, 22 Dec 2021 08:54:41 +0200