Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6154a878 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "New MessageRouter interface to replace Handler." into sc-v2-dev

parents 248f0f42 4dda2323
Loading
Loading
Loading
Loading
+198 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.util.concurrency;

/**
 * Allows triggering methods based on a passed in id or message, generally on another thread.
 *
 * Messages sent on to this router must be processed in order. That is to say, if three
 * messages are sent with no delay, they must be processed in the order they were sent. Moreover,
 * if messages are sent with various delays, they must be processed in order of their delay.
 *
 *  Messages can be passed by either a simple integer or an instance of a class. Unique integers are
 *  considered unique messages. Unique message classes (not instances) are considered unique
 *  messages. You can use message classes to pass extra data for processing to subscribers.
 *
 *  <pre>
 *      // Three messages with three unique integer messages.
 *      // They can be subscribed to independently.
 *      router.sendMessage(0);
 *      router.sendMessage(1);
 *      router.sendMessage(2);
 *
 *      // Three messages with two unique message classes.
 *      // The first and third messages will be delivered to the same subscribers.
 *      router.sendMessage(new Foo(0));
 *      router.sendMessage(new Bar(1));
 *      router.sendMessage(new Foo(2));
 *  </pre>
 *
 * The number of unique ids and message types used should be relatively constrained. Construct
 * a custom message-class and put unique, per-message data inside of it.
 */
public interface MessageRouter {
    /**
     * Alerts any listeners subscribed to the passed in id.
     *
     * The number of unique ids used should be relatively constrained - used to identify the type
     * of message being sent. If unique information needs to be passed with each call, use
     * {@link #sendMessage(Object)}.
     *
     * @param id An identifier for the message
     */
    default void sendMessage(int id) {
        sendMessageDelayed(id, 0);
    }

    /**
     * Alerts any listeners subscribed to the passed in message.
     *
     * The number of message types used should be relatively constrained. If no unique information
     * needs to be passed in, you can simply use {@link #sendMessage(int)}} which takes an integer
     * instead of a unique class type.
     *
     * The class of the passed in object will be used to router the message.
     *
     * @param data A message containing extra data for processing.
     */
    default void sendMessage(Object data) {
        sendMessageDelayed(data, 0);
    }

    /**
     * Alerts any listeners subscribed to the passed in id in the future.
     *
     * The number of unique ids used should be relatively constrained - used to identify the type
     * of message being sent. If unique information needs to be passed with each call, use
     * {@link #sendMessageDelayed(Object, long)}.
     *
     * @param id An identifier for the message
     * @param delayMs Number of milliseconds to wait before alerting.
     */
    void sendMessageDelayed(int id, long delayMs);


    /**
     * Alerts any listeners subscribed to the passed in message in the future.
     *
     * The number of message types used should be relatively constrained. If no unique information
     * needs to be passed in, you can simply use {@link #sendMessageDelayed(int, long)} which takes
     * an integer instead of a unique class type.
     *
     * @param data A message containing extra data for processing.
     * @param delayMs Number of milliseconds to wait before alerting.
     */
    void sendMessageDelayed(Object data, long delayMs);

    /**
     * Cancel all unprocessed messages for a given id.
     *
     * If a message has multiple listeners and one of those listeners has been alerted, the other
     * listeners that follow it may also be alerted. This is only guaranteed to cancel messages
     * that are still queued.
     *
     * @param id The message id to cancel.
     */
    void cancelMessages(int id);

    /**
     * Cancel all unprocessed messages for a given message type.
     *
     * If a message has multiple listeners and one of those listeners has been alerted, the other
     * listeners that follow it may also be alerted. This is only guaranteed to cancel messages
     * that are still queued.
     *
     * @param messageType The class of the message to cancel
     */
    <T> void cancelMessages(Class<T> messageType);

    /**
     * Add a listener for a message that does not handle any extra data.
     *
     * See also {@link #subscribeTo(Class, DataMessageListener)}.
     *
     * @param id The message id to listener for.
     * @param listener
     */
    void subscribeTo(int id, SimpleMessageListener listener);

