5

I need to proxy methods on various view classes in the Android UI Framework such as TextView. Particularly TextView#setText(int resId). This method is not part of an interface. Therefore, Java Proxy will not work since it only works for interfaces. I need to use bytecode manipulation.

I found a library called dexmaker that seemed promising. I am assuming that I need to do runtime byte code manipulation since the Android View classes are only actually available on the device. Dexmaker can proxy public methods on concrete classes. Then I noticed that TextView#setText(int resId) is inexplicably final. The TextView class itself is non-final.

I think I could fork dexmaker to support final methods in non-final classes. Is this possible? I don't want to start this project if it isn't. It would be a huge win for my library though because developers would not need to have subclasses, interfaces, or manual static method calls for their views. My library needs to know when text is set on particular View. A proxy is the perfect design pattern for this.

jophde
  • 444
  • 1
  • 5
  • 13
  • 1
    Random shot (since it's not mentioned): A layout inflater allows to [set a factory class](http://developer.android.com/reference/android/view/LayoutInflater.html#setFactory2(android.view.LayoutInflater.Factory2)) that might be what you're after. This is used in [probe](https://github.com/lucasr/probe/) where the factory dynamically creates proxies using dexmaker to intercept calls. – Stefan Hanke Jan 17 '15 at 02:40
  • Part of my library actually uses the layout inflater factory technique. You are saying that you can set up a proxy there on the view objects that are created via dexmaker? – jophde Jan 17 '15 at 20:18
  • Thanks so much Stefan. The Probe source is exactly what I needed :). If I understood correctly Probe creates entirely new View classes so the dexmaker Proxybuild limitation of not being able to handle final methods isn't an issue? From dexmaker ProxyBuilder docs: "This process works only for classes with public and protected level of visibility." – jophde Jan 17 '15 at 23:02
  • This should have nothing to do with dexmaker. First, I'd go for creating wrapper classes around the respective View that just forwards all calls to the real View instance. Then, you're free to add calls to your liking. Dunno if this road is possible, and feels like being both effortful and brittle. – Stefan Hanke Jan 19 '15 at 06:41
  • I don't think wrapping will work because the object definitely still needs to cast correctly. I doubt TextView tv = (TextView) findViewById(R.id.textView) will work otherwise. The final method is really killer. Are there any options besides patching Android View classes to use interfaces? – jophde Jan 19 '15 at 19:50

2 Answers2

2

The intent of "final" is that the method cannot be overridden. That effectively puts a halt to proxying by extension. However, you can still proxy by wrapping, the way Spring handles it.

That is one reason why it is a best practice to separate the interface from the implementation.

In more concrete terms ...

// This snip is not supposed to be functional, only to demonstrate a concept
interface TextViewInterface {
    void setText (int resId);
}

class TextView implements TextViewInterface {
    public final void setText (int resId) {
    ... snip ...
    }
}

class Proxy$TextView implements TextViewInterface 
extends View { // Added this for Android hierarchy
    private TextView textView;

    public void setText (int  resId) {
        textView.setText(resId);
    }
}

Does this help?

pojo-guy
  • 966
  • 1
  • 12
  • 39
  • This would ordinarily work but the objects need to go into the Android View Tree Hierarchy which only accepts subclasses of View or ViewGroup and I am sure instance of is used in places. If I extend from TextView will the interface method interfere with the class method? – jophde Jan 17 '15 at 01:00
  • The interface mechanism allows you to wrap the class method. You could extend View (call it MyTextViewProxy) and wrap TextView, provided anything extending View is acceptable. I updated the example above to show the extends View. – pojo-guy Jan 17 '15 at 02:44
  • You're running into why certain things are considered best practices in java. Sorry I can't help you more here. – pojo-guy Jan 19 '15 at 13:26
2

As far as I know, this is not possible on Android.

Dexmaker creates dex files that contain new classes. These classes are then added to an application by using dex class loaders. Such dex files can however not be used to replace classes, only to add new subclasses that serve as a proxy.

In this sense, dexmaker is rather like cglib than javassist.

Note that Android does neither provide similar instrumentation capabilities as a regular Jvm where you can instrument final classes and methods by class redefinition via an agent. This is not provided by Android: http://developer.android.com/reference/android/app/Instrumentation.html

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192