欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

荐 【Android】LeakCanary原理解析

程序员文章站 2022-09-01 09:30:26
【Android】LeakCanary原理解析学而不思则罔,思而不学则殆...


学而不思则罔,思而不学则殆

源码

https://github.com/square/leakcanary/tags
下载的是1.6.1的版本,本来想Git clone的。但是国内的网络不行,clone超时,也想通过其他方式,就直接下载了一个ZIP的包,也方便。

概述

LeakCanary是Square公司为Android开发者提供的一个自动检测内存泄漏的工具
LeakCanary本质上是一个基于MAT进行Android应用程序内存泄漏自动化检测的的开源工具,我们可以通过集成LeakCanary提供的jar包到自己的工程中,一旦检测到内存泄漏,LeakCanary就好dump Memory信息,并通过另一个进程分析内存泄漏的信息并展示出来,随时发现和定位内存泄漏问题,而不用每次在开发流程中都抽出专人来进行内存泄漏问题检测,极大地方便了Android应用程序的开发。
Leak使用可以大致分为以下四个方面:注册,观察,分析和展示

荐
                                                        【Android】LeakCanary原理解析

注册

注册调用非常简单,一句话就OK:

public class ExampleApplication extends Application {
  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}

但是中间干的事也不少,来看看注册过程中的时序图:

注册时序图

荐
                                                        【Android】LeakCanary原理解析
注册阶段主要是生成引用观察者{RefWatcher},然后注册到Application的生命周期方法中。当{onActivityCreated(Activity, Bundle)}回调被调用的时候,通过观察{Activity}引用是否全部回收。

注册类图

荐
                                                        【Android】LeakCanary原理解析

1.install

  //LeakCanary.java
  public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }
  
  public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

调用{refWatcher}生成一个{AndroidRefWatcherBuilder},{AndroidRefWatcherBuilder}这是一个采用构建者模式的构建类,主要是用来提供生成{RefWatcher}的参数,直接跳过中间设置参数的地方,直接看{buildAndInstall}方法;

2.buildAndInstall

类图如下:
荐
                                                        【Android】LeakCanary原理解析

  //AndroidRefWatcherBuilder.java
  public RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build(); //1.构建RefWatcher 
    if (refWatcher != DISABLED) {
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);//2.注册Activity监听
      }
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);//3.注册Fragment监听
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

这个方法干了两件事,构建RefWatcher 和注册应用生命周期回调;其中注册监听回调分为ActivityFragment

1.build

通过调用父类{RefWatcherBuilder}的builde方法生成RefWathcer.采用的构建者模式;
荐
                                                        【Android】LeakCanary原理解析

  //RefWatcherBuilder.java
  public final RefWatcher build() {
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }

    if (heapDumpBuilder.excludedRefs == null) {
      heapDumpBuilder.excludedRefs(defaultExcludedRefs());
    }

    HeapDump.Listener heapDumpListener = this.heapDumpListener;
    if (heapDumpListener == null) {
      heapDumpListener = defaultHeapDumpListener();
    }

    DebuggerControl debuggerControl = this.debuggerControl;
    if (debuggerControl == null) {
      debuggerControl = defaultDebuggerControl();
    }

    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
      heapDumper = defaultHeapDumper();
    }

    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      watchExecutor = defaultWatchExecutor();
    }

    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }

    if (heapDumpBuilder.reachabilityInspectorClasses == null) {
      heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
    }

    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        heapDumpBuilder);
  }

2.注册生命周期回调

  //ActivityRefWatcher.java
  public static void install(Context context, RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();//1.采用ApplicationContext
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);//2.创建ActivityRefWatcher
    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);//3.注册回调
  }

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };
  ...
  private final Application application;
  private final RefWatcher refWatcher;

  private ActivityRefWatcher(Application application, RefWatcher refWatcher) {
    this.application = application;
    this.refWatcher = refWatcher;
  }

这里经过注册Activity的生命周期回调,整个注册过程就算结束了。当Activity执行onDestroyed方法时,就是调用onActivityDestroyed方法,接下来就进入到观察阶段了。
其中Fragment注册跟Activity差不多,这里就不在赘述。
接下来就进入观察阶段…

观察

先来一个观察阶段的时序图:

观察时序图