    /**
     * Add a listener for a message of a specific type.
     *
     * See also {@link #subscribeTo(Class, DataMessageListener)}.
     *
     * @param messageType The class of message to listen for.
     * @param listener
     */
    <T> void subscribeTo(Class<T> messageType, DataMessageListener<T> listener);

    /**
     * Remove a listener for a specific message.
     *
     * See also {@link #unsubscribeFrom(Class, DataMessageListener)}
     *
     * @param id The message id to stop listening for.
     * @param listener The listener to remove.
     */
    void unsubscribeFrom(int id, SimpleMessageListener listener);

    /**
     * Remove a listener for a specific message.
     *
     * See also {@link #unsubscribeFrom(int, SimpleMessageListener)}.
     *
     * @param messageType The class of message to stop listening for.
     * @param listener The listener to remove.
     */
    <T> void unsubscribeFrom(Class<T> messageType, DataMessageListener<T> listener);

    /**
     * Remove a listener for all messages that it is subscribed to.
     *
     * See also {@link #unsubscribeFrom(DataMessageListener)}.
     *
     * @param listener The listener to remove.
     */
    void unsubscribeFrom(SimpleMessageListener listener);

    /**
     * Remove a listener for all messages that it is subscribed to.
     *
     * See also {@link #unsubscribeFrom(SimpleMessageListener)}.
     *
     * @param listener The listener to remove.
     */
    <T> void unsubscribeFrom(DataMessageListener<T> listener);

    /**
     * A Listener interface for when no extra data is expected or desired.
     */
    interface SimpleMessageListener {
        /** */
        void onMessage(int id);
    }

    /**
     * A Listener interface for when extra data is expected or desired.
     *
     * @param <T>
     */
    interface DataMessageListener<T> {
        /** */
        void onMessage(T data);
    }
}
+186 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.util.concurrency;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Implementation of {@link MessageRouter}.
 */
public class MessageRouterImpl implements MessageRouter {

    private final DelayableExecutor mDelayableExecutor;

    private final Map<Integer, List<Runnable>> mIdMessageCancelers = new HashMap<>();
    private final Map<Class<Object>, List<Runnable>> mDataMessageCancelers = new HashMap<>();
    private final Map<Integer, List<SimpleMessageListener>> mSimpleMessageListenerMap =
            new HashMap<>();
    private final Map<Class<?>, List<DataMessageListener<Object>>> mDataMessageListenerMap =
            new HashMap<>();

    public MessageRouterImpl(DelayableExecutor delayableExecutor) {
        mDelayableExecutor = delayableExecutor;
    }

    @Override
    public void sendMessageDelayed(int id, long delayMs) {
        addCanceler(id, mDelayableExecutor.executeDelayed(() -> onMessage(id), delayMs));
    }

    @Override
    public void sendMessageDelayed(Object data, long delayMs) {
        addCanceler((Class<Object>) data.getClass(), mDelayableExecutor.executeDelayed(
                () -> onMessage(data), delayMs));
    }

    @Override
    public void cancelMessages(int id) {
        synchronized (mIdMessageCancelers) {
            if (mIdMessageCancelers.containsKey(id)) {
                for (Runnable canceler : mIdMessageCancelers.get(id)) {
                    canceler.run();
                }
                // Remove, don't clear, otherwise this could look like a memory leak as
                // more and more unique message ids are passed in.
                mIdMessageCancelers.remove(id);
            }
        }
    }

    @Override
    public <T> void cancelMessages(Class<T> messageType) {
        synchronized (mDataMessageCancelers) {
            if (mDataMessageCancelers.containsKey(messageType)) {
                for (Runnable canceler : mDataMessageCancelers.get(messageType)) {
                    canceler.run();
                }
                // Remove, don't clear, otherwise this could look like a memory leak as
                // more and more unique message types are passed in.
                mDataMessageCancelers.remove(messageType);
            }
        }
    }

    @Override
    public void subscribeTo(int id, SimpleMessageListener listener) {
        synchronized (mSimpleMessageListenerMap) {
            mSimpleMessageListenerMap.putIfAbsent(id, new ArrayList<>());
            mSimpleMessageListenerMap.get(id).add(listener);
        }
    }

