The introduction

  • This is the fifth article in our Android 10 source code analysis series
  • Branches: android – 10.0.0 _r14
  • Read the full text for about 10 minutes

You will learn the following in this article, and the answers will be given in the summary section at the end of the article

  • Dialog creation process?
  • How is the Dialog view associated with the Window?
  • How to bind a CustomDialog view?
  • How do I use Kotlin named optional parameters to construct classes that implement the Builder pattern?
  • Constructing classes with named optional parameters has the following advantages over Java’s builder pattern?
  • How to use DataBinding in Dialog?

Before reading this article, if you have not seen Apk load process resource load 1 and Apk load process resource Load 2 before clicking on the link below, these articles are related

  • Android 10 source code: Apk source code
  • Android 10 source code: Apk

This paper mainly focuses on the following aspects to analyze:

  • Dialog loads the drawing process
  • How do I use Kotlin named optional parameters to construct classes that implement the Builder pattern
  • How to use DataBinding in Dialog

Source code analysis

Before we start analyzing Dialog’s source code, we need to understand the data structure and functions involved in the Dialog loading and drawing process

Under package Android. app:

  • Dialog: Dialog is the parent class of a Window that performs initialization and some common logic for the Window object
    • AlertDialog: Inherits from Dialog and is the concrete action implementation class of Dialog
    • Alertdialog. Builder: An internal class of AlertDialog that is used to construct AlertDialog
  • AlertController: the control class of an AlertDialog
    • AlertController. AlertParams: is the inner class AlertController, responsible for the AlertDialog initialization parameter

Now that you understand the data structures and functions involved, review the Dialog creation process

AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setIcon(R.mipmap.ic_launcher); Builder. SetMessage (" the Message section "); Builder. SetTitle (" Title "); builder.setView(R.layout.activity_main); Builder. SetPositiveButton (" sure," new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { alertDialog.dismiss(); }}); Builder. SetNegativeButton (" cancel ", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { alertDialog.dismiss(); }}); alertDialog = builder.create(); alertDialog.show();Copy the code

The above code is familiar, mainly using one of the design patterns – the Builder pattern,

  1. Build the AlertDialog.Builder object
  2. The Builder.setxxx family of methods completes the initialization of the Dialog
  3. Call the Builder.create () method to create an AlertDialog
  4. Call AlertDialog’s show() to finish drawing the View and display the AlertDialog

The creation and display of Dialog are mainly completed through the above four steps. Next, the specific implementation of each method is analyzed according to the source code, and how to associate the view of Dialog with Window

1 Create an Alertdialog. Builder object

AlertDialog.Builder builder = new AlertDialog.Builder(this);
Copy the code

Alertdialog. Builder is an internal class of AlertDialog that encapsulates the construction process of an AlertDialog. Look at the Builder’s constructor frameworks/base/core/Java/android/app/AlertDialog. Java

/ / AlertController AlertParams types of member variables private final AlertController. AlertParams P; public Builder(Context context) { this(context, resolveDialogTheme(context, Resources.ID_NULL)); } public Builder(Context Context, int themeResId) {// Construct ContextThemeWrapper, ContextThemeWrapper is a subclass of Context, / / relevant to the subject matter are mainly used for processing and initialize become variable P P = new AlertController. AlertParams (new ContextThemeWrapper (context, resolveDialogTheme(context, themeResId))); }Copy the code
  • ContextThemeWrapper is inherited from ContextWrapper, Application and Service from ContextWrapper, and Activity from ContextThemeWrapper

  • P is AlertDialog. The Builder AlertController. AlertParams types of member variables
  • AlertParams contains the member variables corresponding to the AlertDialog view, and after calling the Builder.setxxx series of methods, the parameters we pass are stored in P

1.1 AlertParams encapsulates initialization parameters

AlertController AlertParams is AlertController inner classes, Responsible for the initialization parameter AlertDialog frameworks/base/core/Java/com/android/internal/app/AlertController. Java

public AlertParams(Context context) { mContext = context; MCancelable = true; mCancelable = true; MInflater = (LayoutInflater) mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); }Copy the code
  • Mainly performed AlertController. AlertParams initialization, initialize some member variables, LayoutInflater to parse the main layout. The XML file, Layoutinflaters are inflaters that can be loaded with layoutInflaters.
  • Once AlertParams is initialized, the alertDialog.Builder is completed

