QSTitle Creation Process

Keywords: Android Java socket xml

This paper will analyze the creation process of QSTitle component in Android 6.0, starting from the boot-up init process to the specific creation of each QSTitle object, how to add the drop-down status bar to sort out the related overall process of QSTitle.

When the android device is powered on, the bootstrap boots into boot (usually uboot), loads initramfs and kernel images, and starts the kernel, then enters the user-mode program. The first user space program is init, and the PID is fixed at 1.
On the android system, the code of init.cpp is located under / system/core/init. Its basic functions are as follows:

  • Management equipment
  • Parse and process the Android startup script init.rc
  • Real-time maintenance of services in this init.rc, including loading Zygote

There is only too much analysis of init.cpp, focusing on Zygote's startup process. init,cpp section recommends related documents http://my.oschina.net/youranhongcha/blog/469028

The init.rc configuration file is located in system/core/rootdir in Android 6.0 code
/ init.zygote32.rc.

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd
    writepid /dev/cpuset/foreground/tasks

Among them:

  1. The keyword service tells the init process to create a process named zygote, and the program to execute is / system/bin/app_process, followed by the passed parameters.
  2. Note the parameter -- start-system-server, which indicates that you want to start System Server
  3. socket zygote stream 660 root system represents the creation of a socket named zygote.
  4. The onrestart keyword that follows represents the command to be executed when the Zygo process restarts.

Start-up of Zygote

We can draw a conclusion from this: zygote is only the name of the service, and the corresponding program for this service is app_process program. We study the implementation of zygote, that is, to study app_process program.
The app_process code is located in frameworks/base/cmds/app_process
/ app_main.cpp, the entry function is main.
The general process of main is as follows:

  • Create an AppRuntime instance runtime
  • Resolve incoming command-line parameters
  • Determine which runtime.start to call and pass in the corresponding parameters
  • Since the parameter - Xzygote/system/bin -- zygote -- start-system-server is passed in init.zygote32.rc, it will execute:
    runtime.start("com.android.internal.os.ZygoteInit", args, zygote);

So app_process doesn't do anything important, just jump to the Java class com.android.internal.os.ZygoteInit. It seems that all the work is done in ZygoteInit. But how does ZygoteInit start?
Let's take a look at AppRuntime.

AndroidRuntime

The runtime.start method is derived from the parent Android Runtime of AppRuntime, and the code is located in frameworks / base / core / JNI / Android Runtime. cpp. Focus on the Android Runtime start method, which does three main things:

First, create virtual machines:

/* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) != 0) {
        return;
    }
    onVmCreated(env);

Then the startReg function is called to register the JNI method:

/*
     * Register android functions.
     */
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }

Finally, parameters are constructed, JNI class objects are created, main methods are obtained, and the following line is executed to execute main entries:

  env->CallStaticVoidMethod(startClass, startMeth, strArray);

So we successfully started a virtual machine from AndroidRuntime.cpp, loaded the Java class ZygoteInit, and went into its main method to execute.

ZygoteInit

Then came ZygoteInit.java's main method, code-bit frameworks/
base/core/java/com/android/internal/os/ZygoteInit.java. Look at its main method:

public static void main(String argv[]) {
   try {
       ...
       registerZygoteSocket(socketName);
       ...
       if (startSystemServer) {
           startSystemServer(abiList, socketName);
       }
       ...
       runSelectLoop(abiList);
       ...
   } catch (...) { ... }
}

First, call registerZygoteSocket to create a socket named zygote:

registerZygoteSocket(socketName);

Then start the System Server component:

if (startSystemServer) {
startSystemServer(abiList, socketName);
}

Finally, call runSelectLoopMode to enter a dead loop, waiting to receive a request from the Activity Manager Service on the socket to create the application:

runSelectLoop(abiList);

The post-startSystem Server (abiList, socketName) was analyzed.
Start the System Server component
System Server is called System Service Process and is responsible for starting key services of Android system. Let's look at the main implementation of the function:

private static boolean startSystemServer(String abiList, String socketName)
       throws MethodAndArgsCaller, RuntimeException {
    ...
    /* Request to fork the system server process */
    pid = Zygote.forkSystemServer(
        parsedArgs.uid, parsedArgs.gid,
        parsedArgs.gids,
        parsedArgs.debugFlags,
        null,
        parsedArgs.permittedCapabilities,
        parsedArgs.effectiveCapabilities);
    ...
    /* For child process */
    if (pid == 0) {
        ...
        handleSystemServerProcess(parsedArgs);
    }
    return true;
}

