8

I've been playing around with SVG support in Android and came up with this library which claims that it supports SVG just as it they were native.

Since I took great pains in discovering that this is not really possible, I went to see how this dude actually managed it. So I came upon his Resources derivative in which he declares a method (loadDrawable) that has default visibility in base Resources class.

The funny thing is, normally lint would just report that you can't write this method since it would hide base method, but in this particular case (note the absense of @Override directive) this method gets to be called as if it were written in the base class. All the methods invoking this method will invoke the override instead of original method. For me coming from classic compilers such as C++ or Pascal, this is totally beyond comprehension.

Based on this, I managed to get my SVG support working completely with one single use of reflection and am super-happy about this, but:

Why does this work?

laalto
  • 150,114
  • 66
  • 286
  • 303
velis
  • 8,747
  • 4
  • 44
  • 64
  • 1
    Yes, I also came across this issue when designing a custom widget extending one of the default ones, where I copied over a package-private callback method, thinking to implement it's semantics manually. I was surprised to observe that this caused the framework implementation to be overridden, without the possibility of calling through to it. At that time I didn't have a lot of time to investigate the issue, so I just changed the method signature to private. Now after testing this on different Android API levels and on the Oracle JVM, it looks like this is a bug in the Dalvik implementation. – corsair992 Apr 14 '14 at 22:10
  • Are you sure the method overrides the base class's method? It is legit to reuse a private or package private method name in a subclass outside the package. If it weren't, that fact would leak implementation information out of he class. But it should not get called as if it overrides the original. – flup Apr 15 '14 at 06:54
  • I am. There's a method named `getDrawable` which calls `loadDrawable` and I haven't overridden it and still my `loadDrawable` is called even when the inherited `getDrawable` invokes it. Originally I just thought I'd have to replace both so that any extarnal call to `loadDrawable` would be caught by my `loadDrawable` and any `getDrawable` would be caught by my override, but it turns out that's not necessary at all. – velis Apr 15 '14 at 06:58
  • 1
    @flup: You can test this very easily by writing a simple test case for this, and running it in both Android and on another JVM. You will see that it will work as expected on other JVMs, and as described here in Android. – corsair992 Apr 15 '14 at 07:36
  • @corsair992: you have actually tested and confirmed this? – velis Apr 15 '14 at 13:06
  • @velis: Yes, as I mentioned in my first comment. – corsair992 Apr 15 '14 at 13:26
  • Hm, I'll test this with ART as well. Maybe there it will fail? But I don't suppose this is a problem in Dalvik engine. It would then rather be a problem with Google's source to bytecode compiler. I don't suppose a VM would complain much about overriding an overridable method. It's the bytecode compiler that should disallow this or am I wrong? Still, you should post this as answer. After I confirm, I will accept it. – velis Apr 15 '14 at 14:01
  • Confirmed: dalvik works while ART reports an error `Before Android 4.1, method ... would have incorrectly overridden the package-private method in android.content.res.Resources`. This (the Dalvik bug) is indeed cause of the incorrect (but still favourable) behaviour. – velis Apr 15 '14 at 18:56
  • Oh, another observation: even dalvik notices this override and reports it, but still allows it. Seems Google noticed the bug a while back, but they decided to let it be because too many apps were exploiting it. – velis Apr 15 '14 at 19:39
  • @velis: Interesting. The correct behaviour would of course be to just not override the package-private method from another package. But it looks like they are reporting it as an error since existing applications may be depending on this behaviour? Strange. You should add a bug report on this if someone hasn't already, and see what the response is. – corsair992 Apr 15 '14 at 19:49
  • @corsair992: You should still post your observation as the answer as it is the actual answer to the problem. I can't mark a comment as answer :) Also, I'm not actually interested in Google's response. They responded with ART which doesn't allow this any more. I just wish they wrote classes such that people would be able to override in the first place... – velis Apr 15 '14 at 20:04

1 Answers1

11

It looks like there is a bug in the Dalvik interpreter which allows package-private methods to be overridden. Apparently, Google recognized this issue (in Jelly Bean?), as Dalvik reports a warning that it is incorrectly overriding a package-private method in this case, and ART reports it as an error and fails to compile it. The correct behaviour would of course be to allow it but not allow overriding package-private methods from other packages, but it looks like Google want to avoid breaking existing applications that depend on this behaviour.

UPDATE: This is now officially confirmed in the ART documentation update on June 16, although it states that ART issues a warning instead of a critical error as velis reported in the comments on the question:

Dalvik incorrectly allowed subclasses to override package-private methods. ART issues a warning in such cases:

Before Android 4.1, method void com.foo.Bar.quux()
would have incorrectly overridden the package-private method in
com.quux.Quux

If you intend to override a class's method in a different package, declare the method as public or protected.

Community
  • 1
  • 1
corsair992
  • 3,050
  • 22
  • 33
  • I shoud note here that the behaviour is changed only so far that the app no longer crashes if it tries this, but it doesn't work either. – velis Oct 20 '14 at 20:45