Android foreground service explains three key classes

1. How to create a notification?

/**
     * 创建服务通知
     */
    private fun createForegroundNotification(): Notification {
        val notificationBuidler = NotificationCompat.Builder(applicationContext, notificationChannelId)
        //通知小图标
        notificationBuidler.setSmallIcon(R.mipmap.ic_launcher_round)
        //通知标题
        notificationBuidler.setContentTitle("苏宁窖藏")
        //通知内容
        notificationBuidler.setContentText("苏宁是国内优秀的跨国企业?$count")
        //设置通知显示的时间
        notificationBuidler.setWhen(System.currentTimeMillis())
        //设定启动的内容
        val  activityIntent: Intent = Intent(this, MainActivity::class.java)
        activityIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        val pendingIntent: PendingIntent = PendingIntent.getActivity(this,
                1,activityIntent, PendingIntent.FLAG_UPDATE_CURRENT)
        notificationBuidler.setContentIntent(pendingIntent)
        //普通视图
        notificationBuidler.setCustomContentView(getContentView())
        //扩展视图
        notificationBuidler.setCustomBigContentView(getBigContentView())
        notificationBuidler.priority = NotificationCompat.PRIORITY_DEFAULT
        //设置为进行中的通知
        notificationBuidler.setOngoing(true)

        //创建通知并返回
        return notificationBuidler.build()
    }
//发送通知给状态栏显示
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.notify(NOTIFICATION_ID, notification)

1) Create Notification with the help of Notification's helper class NotificationCompat.Builder;

2) Set the values ​​of some fields of Notification, such as: smallIcon, contentTitle, contentText, when, etc.;

3) Set the action intent after the Notification click, which is wrapped by PendingIntent. PendingIntent mainly contains Intent and a target action to be executed. Set the purpose Intent of the notification click through notificationBuidler.setContentIntent(pendingIntent);

The PendingInent object is obtained in different ways, and the intent to trigger execution is also different. Generally speaking, there are three types of intent after triggering: entering an Activity, sending a broadcast, starting the service, calling PendingIntent.getActivity(...), PendingIntent.getBroadcast(. ..), PendingIntent.getService(...), PendingIntent.getActivities(); After clicking the notification in the notification bar, the corresponding intent will be touched, and you can enter an Activity, send a broadcast, or start a service. If you want to implement a custom style notification, how do you do it?

4) Create RemoteViews, set the custom view to be displayed through notificationBuidler.setCustomContentView(getContentView()) and set the custom view click intent;

Two parameters are passed in the RemoteViews constructor: package name and xml layout file. This xml layout file corresponds to the interface in the above notification, and the above layout file is relatively simple;

5) Control Notification to display notify() and remove canel() through NotificationManager;

 private fun getContentView(): RemoteViews{
        normalView = RemoteViews(this.packageName, R.layout.notify_content_view)
        //上一首图标添加点击监听
        val intent: Intent = Intent(PLAY_MUSIC)
        intent.component = ComponentName("com.yifan.service", "com.yifan.service.PlayBroadcastReceiver")
        val pendPreviousButtonIntent = PendingIntent.getBroadcast(this, count++, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        normalView.setOnClickPendingIntent(R.id.play_last, pendPreviousButtonIntent);
        return normalView
    }

2. What are the key categories of front desk services?

2.1PendingIntent

PendingIntent is the intention to store the Intent and execute the action. The difference between PendingIntent and Intent is that one is executed immediately and the other is executed at some point in the future;

In Android development, PendingIntent is mainly used in Notification, AlarmManager and Widget. There are three main ways to get PendingIntent: getActivity(), getService() and getBroadcast(). The parameters of these three methods are the same, but the second one The parameter requestCode and the fourth parameter flag are not easy to understand. The second requestCode represents the request code of the sender of the PendingIntent. In most cases, it can be set to 0. In addition, the requestCode will affect the effect of the fourth parameter flags , which is combined here. PendingIntent in Notification to explain;

First of all, it is necessary to clarify what kind of PendingIntent can be regarded as the same PendingIntent. According to the description of PendingIntent in Google documentation, when two PendingIntents are of the same type (that is, two are obtained by getActivity() or getService() or getBroadcast()), and the data, action, component, category, When the flags are the same (specially, the extra of the Intent is not counted), the two PendingIntents are considered the same. In addition, the second parameter requestCode can also be used to distinguish PendingIntent , so even if two PendingIntents are of the same type and the same Intent, if the requestCode is different, they are two different PendingIntents. After clarifying the above, the flag needs to be explained. Here we mainly explain FLAG_CANCEL_CURRENT and FLAG_UPDATE_CURRENT .

FLAG_CANCEL_CURRENT: If the PendingIntent already exists, cancel the previous PendingIntent and generate a new PendingIntent.

FLAG_UPDATE_CURRENT: If the PendingIntent already exists, update the extra data of the Intent of the PendingIntent to the latest.

Therefore, if multiple PendingIntents (deleteIntent, contentIntent, setOnClickPendingIntent) are specified in a Notification, or PendingIntents are specified in multiple Notifications, then if there is the same PendingIntent, when the flag is FLAG_CANCEL_CURRENT, the previous PendingIntent will be cancelled. The content of the Intent cannot be delivered, and the current PendingIntent is not affected; when the flag is FLAG_UPDATE_CURRENT, the extra data of the Intent of the PendingIntent will be updated to the latest one, and the extra data of the previous PendingIntent will be modified to the latest one;

2.2RemoteViews

RemoteViews, as the name suggests, is a remote View, which represents a View structure that can be displayed in other processes . In order to update its interface across processes, RemoteViews provides a set of basic operations to achieve this effect; the usage scenario of RemoteViews in Android There are two kinds: notification bar and desktop widget;

To create a RemoteViews object, we only need to know the current application package name and the resource id of the layout file, which is relatively simple, but it is not so easy to update RemoteViews , because we cannot directly access the View in the layout file, but must pass the specific method provided by RemoteViews to update the View . For example , to set the text content of TextView, you need to use the setTextViewText method, and to set the ImageView image, you need to use the setImageViewResource method . You can also set a click event for the View inside, you need to use PendingIntent and implement it through the setOnClickPendingIntent method . The reason why updating RemoteViews is so complicated is directly because RemoteViews does not provide a findViewById method similar to View, and we cannot get the child Views in RemoteViews;

2.3 Internal mechanism of RemoteViews

There are many construction methods for RemoteViews, one of our most common is

 public RemoteViews(String packageName, int layoutId) {
        this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId);
    }

