如何防止客户端在 Android 库中看到内部私有 类?

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

我有一个包含多个包的库-

让我们说
套餐一;
包 b;

内包a我有publica_class
在包 b 我有 public b_class
a_class 使用 b_class.

我需要从中生成一个库,但我不想让客户端看到 b_class。

我知道的唯一解决方案是将我精美易懂的包扁平化为单个包,并使用 b_class 的默认包访问权限。 还有另一种方法吗?也许使用接口或某种形式的设计模式??

如果我没理解错的话,您是在询问是否要在不公开部分来源的情况下发布您的库供第 3 方使用?如果是这种情况,您可以使用 proguard,这会混淆您的库。默认情况下,所有内容都是 excluded/obfuscated,除非您指定要排除的内容 obfuscated/excluded。

如果您想在客户端根本无法访问代码的情况下分发[部分]代码,这意味着客户端也将无法执行它。 :-O

因此,您只有一个选择:将代码的敏感部分放入 public 服务器 并分发代理来访问它,以便您的代码将保留并执行到您的服务器中,客户端仍然可以通过代理执行它,但无需直接访问它。

您可以使用 servlet、web 服务、RMI 对象或简单的 TCP 服务器,具体取决于代码的复杂程度。

这是我能想到的最安全的方法,但它也值得付出代价:除了使您的系统复杂化之外,它还会为每个远程引入 网络延迟操作,这可能很重要,具体取决于性能要求。此外,您应该确保服务器本身的安全,以避免黑客入侵。如果您已经拥有可以利用的服务器,这可能是一个很好的解决方案。

如果你拒绝将代码移动到一个单独的、受控的服务器上,你所能做的就是在尝试使用你的 API 时 阻碍客户端程序员 .让我们开始将好的实践应用到您的设计中:

  1. 让您的包裹保持现在的样子。
  2. 每 class 你想要 "hide":

    • 设为非public。
    • 将其 public API 提取到新的 public 界面:

    public interface MyInterface {...}

    • 创建一个 public 工厂 class 以获取该接口类型的对象。

    public class MyFactory { public MyInterface createObject(); }

到目前为止,您的包已经松散耦合,并且实现 classes 现在是私有的(正如您已经说过的良好做法所宣扬的那样)。尽管如此,它们仍然可以通过接口和工厂获得。

那么,如何避免 "stranger" 客户端执行您的私人 APIs?接下来是一个创造性的、有点复杂但有效的解决方案,基于 阻碍 客户端程序员:

修改您的工厂classes:向每个工厂方法添加一个新参数:

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

那么,什么是Macguffin?这是您必须在应用程序中定义的新接口,至少有一个方法:

public interface Macguffin
{
    public String dummyMethod();
}

但是不提供此接口的任何可用实现。在你的代码的每个地方你需要提供一个 Macguffin 对象,通过 anonymous class:

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

或者,甚至更高级,通过一个动态代理对象,所以即使客户端程序员敢,也不会找到此实现的“.class”文件反编译代码。

你从中得到了什么?基本上是 劝阻 程序员不要使用需要未知、未记录、无法理解的对象的工厂。工厂 classes 应该只关心不要接收空对象,并调用虚拟方法并检查 return 值它也不为空(或者,如果你想要更高的安全级别,添加未记录的秘密密钥规则)。

所以此解决方案依赖于您的 API 的 微妙混淆 ,以阻止客户端程序员直接使用它。 Macguffin接口及其方法的名字越隐晦越好。

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 ?

是的,使 b_class 包私有(默认访问)并通过反射实例化它以便在 a_class 中使用。

既然你知道完整的 class 名称,反射加载 class:

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

找到您要调用的构造函数:

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

通过使其可访问来允许自己调用构造函数:

con.setAccessible(true);

调用构造函数来获取您的 b_class 实例:

Object o = con.newInstance();

万岁,现在你有了 b_class 的实例。但是,您不能在 Object 的实例上调用 b_class 的方法,因此您有两个选择:

  1. 使用反射来调用 b_class 的方法(不是很有趣,但足够简单,如果你只有几个参数很少的方法可能没问题)。
  2. b_class 实现一个您不介意客户端看到的接口,并将 b_class 的实例投射到该接口(从字里行间我怀疑您可能已经有了这样的接口?)。

你肯定会想要选择选项 2 来减少你的痛苦,除非它让你再次回到原点(用你不想向客户端公开的类型污染命名空间)。

为了完整披露,请注意两点:

1) 使用反射与直接实例化和调用相比有一个(小的)开销。如果你投射到一个接口,你只需支付实例化的反射成本。在任何情况下,除非您在紧密循环中进行数十万次调用,否则这可能不是问题。

2) 没有什么可以阻止坚定的客户找出 class 的名字并做同样的事情,但如果我正确理解你的动机,你只是想公开一个干净的 API,所以这不是真正的担心。

使用 Kotlin 时,您可以为您的库使用 internal 修饰符 类。