荐
                                                        【Android】LeakCanary原理解析

  //ActivityRefWatcher.java
  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };
      
  private final Application application;
  private final RefWatcher refWatcher;

  private ActivityRefWatcher(Application application, RefWatcher refWatcher) {
    this.application = application;
    this.refWatcher = refWatcher;
  }

接收回调{onActivityDestroyed}的时候,调用RefWatcherwatch方法。把Activity加入到观察队列中。
那就来看看RefWatcher类是什么?图先整上。

RefWatcher类图

荐
                                                        【Android】LeakCanary原理解析
根据前面我们可以知道该:

  //RefWatcherBuilder.java
  public final RefWatcher build() {
    ...
    //创建RefWatcher
    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        heapDumpBuilder);
  }

1.watch

  /**
   * Identical to {@link #watch(Object, String)} with an empty string reference name.
   *
   * @see #watch(Object, String)
   */
  public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }

  /**
   * Watches the provided references and checks if it can be GCed. This method is non blocking,
   * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
   * with.
   *
   * @param referenceName An logical identifier for the watched object.
   */
  public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) { //release版本中会生成一个DISABLED对象,直接返回
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString(); //1.随机生成key
    retainedKeys.add(key); //2.加入到Set中,用来判断该对象是否被回收
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);//创建KeyedWeakReference 

    ensureGoneAsync(watchStartNanoTime, reference);
  }

2.ensureGoneAsync

进入异步队列

  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

WatchExecutor是一个接口,其默认实现是空实现。WatchExecutorRetryable 的关系可以类比为线程池Runnable的关系

public interface WatchExecutor {
  WatchExecutor NONE = new WatchExecutor() {
    @Override public void execute(Retryable retryable) {
    }
  };

  void execute(Retryable retryable);
}

当然在Android的实现是{AndroidWatchExecutor},类图如下。
荐
                                                        【Android】LeakCanary原理解析
{AndroidWatchExecutor}中有两个Handler,一个是主线程MainHandler,一个是子线程ThreadHandler。

  //AndroidWatchExecutor.java
  public AndroidWatchExecutor(long initialDelayMillis) {
    mainHandler = new Handler(Looper.getMainLooper());
    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
    handlerThread.start();
    backgroundHandler = new Handler(handlerThread.getLooper());
    this.initialDelayMillis = initialDelayMillis;
    maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
  }
  
  @Override 
  public void execute(Retryable retryable) {
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) { //主线程调用
      waitForIdle(retryable, 0);
    } else { //子线程调用
      postWaitForIdle(retryable, 0);
    }
  }

子类实现分为主线程调用子线程调用

子线程调用

  private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
    mainHandler.post(new Runnable() {
      @Override public void run() {
        waitForIdle(retryable, failedAttempts);
      }
    });
  }

可以看到这里子线程调用还是扔到了主线程的{waitForIdle}方法。

主线程调用

  private void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }

很奇怪,怎么是{addIdleHandler}而不是直接处理呢?因为addIdleHandler调用的时机是消息空闲的时候被调用,不能GC操作不能阻塞主线程UI的信息。先了解Handler 空闲消息的小伙伴可以看Andriod-消息机制Handler
由于这个方法{waitForIdle}是在主线程被调用,所以这里的MQ也是主线的MQ,queueIdle返回的false,该消息会在结束后移除空闲消息队列。

    //MessageQueue.java
    Message next() {
        ...
        for (;;) {
            ...
            //
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle(); //空闲消息执行
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) { //移除空闲消息
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            ...
        }
    }

刚刚分析我们发现,主线程和子线程都会调用{postToBackgroundWithDelay}方法

postToBackgroundWithDelay

在主线程空闲的时候,执行空闲消息,将消息放到子线程{new HandlerThread(LEAK_CANARY_THREAD_NAME)}

  private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        Retryable.Result result = retryable.run();
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }

这里才是真正调用的地方,调用{retryable.run()}。delayMillis的计算有一些控制重试次数和初始化延时的逻辑。
这是时候回到{2.ensureGoneAsync},会调用{ensureGone}