This calls Zygote's static method forkSystem Server to create the System Server process.
In forkSystem Server, JNI calls forkSystem Server-com_android_internal_os_Zygote_nativeForkSystem Server-ForkAndSpecializeCommon to successfully fork a new process.
After the child process is created, there is a sentence

handleSystemServerProcess(parsedArgs);

Enter handleSystem Server Process:

/**
     * Finish remaining work for the newly forked system server process.
     */
    private static void handleSystemServerProcess(
            ZygoteConnection.Arguments parsedArgs)
            throws ZygoteInit.MethodAndArgsCaller {
    ...
    final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
    ...
    if (...) {
        ...
    } else {
      ClassLoader cl = null;
      if (systemServerClasspath != null) {
          cl = new PathClassLoader(systemServerClasspath, ClassLoader.getSystemClassLoader());
          Thread.currentThread().setContextClassLoader(cl);
      }

      /*
       * Pass the remaining arguments to SystemServer.
       */
      RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);
    }

From this, we can see that we get the class name of System Server from the environment variable SYSTEMSERVERCLASSPATH, then load it in, and finally run it with RuntimeInit.zygoteInit, which executes the main method of System Server.

Take a look at RuntimeInit.zygoteInit():

public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
        throws ZygoteInit.MethodAndArgsCaller {
    ...
    commonInit();   // Basic settings (exception capture, time zone, HTTP User-Agent, etc.)
    nativeZygoteInit();
    applicationInit(targetSdkVersion, argv, classLoader); // Call the Main method
}

nativeZygoteInit() is a jni call located in Android Runtime. cpp:

static void com_android_internal_os_RuntimeInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
{
    gCurRuntime->onZygoteInit();
}

onZygoteInit() is a method in AppRuntime, specifically:

virtual void onZygoteInit()
{
   sp<ProcessState> proc = ProcessState::self();
   ALOGV("App process: starting thread pool.\n");
   proc->startThreadPool();
}

Start a thread pool. Native ZygoteInit () is analyzed here, so it can be seen that the process of starting System Server by Zygote is over, and then it is all within System Server.

SystemServer.main()

As mentioned above, RuntimeInit.zygoteInit executes the System Server. main method. Let's look at SystemServer.main():

