解决方法
如果是系统应用,且使用了compileOnly framework.jar ,就用压缩包工具删除内部的 android/content/ServiceConnection 类,其他的类似的报错去删除其对应的接口类。
原因:
-
framework.jar 存在这个类时,会导致编译出的 apk 包含 ServiceConnection.onBindingDied 方法,且方法内会调用 android/content/ServiceConnection$-CC.onBindingDied 方法。在某些情况下触发 onBindingDied 时(比如覆盖安装被绑定的服务) 会出现 NoClassDefFoundError: Failed resolution of: Landroid/content/ServiceConnection$-CC 导致崩溃。
-
通过反编译和写测试代码初步验证 -CC 这种是 java 8 接口中 写 default 方法在编译时会生成的代码
// 带 default 方法的测试接口 public interface TestConnection { void bind(); default void bindingTest(){ Log.d("TestConnection","bindingTest"); } } //另外一个测试类 private val testConnection = object :TestConnection{ override fun bind() { Log.d(TAG,"bind") } }
//编译生成apk后反编译查看代码。 public final class DewarpingHandle$testConnection$1 implements TestConnection { @Override //可以看到编译自动增加了这个方法 public /* synthetic */ void bindingTest() { TestConnection.CC.$default$bindingTest(this); } DewarpingHandle$testConnection$1() { } @Override public void bind() { Log.d("test", "bind"); } } public interface TestConnection { void bind(); void bindingTest(); //这里是反编译工具自动把-CC改成了CC /* renamed from: com.xxxxxxxx.TestConnection$-CC reason: invalid class name */ public final /* synthetic */ class CC { public static void $default$bindingTest(TestConnection _this) { Log.d("TestConnection", "bindingTest"); } } }
可以看到, interface 的接口中写 default 接口,其实是编译时在内部生成一个 -CC 的类,在实例化接口的时候,默认增加了该 default 方法,并直接调用到 TestConnection.-CC 类。从而实现了 “default“ 功能。
-
目前很多系统应用比较特殊,需要调用到系统的隐藏方法,而要在 AS 内编译通过,就必须要 compileOnly framework.jar 并且通过 options.bootstrapClasspath 让编译器优先找 framework.jar 这种做法。而这种情况会导致把系统接口误认为是普通接口,做这种生成 -CC 类 的操作,从而实现适配。但是实际上,系统编译生成 framework 的时候 实际已经满足 Java 8 , 不需要做这种操作,所以系统里面的 ServiceConnection 自然也没有 -CC 这种中间类。从而导致了 NoClassDefFoundError: Failed resolution of: Landroid/content/ServiceConnection$-CC 的报错。(对编译器不熟悉,后半部分是猜想的。)
-
但是从 framework.jar 删除这个类后,编译器直接调用到SDK内的类和方法。猜测编译器直接调用 SDK 的时候能已知系统支持 Java 8 ,支持 default 写法,所以不再需要去写这种 -CC 类去适配。从而生成的 apk 中也没有去增加 onBindingDied 方法,更没有去调用 ServiceConnection$-CC.onBindingDied 方法了。