3.ensureGone

  //RefWatcher.java
  @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();  //1.GC开始时间
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); //2.GC开始时间-观察开始时间

    removeWeaklyReachableReferences(); //3.移除被回收对象的引用key

    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    if (gone(reference)) { //4.判断当前reference引用的对象是否已经被回收掉
      return DONE;
    }
    gcTrigger.runGc(); //5.触发GC
    removeWeaklyReachableReferences();//6.再一次移除被回收对象的引用key
    if (!gone(reference)) { //7.再一次判断当前reference引用的对象是否已经被回收掉
      long startDumpHeap = System.nanoTime();//8.开始dump时间
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);//9.计算GC花费的时间

      File heapDumpFile = heapDumper.dumpHeap(); //10.dump文件
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY; //重试
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); //11.统计dump花费的时间
      //12.构建HeapDump,很重要,后续分析全靠它
      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();
     
      heapdumpListener.analyze(heapDump);//13.回调,主要是回调给服务,去分析泄漏原因
    }
    return DONE;
  }

这里算是观察过程中的最重要的逻辑,可以仔细阅读一下。中间有用到弱引用回收队列,想了解的可以查看【Java】Java的四种引用

1.GC开始时间
2.GC开始时间-观察开始时间
3.移除被回收对象的引用key
4.判断当前reference引用的对象是否已经被回收掉
5.触发GC
6.再一次移除被回收对象的引用key
7.再一次判断当前reference引用的对象是否已经被回收掉
8.开始dump时间
9.计算GC花费的时间
10.dump文件
11.统计dump花费的时间
12.构建HeapDump,很重要,后续分析全靠它
13.回调,主要是回调给服务,去分析泄漏原因

这中间的相关类几乎都采用的接口的模式来实现,便于复用和理解。

removeWeaklyReachableReferences

  private void removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }

如果KeyedWeakReference 引用的对象被回收了,KeyedWeakReference 对象就会被添加到ReferenceQueue队列中,然后在移除retainedKeys中的key.

gone

  private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }

如果retainedKeys中不包含{reference.key},则说明这个引用对象已经被回收了。否则没有被回收。

runGc

荐
                                                        【Android】LeakCanary原理解析
默认实现:

public interface GcTrigger {
  GcTrigger DEFAULT = new GcTrigger() {
    @Override public void runGc() {
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
      // java/lang/ref/FinalizationTester.java
      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perfom a gc.
      Runtime.getRuntime().gc(); //触发GC
      enqueueReferences(); //等待100ms
      System.runFinalization();
    }

    private void enqueueReferences() {
      // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
      // references to the appropriate queues.
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        throw new AssertionError();
      }
    }
  };

  void runGc();
}

dumpHeap

如果GC过后,retainedKeys中任然持有该引用的KEY,则说明这个对象没有被回收,发生了泄漏,dumo泄漏的情况。
荐
                                                        【Android】LeakCanary原理解析
实现在{AndroidHeapDumper}.

创建HeapDump

荐
                                                        【Android】LeakCanary原理解析
dump文件最终的文件是什么样子?

2020-08-23 15:32:37.666 28620-28644/com.example.leakcanary I/System.out: ensureGone heapDumpFile:/storage/emulated/0/Download/leakcanary-com.example.leakcanary/d6fa75e0-a70b-4c9d-9075-f901876318ae_pending.hprof
            HeapDump heapDump = heapDumpBuilder
                    .heapDumpFile(heapDumpFile)
                    .referenceKey(reference.key)
                    .referenceName(reference.name)
                    .watchDurationMs(watchDurationMs)
                    .gcDurationMs(gcDurationMs)
                    .heapDumpDurationMs(heapDumpDurationMs)
                    .build();

这里有三个时间:

时间 说明
watchDurationMs 观察到GC的时间间隔
gcDurationMs GC花费的时间
heapDumpDurationMs dump花费的时间

回调analyze

启动服务,分析和后面展示。
荐
                                                        【Android】LeakCanary原理解析

public final class ServiceHeapDumpListener implements HeapDump.Listener {

  private final Context context;
  private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;

  public ServiceHeapDumpListener(final Context context,
      final Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
    this.context = checkNotNull(context, "context").getApplicationContext();
  }

  @Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass); //启动服务
  }
}

我们来看看HeapDump 对象是什么样子?