/**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }

Generate the System Server object and jump to run();
Go to run() and see:

private void run() {
    ......

     // Start services.
        try {
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
            ......
       } catch (Throwable ex) {
           ...
            throw ex;
        }
        ...
        // Loop forever.
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
}

Start Bootstrap Services (); Start Bootstrap Services (); Start Bootstrap Services (); Start Bootstrap Services (); Start Bootstrap Services (); Start Bootstrap Services (); Start Boo

  private void startBootstrapServices() {
        ......
        Installer installer = mSystemServiceManager.startService(Installer.class);

        // Activity manager runs the show.
        mActivityManagerService = mSystemServiceManager.startService(
                ActivityManagerService.Lifecycle.class).getService();
        mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
        ......
 }

Activity manager runs
Back to System Server, run () has the following sentence:

startOtherServices();

StartOther Services () code:

private void startOtherServices() {
        final Context context = mSystemContext;
        AccountManagerService accountManager = null;
        ContentService contentService = null;
        .......

         mActivityManagerService.systemReady(new Runnable() {
            @Override
            public void run() {
              ......

                try {
                    startSystemUi(context);
                } catch (Throwable e) {
                    reportWtf("starting System UI", e);
                }
         .......

Here mActivityManagerService.systemReady creates a thread to call the startSystemUi(context);

static final void startSystemUi(Context context) {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.android.systemui",
                    "com.android.systemui.SystemUIService"));
        //Slog.d(TAG, "Starting service: " + intent);
        context.startServiceAsUser(intent, UserHandle.OWNER);
    }

Through intent.setComponent(new ComponentName("com.android.systemui"),
"com.android.systemui.SystemUIService"));
Set up the System UI Service to start the system Mui program
Enter System UI Service:

public class SystemUIService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
    }
......

The onCreate method obtains the System UI Application object and calls its startServicesIfNeeded method:

 public void startServicesIfNeeded() {
        final int N = SERVICES.length;
        for (int i=0; i<N; i++) {
            Class<?> cl = SERVICES[i];
            try {
                mServices[i] = (SystemUI)cl.newInstance();//Loading instance
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InstantiationException ex) {
                throw new RuntimeException(ex);
            }
            mServices[i].mContext = this;
            mServices[i].mComponents = mComponents;
            mServices[i].start();//start services
            if (mBootCompleted) {
                mServices[i].onBootCompleted();
            }
        }
        mServicesStarted = true;
    }

In this method, the mServices Started flag is first used to determine whether the system UI-related services are started.
At the same time, check whether Activity Manager Service finishBooting or not according to the system configuration file.
You can see here that there is an array of mServices, which loads their instances through a for loop and calls their start().
But what services does the mServices array actually open?
Let's look at the variables in the System UI Application class:

private final Class<?>[] SERVICES = new Class[] {
            com.android.systemui.tuner.TunerService.class,
            com.android.systemui.keyguard.KeyguardViewMediator.class,
            com.android.systemui.recents.Recents.class,
            com.android.systemui.volume.VolumeUI.class,
            com.android.systemui.statusbar.SystemBars.class,
            com.android.systemui.usb.StorageNotification.class,
            com.android.systemui.power.PowerUI.class,
            com.android.systemui.media.RingtonePlayer.class,
            com.android.systemui.keyboard.KeyboardUI.class,
    };

As you can see, there are many services commonly used in sytemui. We will focus on SystemBars and TunerService later.
Go directly to SystemBars.start():

public void start() {
        mServiceMonitor = new ServiceMonitor(TAG, DEBUG,
                mContext, Settings.Secure.BAR_SERVICE_COMPONENT, this);
        mServiceMonitor.start();  // will call onNoService if no remote service is found
    }

Create a ServiceMonitor instance in start and start();
The comment states that when / service is not started, Service Monitor calls back on NoService of SystemBars/
So look at the onNoService of SystemBars:

public void onNoService() {
        createStatusBarFromConfig();  // fallback to using an in-process implementation
    }

Go straight to createStatusBarFromConfig():

private void createStatusBarFromConfig() {
        final String clsName = mContext.getString(R.string.config_statusBarComponent);
        if (clsName == null || clsName.length() == 0) {
            throw andLog("No status bar component configured", null);
        }
        Class<?> cls = null;
        try {
            cls = mContext.getClassLoader().loadClass(clsName);
        } catch (Throwable t) {
            throw andLog("Error loading status bar component: " + clsName, t);
        }
        try {
            mStatusBar = (BaseStatusBar) cls.newInstance();
        } catch (Throwable t) {
            throw andLog("Error creating status bar component: " + clsName, t);
        }
        mStatusBar.mContext = mContext;
        mStatusBar.mComponents = mComponents;
        mStatusBar.start();
    }

The string given by clsName is com.android.systemui.statusbar.phone.PhoneStatusBar
An example of PhoneStatusBar is obtained through reflection mechanism:

cls = mContext.getClassLoader().loadClass(clsName);
...
mStatusBar = (BaseStatusBar) cls.newInstance();

And call the start method:
PhoneStatusBar inherits from BaseStatusBar;
The start() of BaseStatusBar is called in PhoneStatusBar.
BaseStatusBar.start():

public void start() {
        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
        mDisplay = mWindowManager.getDefaultDisplay();
        mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService(
                Context.DEVICE_POLICY_SERVICE);

        mNotificationColorUtil = NotificationColorUtil.getInstance(mContext);

        mNotificationData = new NotificationData(this);

        mAccessibilityManager = (AccessibilityManager)
                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);

        mDreamManager = IDreamManager.Stub.asInterface(
                ServiceManager.checkService(DreamService.DREAM_SERVICE));
        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);

        .......
        //Instances of many common objects, services, managers, Observer s, etc. in system Mui are provided here.
        .......

        createAndAddWindows(); //A very important method for this article

View createAndAddWindows():

 protected abstract void createAndAddWindows();

It's an abstract method, apparently called in a subclass of BaseStatusBar, PhoneStatusBar.
PhoneStatusBar's createAndAddWindows():

 @Override
 public void createAndAddWindows() {
        addStatusBarWindow();
 }

 private void addStatusBarWindow() {
        makeStatusBarView();//Key method to create StatusBarView
        mStatusBarWindowManager = new StatusBarWindowManager(mContext);
        mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
 }

You can see that the makeStatusBarView method is finally invoked here:

protected PhoneStatusBarView makeStatusBarView() {
        final Context context = mContext;

        Resources res = context.getResources();

        updateDisplaySize(); // populates mDisplayMetrics
        updateResources();

        mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
                R.layout.super_status_bar, null);
        ........
        mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
        mStatusBarView.setBar(this);

        PanelHolder holder = (PanelHolder) mStatusBarWindow.findViewById(R.id.panel_holder);
        mStatusBarView.setPanelHolder(holder);

        mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
                R.id.notification_panel);
        mNotificationPanel.setStatusBar(this);

        if (!ActivityManager.isHighEndGfx()) {
            mStatusBarWindow.setBackground(null);
            mNotificationPanel.setBackground(new FastColorDrawable(context.getColor(
                    R.color.notification_panel_solid_background)));
        }
        ........

        mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
        mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view);
        mKeyguardBottomArea =
                (KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
        ........
        //You can see that many view creation of system Mui key components have been completed here. This method is very important.
        ........
        // Set up the quick settings tile panel
        mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
        if (mQSPanel != null) {
            final QSTileHost qsh = new QSTileHost(mContext, this,
                    mBluetoothController, mLocationController, mRotationLockController,
                    mNetworkController, mZenModeController, mHotspotController,
                    mCastController, mFlashlightController,
                    mUserSwitcherController, mKeyguardMonitor,
                    mSecurityController,
                    mAudioProfileController
                    );
            mQSPanel.setHost(qsh);
            mQSPanel.setTiles(qsh.getTiles());   
            mHeader.setQSPanel(mQSPanel);
            qsh.setCallback(new QSTileHost.Callback() {
                @Override
                public void onTilesChanged() {
                    mQSPanel.setTiles(qsh.getTiles());
                }
            });
        }
        .........

Looking at QSPanel, we found our target, which is a key class in the drop-down status bar.
After mQSPanel loads the xml layout, create the QSTileHost object.
Look directly at QSTileHost's inheritance relationship and construction method:

public class QSTileHost implements QSTile.Host, Tunable {
    .......
    //Here QSTileHost inherits the Tunable interface, and a very classic java callback implementation will follow.


public QSTileHost(Context context, PhoneStatusBar statusBar,

        ........

          TunerService.get(mContext).addTunable(this, TILES_SETTING);
          //The first step of callback implementation
          //Here's this.
          //QSTileHost, which inherits the Tunable interface, is passed to addTunable() of TunerService.

    }

The emphasis is addTunable of TunerService. TunerService looks familiar. Look up at the article. It's with system mbars.
Instantiate and open in System UI Application
Let's look at TunerService's addTunable:

public void addTunable(Tunable tunable, String... keys) {
        for (String key : keys) {
            addTunable(tunable, key);
        }
    }

    private void addTunable(Tunable tunable, String key) {
        if (!mTunableLookup.containsKey(key)) {
            mTunableLookup.put(key, new ArrayList<Tunable>());
        }
        mTunableLookup.get(key).add(tunable);
        Uri uri = Settings.Secure.getUriFor(key);
        if (!mListeningUris.containsKey(uri)) {
            mListeningUris.put(uri, key);
            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
        }
        // Send the first state.
        String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
        //The value here will get a long character, including all system QSTitle component names, which will be split later to generate specific QSTitle objects.
        //Examples: wifi, location, data connection, hotspot, audioprofile, bt, rotation, airplane, screenshot
        tunable.onTuningChanged(key, value);//In the second step, the QSTileHost object passed by addTunable calls its own onTuningChanged method
    }

Look at QSTileHost's onTuning Changed ()

@Override
    public void onTuningChanged(String key, String newValue) {
        if (!TILES_SETTING.equals(key)) {
            return;
        }
        final List<String> tileSpecs = loadTileSpecs(newValue);//The new Value passed by the cut is List
        if (tileSpecs.equals(mTileSpecs)) return;
        for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
            if (!tileSpecs.contains(tile.getKey())) {
                tile.getValue().destroy();
            }
        }
        final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
        for (String tileSpec : tileSpecs) {
            if (mTiles.containsKey(tileSpec)) {
                newTiles.put(tileSpec, mTiles.get(tileSpec));
            } else {
                if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
                try {
                    newTiles.put(tileSpec, createTile(tileSpec));
                    //Create the corresponding object in createTile () based on the QSTitle name cut above
                } catch (Throwable t) {
                }
            }
        }
        mTileSpecs.clear();
        mTileSpecs.addAll(tileSpecs);
        mTiles.clear();
        mTiles.putAll(newTiles);
        if (mCallback != null) {
            mCallback.onTilesChanged();
        }
    }

Look at createTile(tileSpec)

protected QSTile<?> createTile(String tileSpec) {
        if (tileSpec.equals("wifi")) return new WifiTile(this);
        else if (tileSpec.equals("bt")) return new BluetoothTile(this);
        else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
        else if (tileSpec.equals("cell")) return new CellularTile(this);
        else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);
        else if (tileSpec.equals("dnd")) return new DndTile(this);
        else if (tileSpec.equals("rotation")) return new RotationLockTile(this);
        else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);
        else if (tileSpec.equals("location")) return new LocationTile(this);
        else if (tileSpec.equals("cast")) return new CastTile(this);
        else if (tileSpec.equals("hotspot")) return new HotspotTile(this);
        else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
    }

You can see here that each object of QSTitle is created specifically.
So how do these QSTitle objects load into the View of QSPanel?
Where did QSPanel join the StatusBarHeaderView?
Look back at PhoneStatusBar's makeStatusBarView method:

// Set up the quick settings tile panel
        mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
        if (mQSPanel != null) {
            final QSTileHost qsh = new QSTileHost(mContext, this,
                    mBluetoothController, mLocationController, mRotationLockController,
                    mNetworkController, mZenModeController, mHotspotController,
                    mCastController, mFlashlightController,
                    mUserSwitcherController, mKeyguardMonitor,
                    mSecurityController,
                    mAudioProfileController
                    );
            mQSPanel.setHost(qsh);
            mQSPanel.setTiles(qsh.getTiles());   
            mHeader.setQSPanel(mQSPanel);//MHeader - > StatusBarHeaderView QSPanel was added to StatusBarHeaderView.
            qsh.setCallback(new QSTileHost.Callback() {
                @Override
                public void onTilesChanged() {
                    mQSPanel.setTiles(qsh.getTiles());
                }
            });
        }

Just now we started with the constructor of QSTileHost and analyzed the instantiation of each QSTitle. Now what happened after we created QSTileHost?

mQSPanel.setHost(qsh);//Put the QSTileHost object into QSPanel
mQSPanel.setTiles(qsh.getTiles());
/*QSTileHost Get the title used by title to set QSPanel. From the function name,
It's like the answer to our question above.
*/