2 Call the setXXX series of alertdialog. Builder methods

AlertDialog. Builder initialization completes, calls its Builder. The setXXX series method to complete the Dialog initialization frameworks/base/core/Java/android/app/AlertDialog. Java

/ /... Public builder setTitle(@stringres int titleId) {P.mTitle = P.m context.gettext (titleId); return this; } public Builder setMessage(@StringRes int messageId) { P.mMessage = P.mContext.getText(messageId); return this; } public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) { P.mPositiveButtonText = P.mContext.getText(textId); P.mPositiveButtonListener = listener; return this; } public Builder setPositiveButton(CharSequence text, final OnClickListener listener) { P.mPositiveButtonText = text; P.mPositiveButtonListener = listener; return this; } / /... Omitted many builder.setxxx methodsCopy the code

All of the above setXXX methods assign a value to the Builder member variable P, and their return value is of type Builder, so they can be called as a message chain

builder.setTitle().setMessage().setPositiveButton()...
Copy the code

PS: In Kotlin, we should try to avoid using the builder mode. We should use the named optional parameter in Kotlin to achieve the builder mode, which makes the code more concise. In order not to affect the reading fluency, we put this part in the end of the article in the extended reading section

3 builder. The create method

Builder. setXXX series of methods after calling builder.create method to complete the AlertDialog construction, Then take a look at the create method frameworks/base/core/Java/android/app/AlertDialog. Java

Public AlertDialog Create () {// P.mContext is an instance of ContextWrappedTheme final AlertDialog dialog = new AlertDialog(P.mContext, 0, false); / / parameter Dialog is stored in this class/P/mAler AlertController instance, P the variable to AlertController. Through this method AlertParams P.a pply (Dialog. MAlert); // mCancelable defaults to true dialog.setCancelable(P.mCancelable); / / if can cancel set callback to monitor the if (p. Cancelable) {dialog. SetCanceledOnTouchOutside (true); } / / set a series of listening dialog. SetOnCancelListener (p. OnCancelListener); dialog.setOnDismissListener(P.mOnDismissListener); if (P.mOnKeyListener ! = null) { dialog.setOnKeyListener(P.mOnKeyListener); } // Return the AlertDialog object. }Copy the code
  • An AlertDialog is built based on p. Contex
  • MAler is AlertController instance, call the apply method pass AlertController. The P variables in AlertParams
  • Set whether you can click external cancel, which you can by default, and set callback listening
  • Finally, the AlertDialog object is returned

3.1 How to Build an AlertDialog

Let us analysis the AlertDialog is how to build, take a look at it the building method of concrete implementation frameworks/base/core/Java/android/app/AlertDialog. Java

AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0, createContextThemeWrapper); mWindow.alwaysReadCloseOnTouchAttr(); // getContext() returns ContextWrapperTheme // getWindow() returns PhoneWindow // mAlert is an instance of AlertController mAlert = AlertController.create(getContext(), this, getWindow()); }Copy the code

