• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20177

23 Feb 2026 03:46PM UTC coverage: 88.701% (-0.01%) from 88.714%
#20177

push

github

web-flow
Trigger R8's ServiceLoader optimization

This simplifies R8 Full Mode's configuration when paired with R8
optimizations (which is made more difficult to avoid in AGP 9), as when
the optimization is triggered Full Mode will automatically keep the
constructor for the relevant classes.

android-interop-test doesn't currently enable R8 optimizations, so it
doesn't actually demonstrate the benefit, but I have manually confirmed
that enabling proguard-android-optimize.txt does cause the ServiceLoader
optimization in android-interop-test. Note that full mode is a separate
configuration and not necessary to get the ServiceLoader optimization.

35461 of 39978 relevant lines covered (88.7%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.1
/../api/src/main/java/io/grpc/ServiceProviders.java
1
/*
2
 * Copyright 2017 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc;
18

19
import com.google.common.annotations.VisibleForTesting;
20
import com.google.common.base.Supplier;
21
import java.util.ArrayList;
22
import java.util.Collections;
23
import java.util.Comparator;
24
import java.util.Iterator;
25
import java.util.List;
26
import java.util.ListIterator;
27
import java.util.ServiceConfigurationError;
28
import java.util.ServiceLoader;
29

30
final class ServiceProviders {
31
  private ServiceProviders() {
32
    // do not instantiate
33
  }
34

35
  /**
36
   * If this is not Android, returns all available implementations discovered via
37
   * {@link ServiceLoader}.
38
   * If this is Android, returns all available implementations in {@code hardcoded}.
39
   * The list is sorted in descending priority order.
40
   *
41
   * <p>{@code serviceLoader} should be created with {@code ServiceLoader.load(MyClass.class,
42
   * MyClass.class.getClassLoader()).iterator()} in order to be detected by R8 so that R8 full mode
43
   * will keep the constructors for the provider classes.
44
   */
45
  public static <T> List<T> loadAll(
46
      Class<T> klass,
47
      Iterator<T> serviceLoader,
48
      Supplier<Iterable<Class<?>>> hardcoded,
49
      final PriorityAccessor<T> priorityAccessor) {
50
    Iterator<T> candidates;
51
    if (serviceLoader instanceof ListIterator) {
1✔
52
      // A rewriting tool has replaced the ServiceLoader with a List of some sort (R8 uses
53
      // ArrayList, AppReduce uses singletonList). We prefer to use such iterators on Android as
54
      // they won't need reflection like the hard-coded list does. In addition, the provider
55
      // instances will have already been created, so it seems we should use them.
56
      //
57
      // R8: https://r8.googlesource.com/r8/+/490bc53d9310d4cc2a5084c05df4aadaec8c885d/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
58
      // AppReduce: service_loader_pass.cc
59
      candidates = serviceLoader;
×
60
    } else if (isAndroid(klass.getClassLoader())) {
1✔
61
      // Avoid getResource() on Android, which must read from a zip which uses a lot of memory
62
      candidates = getCandidatesViaHardCoded(klass, hardcoded.get()).iterator();
×
63
    } else if (!serviceLoader.hasNext()) {
1✔
64
      // Attempt to load using the context class loader and ServiceLoader.
65
      // This allows frameworks like http://aries.apache.org/modules/spi-fly.html to plug in.
66
      candidates = ServiceLoader.load(klass).iterator();
1✔
67
    } else {
68
      candidates = serviceLoader;
1✔
69
    }
70
    List<T> list = new ArrayList<>();
1✔
71
    while (candidates.hasNext()) {
1✔
72
      T current = candidates.next();
1✔
73
      if (!priorityAccessor.isAvailable(current)) {
1✔
74
        continue;
1✔
75
      }
76
      list.add(current);
1✔
77
    }
1✔
78

79
    // Sort descending based on priority.  If priorities are equal, compare the class names to
80
    // get a reliable result.
81
    Collections.sort(list, Collections.reverseOrder(new Comparator<T>() {
1✔
82
      @Override
83
      public int compare(T f1, T f2) {
84
        int pd = priorityAccessor.getPriority(f1) - priorityAccessor.getPriority(f2);
1✔
85
        if (pd != 0) {
1✔
86
          return pd;
1✔
87
        }
88
        return f1.getClass().getName().compareTo(f2.getClass().getName());
1✔
89
      }
90
    }));
91
    return Collections.unmodifiableList(list);
1✔
92
  }
93

94
  /**
95
   * Returns true if the {@link ClassLoader} is for android.
96
   */
97
  static boolean isAndroid(ClassLoader cl) {
98
    try {
99
      // Specify a class loader instead of null because we may be running under Robolectric
100
      Class.forName("android.app.Application", /*initialize=*/ false, cl);
×
101
      return true;
×
102
    } catch (Exception e) {
1✔
103
      // If Application isn't loaded, it might as well not be Android.
104
      return false;
1✔
105
    }
106
  }
107

108
  /**
109
   * For testing only: Loads service providers for the {@code klass} service using {@link
110
   * ServiceLoader}. Does not support spi-fly and related tricks.
111
   */
112
  @VisibleForTesting
113
  public static <T> Iterable<T> getCandidatesViaServiceLoader(Class<T> klass, ClassLoader cl) {
114
    Iterable<T> i = ServiceLoader.load(klass, cl);
1✔
115
    if (!i.iterator().hasNext()) {
1✔
116
      return null;
×
117
    }
118
    return i;
1✔
119
  }
120

121
  /**
122
   * Load providers from a hard-coded list. This avoids using getResource(), which has performance
123
   * problems on Android (see https://github.com/grpc/grpc-java/issues/2037).
124
   */
125
  @VisibleForTesting
126
  static <T> Iterable<T> getCandidatesViaHardCoded(Class<T> klass, Iterable<Class<?>> hardcoded) {
127
    List<T> list = new ArrayList<>();
1✔
128
    for (Class<?> candidate : hardcoded) {
1✔
129
      T t = createForHardCoded(klass, candidate);
1✔
130
      if (t == null) {
1✔
131
        continue;
1✔
132
      }
133
      list.add(t);
1✔
134
    }
1✔
135
    return list;
1✔
136
  }
137

138
  private static <T> T createForHardCoded(Class<T> klass, Class<?> rawClass) {
139
    try {
140
      return rawClass.asSubclass(klass).getConstructor().newInstance();
1✔
141
    } catch (ClassCastException ex) {
1✔
142
      // Tools like Proguard that perform obfuscation rewrite strings only when the class they
143
      // reference is known, as otherwise they wouldn't know its new name. This means some
144
      // hard-coded Class.forNames() won't be rewritten. This can cause ClassCastException at
145
      // runtime if the class ends up appearing on the classpath but that class is part of a
146
      // separate copy of grpc. With tools like Maven Shade Plugin the class wouldn't be found at
147
      // all and so would be skipped. We want to skip in this case as well.
148
      return null;
1✔
149
    } catch (Throwable t) {
1✔
150
      throw new ServiceConfigurationError(
1✔
151
          String.format("Provider %s could not be instantiated %s", rawClass.getName(), t), t);
1✔
152
    }
153
  }
154

155
  /**
156
   * An interface that allows us to get priority information about a provider.
157
   */
158
  public interface PriorityAccessor<T> {
159
    /**
160
     * Checks this provider is available for use, taking the current environment into consideration.
161
     * If {@code false}, no other methods are safe to be called.
162
     */
163
    boolean isAvailable(T provider);
164

165
    /**
166
     * A priority, from 0 to 10 that this provider should be used, taking the current environment
167
     * into consideration. 5 should be considered the default, and then tweaked based on environment
168
     * detection. A priority of 0 does not imply that the provider wouldn't work; just that it
169
     * should be last in line.
170
     */
171
    int getPriority(T provider);
172
  }
173
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc