在 Android 上突然丢失了很多 类 4
Suddenly missing lots of classes on Android 4
多年来,我的应用在 Android 4.0.3 及更高版本上一直 运行 正常。随着最近的一次小更新,它突然开始在 Android 4.x 设备上崩溃。当我在 Android 4 上调试它时,它可以正常构建和安装,但控制台会收到很多以
开头的错误
W/dalvikvm: VFY: unable to find class referenced in signature (Landroid/media/midi/MidiDeviceInfo;)
I/dalvikvm: Failed resolving Lcom/arlomedia/myapp/App; interface 113 'Landroid/media/midi/MidiManager$OnDeviceOpenedListener;'
W/dalvikvm: Link of class 'Lcom/arlomedia/myapp/App;' failed
E/dalvikvm: Could not find class 'com.arlomedia.myapp.App', referenced from method com.arlomedia.myapp.App.addMidiPorts
W/dalvikvm: VFY: unable to resolve new-instance 447 (Lcom/arlomedia/myapp/App;) in Lcom/arlomedia/myapp/App;
D/dalvikvm: VFY: replacing opcode 0x22 at 0x0007
然后我的应用程序中的许多 class 出现类似错误。该应用程序会一直运行,直到它需要使用这些缺失的 classes 之一,然后崩溃。
我在Android4上看过几篇关于64K方法限制的类似问题的帖子,可以想象,在最近的更新中,我恰好跨过了64K的门槛。所以我尝试根据 these instructions 设置 multidex,但它没有什么区别,我不知道如何判断它是否有效。当我构建一个 APK 文件并检查其内容时,我只看到一个 classes.dex 文件。这可能意味着我的 multidex 设置不工作,或者我没有超过 64K 的方法,我的问题是别的。
我的第二个理论是 Android 6 中添加的 MidiDeviceInfo class 的使用导致 Android 4 出现问题。但是,我正在使用版本检查和 @TargetApi(24)
注释忽略 Android 4-5 上的 MIDI 代码。而且Android5上没有出现这个问题,奇怪的是每次都是第一个报错
这些理论中的一个听起来是否正确,或者听起来像是其他问题?
顺便问一下,"unable to find class referenced in signature" 到底是什么意思——方法签名?我确实有一些方法使用了 MidiDeviceInfo 类型的参数,但我用 @TargetApi(24)
注释了所有这些方法,编译时没有任何问题。
更新
感谢 homerman 的提示,我可以看出方法的数量不是问题。这使我回到 MIDI 兼容性,但我正在查看与以前版本的差异,但我没有看到 MIDI 代码有任何变化。我还应该寻找什么?
更新 2
自上一个版本以来,我找不到与差异相关的任何内容,所以我从我的 VCS 检查了上一个版本,运行 它 运行 很好。但是我在那个版本的 logcat 中注意到 MIDI classes 的所有相同错误。所以看起来这些并不是真正的问题的一部分。我想这正是 Android 在遇到更新的 classes 时所做的。我一直比较上一个好版本和当前版本之间的 logcats,然后我看到了不同之处:我最近向我的 classes 之一添加了一个 OnScrollChangeListener。这产生了另一批丢失的 class 错误,这些错误不在上一个应用程序版本中。当我删除该新功能时,该应用程序再次在 Android 4 上正常运行。
问题来了:我有一个这样的 class 定义:
public class DocumentViewer extends RelativeLayout implements View.OnScrollChangeListener {
if (Build.VERSION.SDK_INT >= 23) {
textView.setOnScrollChangeListener(this);
}
@Override
public void onScrollChange(final View scrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
Log.d("onScrollChange", "scroll changed: " + scrollY);
}
}
显然 class 定义中 View.OnScrollChangeListener
的存在导致一切混乱。有没有办法声明仅针对受支持的 Android 版本?
显然 Android 5 优雅地忽略了这个不受支持的引用,但 Android 4 没有。
View.OnScrollChangeListener
接口是在API第23层加入的。也就是说你的class定义已经有问题了;如果这个 class 在 23 之前加载,你会崩溃。
解决方案是创建一个公共父级(可能是一个接口)和该父级的两个具体实现:一个用于 23 岁之前,一个用于 23 岁以上。然后根据currently-运行设备使用一个工厂得到合适的实现。
public interface DocumentViewer {
// interface methods
}
public class DocumentViewPre23 extends RelativeLayout implements DocumentViewer {
// interface methods
}
public class DocumentViewer23 extends RelativeLayout implements DocumentViewer, View.OnScrollChangeListener {
// interface methods
}
然后你可以有类似的东西
public static DocumentViewer getDocumentViewer() {
if (Build.VERSION.SDK_INT >= 23) {
return new DocumentViewer23();
} else {
return new DocumentViewerPre23();
}
}
android 支持库经常这样做,物有所值。这是来自 ActionBarDrawerToggle
的示例,它根据 API 版本在两个实现之间切换:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
mActivityImpl = new JellybeanMr2Delegate(activity);
} else {
mActivityImpl = new IcsDelegate(activity);
}
public interface Delegate {
...
}
@RequiresApi(18)
private static class JellybeanMr2Delegate implements Delegate {
...
}
private static class IcsDelegate implements Delegate {
...
}
Ben P 的回答让我走上了正确的道路,但我对将我的 class 转换为界面感到困惑,所以这是我想出的:
// this was the original class and I added scrollViewDidScroll() to keep all my related code in one class
public class DocumentViewer extends RelativeLayout {
if (Build.VERSION.SDK_INT >= 23) {
if (this instanceof DocumentViewerExtras) {
// assign the listener on supported devices
DocumentViewerExtras thisExtras = (DocumentViewerExtras)this;
textView.setOnScrollChangeListener(thisExtras);
}
}
public void scrollViewDidScroll(final View scrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
// do something with the scroll results
}
}
// this is a new class that adds OnScrollChangeListener on supported devices
@RequiresApi(23)
public class DocumentViewerExtras extends DocumentViewer implements View.OnScrollChangeListener {
public DocumentViewerExtras(Fragment fragment) {
super(fragment);
}
@Override
public void onScrollChange(final View scrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
this.scrollViewDidScroll(scrollView, scrollX, scrollY, oldScrollX, oldScrollY);
}
}
// this method instantiates the best available class
public DocumentViewer getDocumentViewer(Fragment fragment) {
if (Build.VERSION.SDK_INT >= 23) {
return new DocumentViewerExtras(fragment);
} else {
return new DocumentViewer(fragment);
}
}
// this is how I get a new instance of the class
DocumentViewer documentViewer = getDocumentViewer(this);
多年来,我的应用在 Android 4.0.3 及更高版本上一直 运行 正常。随着最近的一次小更新,它突然开始在 Android 4.x 设备上崩溃。当我在 Android 4 上调试它时,它可以正常构建和安装,但控制台会收到很多以
开头的错误W/dalvikvm: VFY: unable to find class referenced in signature (Landroid/media/midi/MidiDeviceInfo;)
I/dalvikvm: Failed resolving Lcom/arlomedia/myapp/App; interface 113 'Landroid/media/midi/MidiManager$OnDeviceOpenedListener;'
W/dalvikvm: Link of class 'Lcom/arlomedia/myapp/App;' failed
E/dalvikvm: Could not find class 'com.arlomedia.myapp.App', referenced from method com.arlomedia.myapp.App.addMidiPorts
W/dalvikvm: VFY: unable to resolve new-instance 447 (Lcom/arlomedia/myapp/App;) in Lcom/arlomedia/myapp/App;
D/dalvikvm: VFY: replacing opcode 0x22 at 0x0007
然后我的应用程序中的许多 class 出现类似错误。该应用程序会一直运行,直到它需要使用这些缺失的 classes 之一,然后崩溃。
我在Android4上看过几篇关于64K方法限制的类似问题的帖子,可以想象,在最近的更新中,我恰好跨过了64K的门槛。所以我尝试根据 these instructions 设置 multidex,但它没有什么区别,我不知道如何判断它是否有效。当我构建一个 APK 文件并检查其内容时,我只看到一个 classes.dex 文件。这可能意味着我的 multidex 设置不工作,或者我没有超过 64K 的方法,我的问题是别的。
我的第二个理论是 Android 6 中添加的 MidiDeviceInfo class 的使用导致 Android 4 出现问题。但是,我正在使用版本检查和 @TargetApi(24)
注释忽略 Android 4-5 上的 MIDI 代码。而且Android5上没有出现这个问题,奇怪的是每次都是第一个报错
这些理论中的一个听起来是否正确,或者听起来像是其他问题?
顺便问一下,"unable to find class referenced in signature" 到底是什么意思——方法签名?我确实有一些方法使用了 MidiDeviceInfo 类型的参数,但我用 @TargetApi(24)
注释了所有这些方法,编译时没有任何问题。
更新
感谢 homerman 的提示,我可以看出方法的数量不是问题。这使我回到 MIDI 兼容性,但我正在查看与以前版本的差异,但我没有看到 MIDI 代码有任何变化。我还应该寻找什么?
更新 2
自上一个版本以来,我找不到与差异相关的任何内容,所以我从我的 VCS 检查了上一个版本,运行 它 运行 很好。但是我在那个版本的 logcat 中注意到 MIDI classes 的所有相同错误。所以看起来这些并不是真正的问题的一部分。我想这正是 Android 在遇到更新的 classes 时所做的。我一直比较上一个好版本和当前版本之间的 logcats,然后我看到了不同之处:我最近向我的 classes 之一添加了一个 OnScrollChangeListener。这产生了另一批丢失的 class 错误,这些错误不在上一个应用程序版本中。当我删除该新功能时,该应用程序再次在 Android 4 上正常运行。
问题来了:我有一个这样的 class 定义:
public class DocumentViewer extends RelativeLayout implements View.OnScrollChangeListener {
if (Build.VERSION.SDK_INT >= 23) {
textView.setOnScrollChangeListener(this);
}
@Override
public void onScrollChange(final View scrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
Log.d("onScrollChange", "scroll changed: " + scrollY);
}
}
显然 class 定义中 View.OnScrollChangeListener
的存在导致一切混乱。有没有办法声明仅针对受支持的 Android 版本?
显然 Android 5 优雅地忽略了这个不受支持的引用,但 Android 4 没有。
View.OnScrollChangeListener
接口是在API第23层加入的。也就是说你的class定义已经有问题了;如果这个 class 在 23 之前加载,你会崩溃。
解决方案是创建一个公共父级(可能是一个接口)和该父级的两个具体实现:一个用于 23 岁之前,一个用于 23 岁以上。然后根据currently-运行设备使用一个工厂得到合适的实现。
public interface DocumentViewer {
// interface methods
}
public class DocumentViewPre23 extends RelativeLayout implements DocumentViewer {
// interface methods
}
public class DocumentViewer23 extends RelativeLayout implements DocumentViewer, View.OnScrollChangeListener {
// interface methods
}
然后你可以有类似的东西
public static DocumentViewer getDocumentViewer() {
if (Build.VERSION.SDK_INT >= 23) {
return new DocumentViewer23();
} else {
return new DocumentViewerPre23();
}
}
android 支持库经常这样做,物有所值。这是来自 ActionBarDrawerToggle
的示例,它根据 API 版本在两个实现之间切换:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { mActivityImpl = new JellybeanMr2Delegate(activity); } else { mActivityImpl = new IcsDelegate(activity); }
public interface Delegate { ... }
@RequiresApi(18) private static class JellybeanMr2Delegate implements Delegate { ... }
private static class IcsDelegate implements Delegate { ... }
Ben P 的回答让我走上了正确的道路,但我对将我的 class 转换为界面感到困惑,所以这是我想出的:
// this was the original class and I added scrollViewDidScroll() to keep all my related code in one class
public class DocumentViewer extends RelativeLayout {
if (Build.VERSION.SDK_INT >= 23) {
if (this instanceof DocumentViewerExtras) {
// assign the listener on supported devices
DocumentViewerExtras thisExtras = (DocumentViewerExtras)this;
textView.setOnScrollChangeListener(thisExtras);
}
}
public void scrollViewDidScroll(final View scrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
// do something with the scroll results
}
}
// this is a new class that adds OnScrollChangeListener on supported devices
@RequiresApi(23)
public class DocumentViewerExtras extends DocumentViewer implements View.OnScrollChangeListener {
public DocumentViewerExtras(Fragment fragment) {
super(fragment);
}
@Override
public void onScrollChange(final View scrollView, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
this.scrollViewDidScroll(scrollView, scrollX, scrollY, oldScrollX, oldScrollY);
}
}
// this method instantiates the best available class
public DocumentViewer getDocumentViewer(Fragment fragment) {
if (Build.VERSION.SDK_INT >= 23) {
return new DocumentViewerExtras(fragment);
} else {
return new DocumentViewer(fragment);
}
}
// this is how I get a new instance of the class
DocumentViewer documentViewer = getDocumentViewer(this);