It only needs the package name and the id of the resource file to be loaded. It does not support all types of Views, nor does it support custom Views. The types it can support are as follows:

Layout:FrameLyout、LinearLayout、RelativeLayout、GridLayout

View:Button、ImageView、ImageButton、ProgressBar、TextView、ListView、GridView、StackView、ViewStub、AdapterViewFlipper、ViewFlipper、AnalogClock、Chronometer。

If we use RemoteViews which does not support View as EditText, then exception will occur.

Let's look at the set method of RemoteViews

It can be seen from these methods that the methods of View that can be called directly are now completed through a series of set methods of RemoteViews.

We know that the notification bar and desktop widgets are managed by NotificationManager and AppWidgetManager, respectively, and NotificationManager and AppWidgetManager communicate with NotificationManagerService and AppWidgetService in the SystemServer process through Binder, respectively . Therefore, the layout files in the notification bar and desktop widgets are actually The above is loaded in NotificationManagerService and AppWidgetService, and they run in SystemServer , which actually constitutes cross-process communication with our own app process .

 theoretical analysis

First, RemoteViews will be passed to the SystemServer process through Binder, because RemoteViews implements the Parcelable interface and can be transmitted across processes. The system will obtain the resources of the app according to the package name and other information in RemoteViews, and then load the layout files in RemoteViews through LayoutInflater. . The layout file loaded in the SystemServer process is an ordinary View, but for our app process, it is a remote View or RemoteViews. Then the system will perform a series of interface update tasks on the View. These tasks are submitted by the set method before. The update operation of the View by the set method is not executed immediately. All the update operations will be recorded in the RemoteViews. The specific execution requires Wait until the RemoteViews are fully loaded, so that the RemoteViews can be displayed in-process in the SystemServer, and this is what we see as notification bar messages and desktop widgets. When we need to update RemoteViews, we need to call a series of set methods to submit update tasks through NotificationManager and AppWidgetManager. The specific update operations are also done in the SystemServer process.

In theory, the system can fully support all View and View operations through Binder, but this is too expensive, there are too many View methods, and a large number of IPC operations will affect efficiency. In order to solve this problem, the system does not directly support the cross-process access of View through Binder, but provides a concept of Action, Action represents a View operation, and Action also implements the Parcelable interface. The system first encapsulates View operations into Action objects and transmits these objects to remote processes across processes, and then executes specific operations in the Action objects in the remote process. Each time the set method is called in our app, a corresponding Action object will be added to RemoteViews. When we submit our updates through NotificationManager and AppWidgetManager, these Action objects will be transferred to the remote process and sequentially in the remote process. implement. The remote process uses the apply method of RemoteViews to update the View. The inside of the apply method is to traverse all the Action objects and call their apply methods. The specific View update operation is done by the apply method of the Action objects.

The advantage of the above approach is that, firstly, there is no need to define a large number of Binder interfaces, and secondly, a large number of IPC operations are avoided by performing the update operation of RemoteViews in batches in the remote process, which improves the performance of the program.

Source code analysis