HeapDump{heapDumpFile=/storage/emulated/0/Download/leakcanary-com.example.leakcanary/0d1469fe-88b0-4722-bc73-0c346057e78f_pending.hprof
    , referenceKey='b05c7f1d-c1b3-4d16-919a-d5c8e673c0a8'
    , referenceName=''
    , excludedRefs=| Field: android.os.Message.obj
    | Field: android.os.Message.next
    | Field: android.os.Message.target
    | Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
    | Thread:FinalizerWatchdogDaemon (always)
    | Thread:main (always)
    | Thread:LeakCanary-Heap-Dump (always)
    | Class:java.lang.ref.WeakReference (always)
    | Class:java.lang.ref.SoftReference (always)
    | Class:java.lang.ref.PhantomReference (always)
    | Class:java.lang.ref.Finalizer (always)
    | Class:java.lang.ref.FinalizerReference (always)
    
    , watchDurationMs=5021
    , gcDurationMs=143
    , heapDumpDurationMs=2606
    , computeRetainedHeapSize=false
    , reachabilityInspectorClasses=[class com.squareup.leakcanary.AndroidReachabilityInspectors$ViewInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$ActivityInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$DialogInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$ApplicationInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$FragmentInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$SupportFragmentInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$MessageQueueInspector, class com.squareup.leakcanary.AndroidReachabilityInspectors$MortarPresenterInspector]}

其中excludedRefs表示需要忽略的引用,也可以通过{createBuilder(EnumSet refs)}来自定义需要忽略的引用。其中{AndroidExcludedRefs}中定义各种Android需要忽略的引用,感兴趣的可以仔细看看。
很奇怪,看不懂,没事后面我们来分析,这个对象是怎么得出内存泄漏的呢?并找出泄漏路径的呢?

分析

先看一下其时序图:

分析过程时序图

荐
                                                        【Android】LeakCanary原理解析
应该能看出来,主要是在{HeapAnalyzer}

HeapAnalyzerService

public final class HeapAnalyzerService extends ForegroundService
        implements AnalyzerProgressListener {
    ...
    @Override
    protected void onHandleIntentInForeground(@Nullable Intent intent) {
        if (intent == null) {
            CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
            return;
        }
        String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
        HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
        //1。构建HeapAnalyzer 
        HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);    
        //2.开始分析
        AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);
        //3.发送分析结果,展示
        AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
    }
    ...
}

{HeapAnalyzerService}主要试分析heapDumpFile文件是否存在泄漏。最重要的是第二步。

HeapAnalyzer

    //HeapAnalyzer.java
    /**
     * Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
     * and then computes the shortest strong reference path from that instance to the GC roots.
     */
    public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey,
                                       boolean computeRetainedSize) {
        long analysisStartNanoTime = System.nanoTime();

        if (!heapDumpFile.exists()) {
            Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
            return failure(exception, since(analysisStartNanoTime));
        }

        try {
            listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
            HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
            HprofParser parser = new HprofParser(buffer);
            listener.onProgressUpdate(PARSING_HEAP_DUMP);
            Snapshot snapshot = parser.parse();
            listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
            deduplicateGcRoots(snapshot);
            listener.onProgressUpdate(FINDING_LEAKING_REF);
            Instance leakingRef = findLeakingReference(referenceKey, snapshot); //判断是否泄漏路径

            // False alarm, weak reference was cleared in between key check and heap dump.
            if (leakingRef == null) { //没有泄漏
                return noLeak(since(analysisStartNanoTime));
            }
            //找到泄漏路径
            return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
        } catch (Throwable e) {
            return failure(e, since(analysisStartNanoTime));
        }
    }

其中{deduplicateGcRoots}和{findLeakingReference}方法才是重点。关于这两个方法的分析详见下回分解!!!

AbstractAnalysisResultService

AbstractAnalysisResultService主要作用是拿到前面的分析结果进行展示,篇幅有限,不在赘述。

展示

  //LeakCanaryInternals.java
  public static final String SAMSUNG = "samsung";
  public static final String MOTOROLA = "motorola";
  public static final String LENOVO = "LENOVO";
  public static final String LG = "LGE";
  public static final String NVIDIA = "NVIDIA";
  public static final String MEIZU = "Meizu";
  public static final String HUAWEI = "HUAWEI";
  public static final String VIVO = "vivo";
  
  public static void showNotification(Context context, CharSequence contentTitle,
      CharSequence contentText, PendingIntent pendingIntent, int notificationId) {
    Notification.Builder builder = new Notification.Builder(context)
        .setContentText(contentText)
        .setContentTitle(contentTitle)
        .setAutoCancel(true)
        .setContentIntent(pendingIntent);

    Notification notification = buildNotification(context, builder);
    NotificationManager notificationManager =
        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    notificationManager.notify(notificationId, notification); //发送通知
  }

本文地址:https://blog.csdn.net/yuzhangzhen/article/details/108108453