我的应用程序在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$4; interface 113 'Landroid/media/midi/MidiManager$OnDeviceOpenedListener;'
W/dalvikvm: Link of class 'Lcom/arlomedia/myapp/App$4;' failed
E/dalvikvm: Could not find class 'com.arlomedia.myapp.App$4', referenced from method com.arlomedia.myapp.App.addMidiPorts
W/dalvikvm: VFY: unable to resolve new-instance 447 (Lcom/arlomedia/myapp/App$4;) in Lcom/arlomedia/myapp/App;
D/dalvikvm: VFY: replacing opcode 0x22 at 0x0007
,然后在我的应用程序中的许多类上出现类似的错误。该应用程序将运行,直到需要使用这些缺少的类之一,然后崩溃。
我已经阅读了几篇与Android 4的64K方法限制有关的类似问题,可以想象,在最近的更新中,我碰巧越过了64K阈值。因此,我尝试根据these instructions设置multidex,但这没有什么区别,我也不知道如何判断它是否有效。构建APK文件并检查其内容时,我仅看到一个classes.dex文件。这可能意味着我的multidex设置无法正常工作,或者我的方法不超过64K,而我的问题出在其他方面。
我的第二个理论是,在Android 6中添加的MidiDeviceInfo类的使用在Android 4上引起了问题。但是,我使用版本检查和@TargetApi(24)
注释来忽略MIDI代码在Android 4-5上。而且这个问题在Android 5上不会发生。但是奇怪的是,这是每次都会弹出的第一个错误。
这些理论之一听起来是否正确,或者听起来像是其他问题?
顺便说一句,“无法找到签名中引用的类”是什么意思-方法签名?我确实有一些方法使用MidiDeviceInfo类型的参数,但是我用@TargetApi(24)
注释了所有方法,并且编译没有问题。
更新
借助homerman的技巧,我可以看到方法的数量不是问题。这使我回到MIDI兼容性上,但是我正在查看与以前版本的差异,并且我看不到MIDI代码的任何更改。我还要寻找什么?
更新2
自上一个版本以来,我在差异中找不到任何相关的内容,因此我从VCS中检出了最后一个版本并运行它,它运行正常。但是我在该版本的logcat中注意到MIDI类的所有相同错误。因此,看来这些并不是真正的问题的一部分。我想这就是Android遇到新类时所做的事情。我一直在比较上一个好版本和当前版本之间的logcat,然后看到了不同之处:我最近在一个类中添加了一个OnScrollChangeListener。这就产生了另一批丢失的类错误,这些错误不是上一个应用程序版本中的。当我删除该新功能时,该应用程序又可以在Android 4上正常运行。
这是问题所在:我有一个这样的类定义:
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);
}
}
显然,类定义中View.OnScrollChangeListener
的存在是在弄乱一切。有没有办法声明仅适用于受支持的Android版本?
显然,Android 5会优雅地忽略此不受支持的引用,但Android 4不会。
答案 0 :(得分:2)
在API级别23中添加了View.OnScrollChangeListener
接口。这意味着您的类定义已经是一个问题;如果此类在23岁之前加载,您将崩溃。
解决方案是创建一个公共父级(可能是一个接口)和该父级的两个具体实现:一个用于23之前的版本,一个用于23+的版本。然后使用工厂基于当前运行的设备获取适当的实现。
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 { ... }
答案 1 :(得分:0)
Ben P的回答使我处在正确的轨道上,但是我对将类转换为接口感到困惑,所以这是我想出的:
// 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);