When was PhoneWindows created? AlertDialog inherited from Dialog, first call the super constructor, look at the constructor Dialog frameworks/base/core/Java/android/app/Dialog. Java

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { ... MWindowManager = (WindowManager) context.getSystemService(context.window_service); // Build PhoneWindow final Window w = new PhoneWindow(mContext); // mWindow is PhoneWindow instance mWindow = w; w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setOnWindowSwipeDismissedCallback(() -> { if (mCancelable) { cancel(); }}); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); MListenersHandler = new ListenersHandler(this); }Copy the code
  • Get the WindowManager object, build the PhoneWindow, and now we know that the PhoneWindow is created in the Dialog constructor
  • Initialize the Dialog member variable mWindow, which is an instance of PhoneWindow
  • Initialize the Dialog member variable mListenersHandler, which inherits Handler

Let’s go back to the AlertDialog constructor, and inside the AlertDialog constructor, we call the AlertController.create method, so let’s look at this method

public static final AlertController create(Context context, DialogInterface di, Window window) { final TypedArray a = context.obtainStyledAttributes( null, R.styleable.AlertDialog, R.attr.alertDialogStyle, R.style.Theme_DeviceDefault_Settings); int controllerType = a.getInt(R.styleable.AlertDialog_controllerType, 0); a.recycle(); // Use different AlertController switch (controllerType) {case MICRO: // MicroAlertController is matrix style inherited from AlertController return new MicroAlertController(context, di, window); default: return new AlertController(context, di, window); }}Copy the code

ControllerType returns different AlertControllers. How is an AlertDialog built

4 Call Dialog’s show method to display the Dialog

Call the create method of AlertDialog.Builder to return the instance of AlertDialog, and finally call the show method of AlertDialog to display dialog, but AlertDialog is inherited from dialog, Actually call is Dialog show method frameworks/base/core/Java/android/app/Dialog. Java

Public void show() {// mShowing variable used to indicate whether the current dialog is showing if (mShowing) {if (mDecor! = null) { if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); } mDecor.setVisibility(View.VISIBLE); } return; } mCanceled = false; // mCreated specifies that the dispatchOnCreate method is executed only once if (! mCreated) { dispatchOnCreate(null); } else { // Fill the DecorView in on any configuration changes that // may have occured while it was removed from the WindowManager. final Configuration config = mContext.getResources().getConfiguration(); mWindow.getDecorView().dispatchConfigurationChanged(config); } // Set ActionBar onStart(); // getDecorView mDecor = mwindow.getdecorview (); if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { final ApplicationInfo info = mContext.getApplicationInfo(); mWindow.setDefaultIcon(info.icon); mWindow.setDefaultLogo(info.logo); mActionBar = new WindowDecorActionBar(this); } / / to get layout parameters WindowManager. LayoutParams. L = mWindow getAttributes (); boolean restoreSoftInputMode = false; if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) { l.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; restoreSoftInputMode = true; } // Add DecorView and layout parameters to WindowManager to finish drawing the view mWindowManager.addView(mDecor, L); if (restoreSoftInputMode) { l.softInputMode &= ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; } mShowing = true; // Send a Dialog message to Handler to display AlertDialog sendShowMessage(); }Copy the code
  • Check if dialog is displayed and return if so
  • Check whether the dispatchOnCreate method has been invoked. If not, invoke the dispatchOnCreate method
  • Get the layout parameters and add them to the WindowManager, and call the addView method to finish drawing the view
  • Sends a Dialog message to the Handler to display the AlertDialog

4.1 dispatchOnCreate

In the above code, determine whether the dispatchOnCreate method has been called based on the mCreated variable, If not then call dispatchOnCreate method frameworks/base/core/Java/android/app/Dialog. Java

void dispatchOnCreate(Bundle savedInstanceState) { if (! MCreated) {// call onCreate onCreate(savedInstanceState); mCreated = true; }}Copy the code

In the dispatchOnCreate method, we call the onCreate method of the Dialog, and the onCreate method of the Dialog is empty, because we’re creating an AlertDialog object, and AlertDialog inherits from Dialog, So call the AlertDialog onCreate method frameworks/base/core/Java/android/app/AlertDialog. Java

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mAlert.installContent();
}
Copy the code

In this method, we call the installContent method of the AlertController, Look at some of the specific implementation logic frameworks/base/core/Java/com/android/internal/app/AlertController. Java

Void installContent() {void installContent() {void installContent() {void installContent(); // Call the setContentView method to parse the layout file mwindow.setContentView (contentView); // Initialize the component setupView() in the layout file; }Copy the code
  • Call the selectContentView method to get the layout file and see how it works

frameworks/base/core/java/com/android/internal/app/AlertController.java

private int selectContentView() {
    if (mButtonPanelSideLayout == 0) {
        return mAlertDialogLayout;
    }
    if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) {
        return mButtonPanelSideLayout;
    }
    return mAlertDialogLayout;
}
Copy the code

The returned layout is mAlertDialogLayout, Layout file is initialized in AlertController constructor frameworks/base/core/Java/com/android/internal/app/AlertController. Java

mAlertDialogLayout = a.getResourceId(
                R.styleable.AlertDialog_layout, R.layout.alert_dialog);
Copy the code
  • Window.setContentView is called to parse the layout file, and the Activity’s setContentView finally calls window. setContentView. You can refer to the previous article Activity layout loading process 0xA03 Android 10 source code analysis: Apk loading process resource loading
  • Call the setupView method to initialize the components in the layout file, where the dispatchOnCreate method analysis ends

4.2 Call mWindowManager.addView to complete the View drawing

Back to our Dialog show method, after dispatchOnCreate method was performed, and call the onStart method, this method is mainly used to set the ActionBar, then initializes the WindowManager. LayoutParams object, Last call mWindowManager. AddView () method of drawing interface, mapped call sendShowMessage method after frameworks/base/core/Java/android/app/Dialog. Java

private void sendShowMessage() {
    if (mShowMessage != null) {
        // Obtain a new message so this dialog can be re-used
        Message.obtain(mShowMessage).sendToTarget();
    }
}
Copy the code

Display an AlertDialog by sending a Dialog message to the Handler, which is eventually executed in the handleMessage method of the ListenersHandler, the Dialog’s inner class, Inherit the Handler frameworks/base/core/Java/android/app/Dialog. Java

public void handleMessage(Message msg) { switch (msg.what) { case DISMISS: ((OnDismissListener) msg.obj).onDismiss(mDialog.get()); break; case CANCEL: ((OnCancelListener) msg.obj).onCancel(mDialog.get()); break; case SHOW: ((OnShowListener) msg.obj).onShow(mDialog.get()); break; }}Copy the code

If msg.what = SHOW, the onshowlistener. onShow method is executed, MSG. What’s value assignment and OnShowListener call setOnShowListener method frameworks/base/core/Java/android/app/Dialog. Java

public void setOnShowListener(@Nullable OnShowListener listener) {
    if (listener != null) {
        mShowMessage = mListenersHandler.obtainMessage(SHOW, listener);
    } else {
        mShowMessage = null;
    }
}
Copy the code

The mListenersHandler constructs the Message object that is received by the mListenersHandler when we send a showMessage in the Dialog

4.3 How to bind the view of the custom Dialog

In the above analysis, according to the mCreated variable, determine whether the dispatchOnCreate method has been called. If not, the dispatchOnCreate method is called. In the dispatchOnCreate method, the onCreate method of Dialog is mainly called. Since the AlertDialog object is created and AlertDialog inherits from Dialog, the onCreate method of AlertDialog is actually called to parse the layout file and initialize its controls

Similarly, our custom CustomDialog inherits from Dialog, so we call our custom CustomDialog onCreate method as follows

public class CustomDialog extends Dialog { Context mContext; / /... Override protected void onCreate(Bundle savedInstanceState) {super.oncreate (savedInstanceState); LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.custom_dialog, null); setContentView(view); }}Copy the code

In the onCreate method calls the Dialog the setContentView method, to analyze the setContentView method frameworks/base/core/Java/android/app/Dialog. Java

public void setContentView(@NonNull View view) {
    mWindow.setContentView(view);
}
Copy the code

MWindow is an instance of PhoneWindow, and the last call to the PhoneWindow setContentView parses the layout file, The setContentView of the Activity finally calls the PhoneWindow setContentView method. For details, see the previous article Activity layout loading process 0xA03 Android 10 source code analysis: Apk resource loading process

conclusion

The display logic of Dialog and Activity is similar. They both manage the Window object internally and use the Window object to realize the loading and display logic of the interface

Dialog creation process?

  1. Build the AlertDialog.Builder object
  2. The Builder.setxxx family of methods completes the initialization of the Dialog
  3. Call the Builder.create () method to create an AlertDialog
  4. Call AlertDialog’s show() to initialize the layout file of the Dialog, the Window object, etc., and then execute the mWindowManager.addView method to begin drawing the View and finally display the Dialog

How is the Dialog view associated with the Window?

  • The Window object is initialized in the Dialog constructor
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { ... MWindowManager = (WindowManager) context.getSystemService(context.window_service); // Build PhoneWindow final Window w = new PhoneWindow(mContext); // mWindow is PhoneWindow instance mWindow = w; . }Copy the code
  • Call the Dialog show method to complete the view drawing and Dialog display
Public void show() {// getDecorView mDecor = mwindow.getdecorview (); / / to get layout parameters WindowManager. LayoutParams. L = mWindow getAttributes (); // Add DecorView and layout parameters to WindowManager mWindowManager.addView(mDecor, L); }Copy the code

Eventually, the DecorView is added to the Window through the WindowManager, and the Window object is used to load and display the interface logic

How to bind a CustomDialog view?

  • Call the “show” method of the Dialog. Inside the method, according to the mCreated variable, it will determine whether the dispatchOnCreate method has been called. If not, it will call the “dispatchOnCreate” method. The main call to Dialog’s onCreate method is in the dispatchOnCreate method
  • Custom CustomDialog inherits from Dialog, so call the custom CustomDialog onCreate method. Call setContentView from the CustomDialog onCreate method. The last call is PhoneWindow setContentView parsing layout file, parsing process refer to 0xA03 Android 10 source code analysis: Apk loading process resource loading

How do I use Kotlin named optional parameters to construct classes that implement the Builder pattern?

Refer to the extended reading section for this section

Constructing classes with named optional parameters has the following advantages over Java’s builder pattern?

  • The code is very concise
  • Each parameter name can be displayed, declare the object does not need to be written in order, very flexible
  • Each parameter in the constructor is declared by val, which is safer in multi-threaded concurrent business scenarios
  • Kotlin’s require method, let’s be a little bit friendlier with parameter constraints

How to use DataBinding in Dialog?

Refer to the extended reading section for this section

Further reading

1. Kotlin implements the Builder pattern

As mentioned earlier, you should avoid using the builder pattern in Kotlin. Instead, use Kotlin’s named optional parameters to construct classes and implement the builder pattern for cleaner code

In the introduction of the Builder pattern in the Effective Java book, it is described this way: In essence, the Builder pattern emulates named computable parameters, just as in Ada and Python

For Java to implement a custom dialog with the builder pattern, it is not shown here, you can baidu, Google search, the code appears very long…….. Fortunately, Kotlin is a transformation language with named optional parameters. Both functions and constructors in Kotlin support this feature. Next, we will construct classes using named optional parameters to implement the Builder pattern.

class AppDialog(
    context: Context,
    val title: String? = null,
    val message: String? = null,
    val yes: AppDialog.() -> Unit
) : DataBindingDialog(context, R.style.AppDialog) {

    init {
        requireNotNull(message) { "message must be not null" }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        requestWindowFeature(Window.FEATURE_NO_TITLE)

        setContentView(root)
        display.text = message
        btnNo.setOnClickListener { dismiss() }
        btnYes.setOnClickListener { yes() }
    }
}
Copy the code

The invocation is also simpler

AppDialog(
        context = this@MainActivity,
        message = msg,
        yes = {
            // do something
        }).show()
Copy the code

Constructing classes with named optional parameters has the following advantages over Java’s builder pattern:

  • The code is very concise
  • Each parameter name can be displayed, declare the object does not need to be written in order, very flexible
  • Each parameter in the constructor is declared by val, which is safer in multi-threaded concurrent business scenarios
  • Kotlin’s require method, let’s be a little bit friendlier with parameter constraints

2. How to use DataBinding in Dialog

What is DataBinding? Check out the Google website for more details

DataBinding is Google’s DataBinding support library in Jetpack that allows you to directly bind your application’s data sources in page components

Based on the Dailog builder pattern implemented with Kotlin’s named optional parameter constructor class, the DataBinding secondary encapsulation, along with the DataBinding DataBinding feature, makes the Dialog much simpler and easier to use

Step1: define a base class, DataBindingDialog

abstract class DataBindingDialog(@NonNull context: Context, @StyleRes themeResId: Int) :
    Dialog(context, themeResId) {

    protected inline fun <reified T : ViewDataBinding> binding(@LayoutRes resId: Int): Lazy<T> =
        lazy {
            requireNotNull(
                DataBindingUtil.bind<T>(LayoutInflater.from(context).inflate(resId, null))
            ) { "cannot find the matched view to layout." }
        }

}
Copy the code

Step2: transform AppDialog

class AppDialog( context: Context, val title: String? = null, val message: String? = null, val yes: AppDialog.() -> Unit ) : DataBindingDialog(context, R.style.AppDialog) { private val mBinding: DialogAppBinding by binding(R.layout.dialog_app) init { requireNotNull(message) { "message must be not null" } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) requestWindowFeature(Window.FEATURE_NO_TITLE) mBinding.apply { setContentView(root)  display.text = message btnNo.setOnClickListener { dismiss() } btnYes.setOnClickListener { yes() } } } }Copy the code

The same is true of DataBinding for activities, fragments, and Adapters. Kotlin’s inline, reified, DSL, and other syntax can be used to design simpler and maintainable code

DataBindingActivity, DataBindingFragment, and DataBindingDialog base libraries based on DataBinding encapsulation code, will be improved in the future, click JDataBinding to check. Welcome to the start

conclusion

Dedicated to share a series of Android system source code, reverse analysis, algorithm, translation, Jetpack source code related articles, is trying to write a better article, if this article is helpful to you to give a star, what is not written in the article clearly, or have better advice, welcome to leave a message, welcome to learn together, Moving forward together on the technological road.

Plan to establish a most complete and latest AndroidX Jetpack related components of the actual combat project and related components of the principle of analysis article, is gradually increasing Jetpack new members, the warehouse continues to update, you can go to check: Androidx-jetpack-practice, if this warehouse is helpful to you, please give me a thumbs up and I will finish more project practices for new members of Jetpack one after another.

algorithm

Since LeetCode has a large question bank, hundreds of questions can be selected for each category. Due to the limited energy of each person, it is impossible to brush all the questions. Therefore, I sorted the questions according to the classic types and the difficulty of the questions.

  • Data structures: arrays, stacks, queues, strings, linked lists, trees…
  • Algorithms: Search algorithm, search algorithm, bit operation, sorting, mathematics,…

Each problem will be implemented in Java and Kotlin, and each problem has its own solution ideas, time complexity and space complexity. If you like algorithms and LeetCode like me, you can pay attention to my LeetCode problem solution on GitHub: Leetcode-Solutions-with-Java-And-Kotlin, come to learn together And look forward to growing with you.

Android 10 source code series

I’m writing a series of Android 10 source code analysis articles. Knowing the system source code is not only helpful in analyzing problems, but also very helpful in the interview process. If you like to study Android source code as MUCH as I do, You can follow my Android10-source-Analysis on GitHub, and all articles will be synchronized to this repository.

  • How is APK generated
  • APK installation process
  • 0xA03 Android 10 source code analysis: APK loading process of resource loading
  • Android 10 source code: APK
  • Dialog loading and drawing process and use in Kotlin, DataBinding
  • More……

Tool series

  • Shortcuts to AndroidStudio that few people know
  • Shortcuts to AndroidStudio that few people know
  • All you need to know about ADB commands
  • How to get video screenshots efficiently
  • 10 minutes introduction to Shell scripting
  • How to package Kotlin + Android Databinding in your project

The reverse series

  • Dynamically debug APP based on Smali file Android Studio
  • The Android Device Monitor tool cannot be found in Android Studio 3.2