How to prevent client from seeing internal private classes in Android library ?

2022-09-01 23:40:52

I have a library with several packages-

lets say
package a;
package b;

inside package a I have public a_class
inside package b I have public b_class
a_class uses b_class.

I need to generate a library from this , but I do not want the Client to see b_class.

The only solution I know of is to flatten my beautifully understandable packages to single package and to use default package access for b_class. Is there another way to do so ? maybe using interfaces or some form of design pattern ??


答案 1

If you reject to move the code to an individual, controlled server, all you can do is to hinder the client programmer when trying to use your APIs. Let's begin applying good practices to your design:

  1. Let your packages organized as they are now.
  2. For every class you want to "hide":

    • Make it non-public.
    • Extract its public API to a new, public interface:

    public interface MyInterface {...}

    • Create a public factory class to get an object of that interface type.

    public class MyFactory { public MyInterface createObject(); }

So far, you have now your packages loosely coupled, and the implementation classes are now private (as good practices preach, and you already said). Still, they are yet available through the interfaces and factories.

So, how can you avoid that "stranger" clients execute your private APIs? What comes next is a creative, a little complicated, yet valid solution, based on hindering the client programmers:

Modify your factory classes: Add to every factory method a new parameter:

public class MyFactory
{
    public MyInterface createObject(Macguffin parameter);
}

So, what is ? It is a new interface you must define in your application, with at least one method:Macguffin

public interface Macguffin
{
    public String dummyMethod();
}

But do not provide any usable implementation of this interface. In every place of your code you need to provide a object, create it through an anonymous class:Macguffin

MyFactory.getObject(new Macguffin(){
    public String dummyMethod(){
        return "x";
    }
});

Or, even more advanced, through a dynamic proxy object, so no ".class" file of this implementation would be found even if the client programmer dares to decompile the code.

What do you get from this? Basically is to dissuade the programmer from using a factory which requires an unknown, undocumented, ununderstandable object. The factory classes should just care not to receive a null object, and to invoke the dummy method and check the return value it is not null either (or, if you want a higher security level, add an undocumented secret-key-rule).

So this solution relies upon a subtle obfuscation of your API, to discourage the client programmer to use it directly. The more obscure the names of the Macguffin interface and its methods, the better.


答案 2

I need to generate a library from this , but I do not want the Client to see b_class. The only solution I know of is to flatten my beautifully understandable packages to single package and to use default package access for b_class. Is there another way to do so ?

Yes, make package-private (default access) and instantiate it via reflection for use in . b_classa_class

Since you know the full class name, reflectively load the class:

Class<?> clz = Class.forName("b.b_class")

Find the constructor you want to invoke:

Constructor<?> con = clz.getDeclaredConstructor();

Allow yourself to invoke the constructor by making it accessible:

con.setAccessible(true);

Invoke the constructor to obtain your instance:b_class

Object o = con.newInstance();

Hurrah, now you have an instance of . However, you can't call 's methods on an instance of , so you have two options:b_classb_classObject

  1. Use reflection to invoke 's methods (not much fun, but easy enough and may be ok if you only have a few methods with few parameters).b_class
  2. Have implement an interface that you don't mind the client seeing and cast your instance of to that interface (reading between the lines I suspect you may already have such an interface?). b_classb_class

You'll definitely want to go with option 2 to minimise your pain unless it gets you back to square one again (polluting the namespace with types you don't want to expose the client to).

For full disclosure, two notes:

1) There is a (small) overhead to using reflection vs direct instantiation and invocation. If you cast to an interface you'll only pay the cost of reflection on the instantiation. In any case it likely isn't a problem unless you make hundreds of thousands of invocations in a tight loop.

2) There is nothing to stop a determined client from finding out the class name and doing the same thing, but if I understand your motivation correctly you just want expose a clean API, so this isn't really a worry.


推荐