First of all, we start with the set method of RemoteViews, such as the method setImageViewResource to set the picture. Its internal implementation is like this:

    /**
     * Equivalent to calling {@link ImageView#setImageResource(int)}
     *
     * @param viewId The id of the view whose drawable should change
     * @param srcId The new resource id for the drawable
     */
    public void setImageViewResource(int viewId, int srcId) {
        setInt(viewId, "setImageResource", srcId);
    }

In the above code, viewId is the id of the operated View, setImageResource is the method name, and srcId is the image resource id to be set for this ImageView. The method name here is the same as the setImageResource of ImageView. Let's look at the specific implementation of the setInt method:

    /**
     * Call a method taking one int on a view in the layout for this RemoteViews.
     *
     * @param viewId The id of the view on which to call the method.
     * @param methodName The name of the method to call.
     * @param value The value to pass to the method.
     */
    public void setInt(int viewId, String methodName, int value) {
        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
    }

It can be seen that it does not directly operate the View, but adds a ReflectionAction object, which literally should be a reflection-type action, and then look at the implementation of addAction:

/**
     * Add an action to be executed on the remote side when apply is called.
     *
     * @param a The action to add
     */
    private void addAction(Action a) {
        if (hasLandscapeAndPortraitLayouts()) {
            throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
                    " layouts cannot be modified. Instead, fully configure the landscape and" +
                    " portrait layouts individually before constructing the combined layout.");
        }
        if (mActions == null) {
            mActions = new ArrayList<>();
        }
        mActions.add(a);
    }

As you can see from the above code, an ArrrayList named mActions is maintained inside RemoteViews, and all operations for View update are added to this collection. Note that these actions are only added and saved, not executed. At this point, the source code of the setImageViewResource method is over. Now we need to figure out the execution of these Actions. Let's look at the apply method of RemoteViews again:

    /** @hide */
    public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
        RemoteViews rvToApply = getRemoteViewsToApply(context);

        View result = inflateView(context, rvToApply, parent);
        rvToApply.performApply(result, parent, handler);
        return result;
    }

First, RemoteViews will load its layout files through LayoutInflater, and after loading, perform some update operations through the performApply method.

   private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
        if (mActions != null) {
            handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
            final int count = mActions.size();
            for (int i = 0; i < count; i++) {
                Action a = mActions.get(i);
                a.apply(v, parent, handler);
            }
        }
    }

Here, the mActions collection is traversed and the apply method of each Action is executed. It should be seen that the apply method of the Action is the place to actually operate the View update.

When we call the set method of RemoteViews, their interface will not be updated immediately, and their interface must be updated through the notify method of NotificationManager and the updateAppWidget method of AppWidgetManager. In fact, in the internal implementation of updateAppWidget of AppWidgetManager, the interface is loaded or updated through the apply method and reapply method of RemoteViews. The difference between apply and reapply is: apply will load the layout and update the interface, while reapply will only update the interface. The apply method is called during initialization, and the reapply method is called for subsequent updates.

ReflectionAction is a subclass of Action, let's take a look at its source code:

/**
 * Base class for the reflection actions.
 */
private final class ReflectionAction extends Action {

    String methodName;
    int type;
    Object value;

    ReflectionAction(int viewId, String methodName, int type, Object value) {
        this.viewId = viewId;
        this.methodName = methodName;
        this.type = type;
        this.value = value;
    }

    @Override
    public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
        final View view = root.findViewById(viewId);
        if (view == null) return;

        Class<?> param = getParameterType();
        if (param == null) {
            throw new ActionException("bad type: " + this.type);
        }

        try {
            getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
        } catch (ActionException e) {
            throw e;
        } catch (Exception ex) {
            throw new ActionException(ex);
        }
    }
}

Its internal implementation is a bit long, we mainly look at its apply method.

getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));

This code is that it operates on the View by reflection, getMethod obtains the Method object required for reflection according to the method name, and then executes the method.

The click event in RemoteViews only supports the initiation of PendingIntent, and does not support the method of onClickListener. We need to pay attention to the difference and connection between the methods setOnClickPendingIntent, setPendingIntentTemplate and setOnClickFillInIntent. setOnClickPendingIntent is used to set click events for ordinary Views, but it cannot set click events for items in ListView or GridView and StackView, because the overhead is relatively large, and the system prohibits this method. The setPendingIntentTemplate method can set a click event for the item. For details, please refer to this article Android Widget Advanced - RemoteViews of App Widget .

2.4 Advantages and disadvantages of RemoteViews

In actual development, we can choose AIDL to implement cross-process communication, but if the interface is updated frequently, there will be efficiency problems, and the AIDL interface may become very complicated, but if RemoteViews is used to implement this problem Now, the disadvantage of RemoteViews is that it only supports some common Views, but not custom Views.

refer to:

RemoteViews in Android - Programmer Sought

Android-based network music player - notification bar control (RemoteViews) (ten) - Programmer Sought

RemoteViews in Android - Programmer Sought

Guess you like

Origin blog.csdn.net/ahou2468/article/details/122527344