    @Override
    public <T> void subscribeTo(Class<T> messageType, DataMessageListener<T> listener) {
        synchronized (mDataMessageListenerMap) {
            mDataMessageListenerMap.putIfAbsent(messageType, new ArrayList<>());
            mDataMessageListenerMap.get(messageType).add((DataMessageListener<Object>) listener);
        }
    }

    @Override
    public void unsubscribeFrom(int id, SimpleMessageListener listener) {
        synchronized (mSimpleMessageListenerMap) {
            if (mSimpleMessageListenerMap.containsKey(id)) {
                mSimpleMessageListenerMap.get(id).remove(listener);
            }
        }
    }

    @Override
    public <T> void unsubscribeFrom(Class<T> messageType, DataMessageListener<T> listener) {
        synchronized (mDataMessageListenerMap) {
            if (mDataMessageListenerMap.containsKey(messageType)) {
                mDataMessageListenerMap.get(messageType).remove(listener);
            }
        }
    }

    @Override
    public void unsubscribeFrom(SimpleMessageListener listener) {
        synchronized (mSimpleMessageListenerMap) {
            for (Integer id : mSimpleMessageListenerMap.keySet()) {
                mSimpleMessageListenerMap.get(id).remove(listener);
            }
        }
    }

    @Override
    public <T> void unsubscribeFrom(DataMessageListener<T> listener) {
        synchronized (mDataMessageListenerMap) {
            for (Class<?> messageType : mDataMessageListenerMap.keySet()) {
                mDataMessageListenerMap.get(messageType).remove(listener);
            }
        }
    }

    private void addCanceler(int id, Runnable canceler) {
        synchronized (mIdMessageCancelers) {
            mIdMessageCancelers.putIfAbsent(id, new ArrayList<>());
            mIdMessageCancelers.get(id).add(canceler);
        }
    }

    private void addCanceler(Class<Object> data, Runnable canceler) {
        synchronized (mDataMessageCancelers) {
            mDataMessageCancelers.putIfAbsent(data, new ArrayList<>());
            mDataMessageCancelers.get(data).add(canceler);
        }
    }

    private void onMessage(int id) {
        synchronized (mSimpleMessageListenerMap) {
            if (mSimpleMessageListenerMap.containsKey(id)) {
                for (SimpleMessageListener listener : mSimpleMessageListenerMap.get(id)) {
                    listener.onMessage(id);
                }
            }
        }

        synchronized (mIdMessageCancelers) {
            if (mIdMessageCancelers.containsKey(id) && !mIdMessageCancelers.get(id).isEmpty()) {
                mIdMessageCancelers.get(id).remove(0);
                if (mIdMessageCancelers.get(id).isEmpty()) {
                    mIdMessageCancelers.remove(id);
                }
            }
        }
    }

    private void onMessage(Object data) {
        synchronized (mDataMessageListenerMap) {
            if (mDataMessageListenerMap.containsKey(data.getClass())) {
                for (DataMessageListener<Object> listener : mDataMessageListenerMap.get(
                        data.getClass())) {
                    listener.onMessage(data);
                }
            }
        }

        synchronized (mDataMessageCancelers) {
            if (mDataMessageCancelers.containsKey(data.getClass())
                    && !mDataMessageCancelers.get(data.getClass()).isEmpty()) {
                mDataMessageCancelers.get(data.getClass()).remove(0);
                if (mDataMessageCancelers.get(data.getClass()).isEmpty()) {
                    mDataMessageCancelers.remove(data.getClass());
                }
            }
        }
    }
}
+16 −0
Original line number Diff line number Diff line
@@ -170,4 +170,20 @@ public abstract class SysUIConcurrencyModule {
    public static Executor provideUiBackgroundExecutor() {
        return Executors.newSingleThreadExecutor();
    }

    /** */
    @Provides
    @Main
    public static MessageRouter providesMainMessageRouter(
            @Main DelayableExecutor executor) {
        return new MessageRouterImpl(executor);
    }

    /** */
    @Provides
    @Background
    public static MessageRouter providesBackgroundMessageRouter(
            @Background DelayableExecutor executor) {
        return new MessageRouterImpl(executor);
    }
}
+24 −35
Original line number Diff line number Diff line
@@ -36,7 +36,6 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.SystemProperties;
import android.provider.Settings;
@@ -60,6 +59,8 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSIconViewImpl;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.MessageRouter;

