【问题标题】:How to prevent client from seeing internal private classes in Android library ?如何防止客户端在 Android 库中看到内部私有类?
【发布时间】:2015-03-24 16:24:06
【问题描述】:

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

让我们说
包一个;
包b;

在包 a 里面我有 public a_class
在包 b 我有公共 b_class
a_class 使用 b_class。

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

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

【问题讨论】:

  • 这些包/类之间有什么关系吗?

标签: java android jar android-library


【解决方案1】:

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

  1. 让您的包裹按现在的方式组织起来。
  2. 对于每个你想“隐藏”的类:

    • 不公开。
    • 将其公共 API 提取到新的公共接口:

    public interface MyInterface {...}

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

    public class MyFactory { public MyInterface createObject(); }

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

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

修改你的工厂类:为每个工厂方法添加一个新参数:

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

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

public interface Macguffin
{
    public String dummyMethod();
}

不提供此接口的任何可用实现。在代码的每个地方,您都需要提供一个Macguffin 对象,通过匿名 类创建它:

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

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

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

因此,此解决方案依赖于对您的 API 进行微妙的混淆,以阻止客户端程序员直接使用它。 Macguffin 接口及其方法的名称越模糊越好。

【讨论】:

  • 这是一个巧妙的解决方案,虽然正如你提到的,感觉更像是在阻碍开发人员,我的目的是让他们更容易使用 API,意味着他们只能看到 API 的部分他们预计会使用,我想真正做到这一点的唯一方法是良好的文档,并拥有一个你告诉他们的工厂是起点,并且只包括他们需要使用的东西。如果他们确实使用其他方法,我不会太在意,它不会发生任何不好的事情,只是试图从应用程序的公共范围中消除混乱。感谢您的回答!
  • 如上述答案中所述 - 我认为动态代理可能是这种情况的更好解决方案。
  • 不要为麦格芬使用晦涩的名字!如果它是一个漂亮且可读的 API 中唯一奇怪的,它肯定会看起来很可疑。对于一些客户端程序员来说,这可能看起来像是“严格禁止入场!”——只会阻碍他们早睡的标志。请改用严肃的名称,例如“Caller”、“Instance”或“MyAppNameContext”。这样,程序员会简单地假设他遗漏了一些东西,并且您该死的文档不完整。不要让他幻想成为一个很酷的黑客。提醒他是个白痴。不用说:我喜欢这篇有创意的帖子 :-)
  • @Doe Johnson “不要让他幻想自己是个酷黑客。提醒他自己是个白痴。”你完全明白了,我的朋友。谢谢!
  • @Croc 我知道这确实对我没有好处,但老实说,我不得不承认 Stevie 的解决方案比我的要干净得多(除了 SecurityManager 可以阻止执行 setAccesible 的事实) .
【解决方案2】:

我需要从中生成一个库,但我不希望客户端看到 b_class。我知道的唯一解决方案是将我精美易懂的包扁平化为单个包,并对 b_class 使用默认包访问。还有其他方法吗?

是的,将b_class 设为包私有(默认访问)并通过反射将其实例化以在a_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) 没有什么可以阻止一个坚定的客户找出类名并做同样的事情,但如果我正确理解你的动机,你只想公开一个干净的 API,所以这不是真的担心。

【讨论】:

  • 感谢您的回答。虽然这确实解决了问题,但问题确实专门针对 android,并且反射对于 android 来说通常不是一个好主意,尤其是如果您使用 proguard。
  • 不用担心 :) ...你是对的,pro-guard 增加了一两个皱纹 - 你必须配置它不要混淆你想要的类的类 + 构造函数名称反射性地实例化/调用。
  • 只是想补充一点:我还没有看到令人信服的证据表明应该在 Android 中避免反射 - 检查这个问题stackoverflow.com/questions/7224318/… 中接受的答案(我的)......请注意 dsl4xml 库大量使用反射,但在真实设备上的紧密循环中表现良好(即使是旧设备 - 这个答案已有多年历史) - 大约 15% 的开销与 Gingerbread/Dalvik 上的非反射代码相比,事情可能甚至随着更新的 Android 和 ART 的改进。
  • 看起来 proguard + dexguard 可以处理开箱即用的反射内容proguard.sourceforge.net/FAQ.html#forname
【解决方案3】:

如果我理解正确,您是在询问是否要在不披露部分来源的情况下发布您的库以供第三方使用?如果是这种情况,您可以使用proguard,它可以混淆您的库。默认情况下,所有内容都将被排除/混淆,除非您指定要排除混淆/排除的内容。

【讨论】:

  • 感谢您的回答-但这不是解决方案。我正在使用 DexGuard - 所以源代码得到了很好的保护。我希望使用我的库的客户端根本看不到 b_class,在智能感知窗口中看不到方法名称和“b_class”。
【解决方案4】:

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

因此,您只有一个选择:将代码的合理部分放入 公共服务器 并分发代理来访问它,这样您的代码将被保存并在您的服务器中执行客户端仍然可以通过代理执行它,但无需直接访问它。

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

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

【讨论】:

  • 这不是关于人们能够看到“代码”,而是关于人们能够访问这些方法,我很高兴他们拥有源代码,但是我没有私有方法不介意他们看到,但良好的编程习惯说,为了松散耦合库,我应该限制入口点。只要他们知道私有方法是私有的,我很高兴他们阅读私有方法,因此他们不必担心它们,只需要使用少数接口即可。有没有办法在不扁平化包结构的情况下做到这一点?
  • @Ben 我明白了,但我的回答仍然有效。如果您希望客户端不访问您的“私有”代码而不将您的包奉承为具有公共和私有 API 的单个包......您可以将您的代码完全隐藏到远程服务器中。然后,如果您愿意,可以使用开放代码发布您的 javadocs。
【解决方案5】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-06
    • 1970-01-01
    • 1970-01-01
    • 2013-07-30
    相关资源
    最近更新 更多