Look at qsh.getTiles() and mQSPanel.setTiles()

    @Override
    public Collection<QSTile<?>> getTiles() {  
        return mTiles.values();//Get the title Value
    }


    public void setTiles(Collection<QSTile<?>> tiles) {
        for (TileRecord record : mRecords) {
            removeView(record.tileView);
        }
        mRecords.clear();
        for (QSTile<?> tile : tiles) {
            addTile(tile);//Here, each specific QStitle is added Tile into QSPanel here.
        }
        if (isShowingDetail()) {
            mDetail.bringToFront();
        }
    }

Look at addTile():

private void addTile(final QSTile<?> tile) {
        final TileRecord r = new TileRecord();
        r.tile = tile;
        r.tileView = tile.createTileView(mContext);
        r.tileView.setVisibility(View.GONE);
        final QSTile.Callback callback = new QSTile.Callback() {
            @Override
            public void onStateChanged(QSTile.State state) {
                if (!r.openingDetail) {
                    drawTile(r, state);
                }
            }
            @Override
            public void onShowDetail(boolean show) {
                QSPanel.this.showDetail(show, r);
            }
            @Override
            public void onToggleStateChanged(boolean state) {
                if (mDetailRecord == r) {
                    fireToggleStateChanged(state);
                }
            }
            @Override
            public void onScanStateChanged(boolean state) {
                r.scanState = state;
                if (mDetailRecord == r) {
                    fireScanStateChanged(r.scanState);
                }
            }

            @Override
            public void onAnnouncementRequested(CharSequence announcement) {
                announceForAccessibility(announcement);
            }
        };
        r.tile.setCallback(callback);
        final View.OnClickListener click = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                r.tile.click();
            }
        };
        final View.OnClickListener clickSecondary = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                r.tile.secondaryClick();
            }
        };
        final View.OnLongClickListener longClick = new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                r.tile.longClick();
                return true;
            }
        };
        r.tileView.init(click, clickSecondary, longClick);
        r.tile.setListening(mListening);
        callback.onStateChanged(r.tile.getState());
        r.tile.refreshState();
        mRecords.add(r);

        addView(r.tileView);//Load into QSPanel
    }

In addTile(), a callback is created, and many operations, such as Title refresh, click event, etc., will be linked to the callback in actual operation.
Finally, the addView function loads the QSTitle into QSPanel.

Posted by Illusionist on Mon, 01 Apr 2019 09:03:28 -0700