import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -110,18 +111,18 @@ public class GarbageMonitor implements Dumpable {
    private static final int DO_GARBAGE_INSPECTION = 1000;
    private static final int DO_HEAP_TRACK = 3000;

    private static final int GARBAGE_ALLOWANCE = 5;
    static final int GARBAGE_ALLOWANCE = 5;

    private static final String TAG = "GarbageMonitor";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private final Handler mHandler;
    private final MessageRouter mMessageRouter;
    private final TrackedGarbage mTrackedGarbage;
    private final LeakReporter mLeakReporter;
    private final Context mContext;
    private final ActivityManager mAm;
    private final DelayableExecutor mDelayableExecutor;
    private MemoryTile mQSTile;
    private DumpTruck mDumpTruck;
    private final DumpTruck mDumpTruck;

    private final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<>();
    private final ArrayList<Long> mPids = new ArrayList<>();
@@ -133,13 +134,16 @@ public class GarbageMonitor implements Dumpable {
    @Inject
    public GarbageMonitor(
            Context context,
            @Background Looper bgLooper,
            @Background DelayableExecutor delayableExecutor,
            @Background MessageRouter messageRouter,
            LeakDetector leakDetector,
            LeakReporter leakReporter) {
        mContext = context.getApplicationContext();
        mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

        mHandler = new BackgroundHeapCheckHandler(bgLooper);
        mDelayableExecutor = delayableExecutor;
        mMessageRouter = messageRouter;
        mMessageRouter.subscribeTo(DO_GARBAGE_INSPECTION, this::doGarbageInspection);
        mMessageRouter.subscribeTo(DO_HEAP_TRACK, this::doHeapTrack);

        mTrackedGarbage = leakDetector.getTrackedGarbage();
        mLeakReporter = leakReporter;
@@ -158,13 +162,13 @@ public class GarbageMonitor implements Dumpable {
            return;
        }

        mHandler.sendEmptyMessage(DO_GARBAGE_INSPECTION);
        mMessageRouter.sendMessage(DO_GARBAGE_INSPECTION);
    }

    public void startHeapTracking() {
        startTrackingProcess(
                android.os.Process.myPid(), mContext.getPackageName(), System.currentTimeMillis());
        mHandler.sendEmptyMessage(DO_HEAP_TRACK);
        mMessageRouter.sendMessage(DO_HEAP_TRACK);
    }

    private boolean gcAndCheckGarbage() {
@@ -586,33 +590,18 @@ public class GarbageMonitor implements Dumpable {
        }
    }

    private class BackgroundHeapCheckHandler extends Handler {
        BackgroundHeapCheckHandler(Looper onLooper) {
            super(onLooper);
            if (Looper.getMainLooper().equals(onLooper)) {
                throw new RuntimeException(
                        "BackgroundHeapCheckHandler may not run on the ui thread");
            }
        }

        @Override
        public void handleMessage(Message m) {
            switch (m.what) {
                case DO_GARBAGE_INSPECTION:
    private void doGarbageInspection(int id) {
        if (gcAndCheckGarbage()) {
                        postDelayed(GarbageMonitor.this::reinspectGarbageAfterGc, 100);
            mDelayableExecutor.executeDelayed(this::reinspectGarbageAfterGc, 100);
        }

                    removeMessages(DO_GARBAGE_INSPECTION);
                    sendEmptyMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
                    break;
        mMessageRouter.cancelMessages(DO_GARBAGE_INSPECTION);
        mMessageRouter.sendMessageDelayed(DO_GARBAGE_INSPECTION, GARBAGE_INSPECTION_INTERVAL);
    }

                case DO_HEAP_TRACK:
    private void doHeapTrack(int id) {
        update();
                    removeMessages(DO_HEAP_TRACK);
                    sendEmptyMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
                    break;
            }
        }
        mMessageRouter.cancelMessages(DO_HEAP_TRACK);
        mMessageRouter.sendMessageDelayed(DO_HEAP_TRACK, HEAP_TRACK_INTERVAL);
    }
}
+371 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading