方法getDeclaredMethods
,当在一个类对象上调用时,应该返回一个Method对象数组,这些对象表示直接声明为该类的一部分的方法,它不应该返回任何继承的方法。
当我直接通过Android Studio安装应用时,无论活动的构建版本变体如何,此功能都能正常工作。切换到发布构建版本不足以触发问题。
编译APK或App Bundle(.aab)并以这种方式安装应用时会出现问题。(直接将APK复制到设备上,或在Google Play商店推出捆绑包并从那里安装应用)。
以下是我的测试场景,在一个全新的Android Studio项目中,使用SDK 33、minSdk 21
(Android 5.0)、minifyEnabled false
,并删除默认的proguardFiles
语句,以确保这不是由R8 / ProGuard引起的。
接口:
// TestInterface.java
package com.example.testapp;
public interface TestInterface {
default String methodWithDefault() {
return "default";
}
String methodWithoutDefault();
}
实现类:
// TestClass.java
package com.example.testapp;
public class TestClass implements TestInterface {
@Override
public String methodWithoutDefault() {
return "non-default";
}
}
测试用例:
// MainActivity.java
package com.example.testapp;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TestClass test = new TestClass();
StringBuilder sb = new StringBuilder("Methods:\n");
for (Method m : TestClass.class.getDeclaredMethods()) {
sb.append('\n').append(m.toString()).append('\n');
try {
String s = (String) m.invoke(test);
sb.append("Result: ").append(s).append('\n');
} catch (InvocationTargetException e) {
sb.append("Target exception: ").append(e.getTargetException()).append('\n');
} catch (IllegalAccessException e) {
sb.append("Illegal access.\n");
}
}
System.out.println(sb);
TextView textView = findViewById(R.id.textView);
textView.setText(sb.toString());
}
}
app/build.gradle
的内容:
plugins {
id 'com.android.application'
}
android {
namespace 'com.example.testapp'
compileSdk 33
defaultConfig {
applicationId "com.example.testapp"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility 11
targetCompatibility 11
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
}
直接从Android Studio运行时的输出:
Methods:
public java.lang.String com.example.testapp.TestClass.methodWithoutDefault()
Result: non-default
构建APK并将其安装在设备上时的输出:
Methods:
public java.lang.String com.example.testapp.TestClass.methodWithDefault()
Result: default
public java.lang.String com.example.testapp.TestClass.methodWithoutDefault()
Result: non-default
问题:
1.为什么会发生这种情况?
1.解决这个问题的最佳方法是什么?
1条答案
按热度按时间jchrr9hc1#
在典型的橡皮鸭调试方式中,我发现了一些重要的细节,以及如何在改进测试用例的同时解决这个问题,然后将其发布到StackOverflow上...
首先,让我们打印一些方法的属性,我们可以修改
MainActivity
如下:这让Android Studio抱怨不已,因为
isDefault()
只能从SDK 24开始使用,而我们的minSdk是21。让我们将minSdk增加到24并检查输出:
哦,继承的方法不见了!如果你仔细研究minSdk,你会发现任何〈= 23的值都会出现这个问题。所以,我们做了第一个重要的实现:
(Note安装APK的Android设备的实际SDK版本似乎并不重要;我正在SDK 25/Android 7.1.1设备上测试这一切。)
让我们切换回minSdk 21,并使对
isDefault
的调用以SDK版本检查为条件,如下所示:在安装了SDK版本24或更高版本的设备上安装新APK时,将生成以下输出:
令人困惑的是,该方法 * 没有 * 被标记为默认值,但它被标记为合成的,所以:
Method.isSynthetic()
过滤掉继承的方法。**所以,为了回答我们的问题:
1.为什么会发生这种情况?
我认为合成方法是在minSdk小于24时生成的,因为APK * 可以 * 安装在旧的Android设备上,该设备在其JVM中没有对默认接口方法的"直接"支持。
当直接通过Android Studio安装应用程序时,我猜minSdk值会被忽略,因为Android Studio可以检查设备的实际版本是什么。
如果任何人有更确切的信息,请分享。
1.解决这个问题的最佳方法是什么?
继承的默认方法可以通过调用
isSynthetic()
过滤掉,isSynthetic()
将返回true
。如果你想保留一些其他的合成方法,只过滤掉这些方法,我不知道如何实现,但这应该是一个非常罕见的情况。