未充分利用的 Android 库的全面指南
已发表: 2022-03-11任何有经验的开发人员都会告诉您,他们最好的代码不是他们编写的代码。 这是他们从别人的工作中获取的代码。
是的,我们的开发人员是创新的问题解决者,但我们遇到的许多问题已经得到解决——并且补救措施打包到任何人都可以使用的库中。 当免费的轮子无处不在时,为什么要重新发明轮子?
安卓也不例外。 代码重用的最终来源是 Android SDK 本身,它带有出色的构造和服务,可以为您完成大量工作。
但是,在 SDK 不足的地方,Android 社区创建了一些顶级库,可以为您节省大量的编码工作,并用经过高度调整、审查和测试的实现取而代之。 我不是在谈论显而易见的库——Android 支持库、Android 设计支持库、Gson。 我指的是你可能不知道的工具。 即使你这样做了,你可能还没有使用它们。
多年来,我一直在开发、指导和领导 Android 团队,并且研究和使用了数十种外部工具和库。 (我什至知道阅读他们的实现代码并与开发人员讨论他们的内部结构。)许多人在帮助我完成工作方面非常有效,但事实是,大多数人都没有。
这就是我将本指南放在一起的原因。 依靠我的经验,以及其他移动开发人员的经验,以确保您使用的是最好的库。 我选了七个。 我怀疑它们很快也会成为你的最爱。
选择正确的 Android 库
在选择库时,我会寻找四个关键特性:
- 它为实际且重要的问题提供了一致且高质量的解决方案。
- 它使用尽可能简单的 API。
- 它不会强制对我的整体架构进行任何更改。
- 它拥有庞大的用户群,最好还有一个活跃的开发者社区。
前三个功能是交易破坏者。 如果它们不存在,我继续前进或开始手动编码。
我在下面介绍的库通过了所有四个测试。 它们还解决了移动开发中一些最具挑战性的方面。
- 两个用于依赖注入、布局到 Java 绑定、模拟对象的库。
- 应用内发布/订阅消息模型。
- 安全、高效、自恢复的 HTTP 通信层。
- 图像处理:下载、缓存、调整大小和加载到 RAM。
- 实时视频流。
- 内存泄漏检测。
ButterKnife:终极依赖注入工具
这是 Android 的终极依赖注入库。 简单、健壮、超快速(无反射!),并且能够消除您应用程序的大量样板代码。
不再需要通过调用findViewById()
来直接绑定每个视图; 相反,有一个带注释的视图让您可以直接访问代码。ButterKnife 还消除了对样板 UI 事件(例如onClick
、 onTouch
等)的需求,并将它们替换为自动注入的代码。
但是足够的闲聊,让我们看看代码。
视图字段绑定:
class MyButterKnifeActivity extends Activity { @BindView(R.id.name) TextView name; @BindView(R.id.address) TextView address; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // MUST BE CALLED BEFORE ACCESSING UI FIELDS name.setText(“etc etc”); } }
资源绑定:
class ExampleActivity extends Activity { @BindString(R.string.username) String username; @BindDrawable(R.drawable.graphic) Drawable graphic; @BindColor(R.color.bg_color) int bgColor; @BindDimen(R.dimen.lower_padding) Float lowerPadding; // and no need for getResources().getString()/getDrawable/getColor() }
UI 事件绑定:
@OnClick(R.id.my_button) public void clickHandler(View view) { // onClick logic goes here }
AndroidAnnotations:将依赖注入提升到新的水平
在依赖注入方面,AndroidAnnotations 与 ButterKnife 紧随其后,它使用了一种稍微不同的方法:自动生成的类,一旦你掌握了它们,就会非常简单。 更有用的是它允许你“基于名称”的依赖注入。 例如@ViewById ListView myUserList;
指示库为该字段分配同名的layoutListView
。
AndroidAnnotations 也非常快,但它以与 ButterKnife 不同的方式实现了这一点。 AndroidAnnotations 不是运行时绑定依赖注入,而是创建所有受影响活动的构建时间副本并将其连接逻辑推送到其中,从而使您可以获得与手动编码逻辑相同的性能。
但 AndroidAnnotations 的注入功能比这更进一步。 您可以将状态和布局注入到活动中。
AndroidAnnotations 实现:
@NoTitle @Fullscreen @EActivity(R.layout.my_layout) public class MyActivity extends Activity { @ViewById ListView customerList; // auto-binded to R.id.customerList @App MyApplication app; // auto-binded to app object @AminationRes Animation fadeoutAnimation; @UiThread void updateUI() { // main thread action } }
最后一个注释需要更多解释:多线程 Android 应用程序的一个常见任务是从后台(或工作)线程切换到前向(或主或 UI)线程,这是唯一允许访问 UI 组件的线程. 这项任务虽然并不复杂,但通常是必需的,并且涉及一些混乱的编码:
new Handler(Looper.getMainLooper()).post(new Runnable() { logic goes here } ); // NO ANNOTATIONS
在 AndroidAnnotations 中,您需要做的就是使用 @UiThread 注释您的函数,并且现在保证始终执行:
@UiThread void updateUI() {..} // WITH ANNOTATIONS
请注意,此注释适用于标准 Android 组件类(活动、服务等)。 但是当我也想注释自己的类时会发生什么?
在这里,AndroidAnnotations 提出了一个新概念,即EBean
。 您所要做的就是使用@EBean
标记您的类,然后您就可以开始了:
@EBean public class MyNonComponentClass { @SystemService NotificationManager notifManager; @Bean MyOtherClass dependency; @UiThread void updateUI() { // main thread work goes here } }
EventBus:轻松实现跨组件通信
EventBus 库将困扰 Android 开发人员多年的问题变成了公园散步。 跨组件通信从未如此简单——使用简单的发布/订阅模型在系统的任何两个部分之间进行通信。
您的后台轮询服务不再需要知道您的片段来为它们提供更改事件。
EventBus 的使用很简单。
一种。 创建事件类。 在这里使用 POJO 是最好的:
class NewUserEvent { String fullname; String address; String role; // add getters and setters }
湾。 在类中创建事件处理方法——您希望订阅这些事件的任何类:
class MySubscriber { @Subscribe public void newUserHandler(NewUserEvent event) { // handle NewUserEvent } @Subscribe public void newUserHandler(AnotherEvent event) { // handle AnotherEvent } }
但是,嘿,任何一个经验不足的 Android 开发人员都会停下来问:这些处理程序的线程模型是什么? 如果处理程序涉及 UI 组件访问,我可以强制处理程序离开主线程吗? 好问题…
默认情况下,所有处理程序方法都在工作线程上运行,该工作线程取自由 EventBus 本身分配和维护的线程池。 如果您需要在主线程上运行处理程序方法,请按如下方式扩展您的订阅注释:
@Subscribe(threadMode = ThreadMode.MAIN) public void runOnMainThreadHandler(AnotherEvent event) { … }
警告:不要过度使用此功能! 永远不要在主线程上执行长时间运行的操作,即使是快速操作,也要小心。 压倒主线程是让你的应用程序缓慢、跳跃并且对用户来说基本上不那么有趣的最可靠方法。
C。 管理订阅者类的 EventBus 注册生命周期——即它何时连接以及何时断开与总线的连接? 活动的合理注册流程是:
class MySubscriberActivity extends Activity { @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); // START RECEIVING EVENTS HERE } @Override public void onStop() { EventBus.getDefault().unregister(this); // NO MORE EVENTS super.onStop(); } }
当然,以上只是一个例子。 您可以在您选择的任何地方执行(取消)注册。
d。 最后,实际触发一个事件:
EventBus.getDefault().post(new MyEvent(“I'm here”));
有关使用 EventBus 的更多信息:多播事件(默认行为)、使用粘性事件、传递线程、优先级等等。 但以上内容足以让您开始使用这种简单但功能强大的技术。
OkHttp:Android 的 HttpClient on Steroids
Android 的 HttpClient 应该是这样写的。 非常简单,非常聪明。 OkHttp 库在内部负责重试循环、有效负载自动压缩、Http/2 支持、连接池和响应缓存,因此您可以避免不必要的网络访问。
OkHttp 的使用很简单。
发帖:
OkHttpClient client = new OkHttpClient(); MediaType JSON = MediaType.parse("application/json; charset=utf-8"); RequestBody body = RequestBody.create(JSON, json_str); Request request = new Request.Builder() .url(url) .post(body) .build(); Response response = client.newCall(request).execute(); return response.body().string();
Http 获取:
OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(urls[0]) .build(); Response responses = client.newCall(request).execute(); String jsonData = responses.body().string();
OkHttp 还支持异步联网、请求重定向路由查询、本地缓存查询等实用功能。 随意在需要的地方使用它们。 大多数开发人员使用 OkHttp 作为 Android 默认 HTTP 客户端 HttpURLConnection 的更智能替代品。 事实上,整个项目都是作为 HttpURLConnection 的私有分支开始的。
我喜欢它的健壮性——它立即添加到您的网络层。
毕加索:谷歌也使用它是有充分理由的!
Picasso 是管理图像下载、缓存、调整大小和裁剪的最简单、最可靠的方法。

这个说法:
Picasso.with(context).load(url).resize(50,50).centerCrop().into(imageView)
将为您执行此操作:
- 连接到远程 URL。
- 下载图像。
- 将其存储在本地 LRU 缓存中,它也会为您管理。
- 在将原始图像加载到内存之前调整其大小。
- 在 Picasso 管理的线程池上运行上述所有操作。
- 使用调整大小的图像来填充您的 imageView。
- 在任何未来运行之前,请检查本地缓存以确保确实需要网络往返。
构建上述任务集需要很多小时的工作,即使对于高级开发人员也是如此。 这假设你记住了一切。 如果您忘记了调整大小部分怎么办?
好吧,在一般的 Android 设备上,一个应用程序的 RAM 不超过 50 到 60 兆字节,大多数 Android 设备的像素到字节因子是 4。这意味着尝试从 SD 卡加载 13 兆像素的图像将需要 52 兆字节的 RAM。 换句话说,您的应用程序会立即崩溃。
这只是毕加索实力的一个例子。 在优化/调试媒体密集型遗留项目时,我做的第一件事就是将所有图像加载切换到 Picasso。 您会惊讶于这一简单步骤对应用程序质量的影响。
该库的强大功能的最有力证明之一:过去两年 Google 自己的许多 Android 代码示例都使用 Picasso 进行图像加载。
ActiveAndroid:ORM 无性能开销
ORM 是对象关系映射的缩写,在 J2EE 时代很流行。 它允许您将 POJO 存储在数据库中并从数据库中检索它们,而无需将它们转换为单独的字段。
有帮助吗? 非常重要,因为它允许您编写大部分应用程序而无需编写任何 SQL 语句。
它也非常有效。 在过去,ORM 平台大量依赖反射,并且因速度慢而臭名昭著。 现代平台,包括 ActiveAndroid,速度要快得多,并且对于大多数实际需求,不会因原始 SQL 编码而受到性能开销的影响。
用法:
一种。 通过扩展自定义应用程序类在应用程序对象中初始化:
public class MyApplication extends extends com.activeandroid.app.Application { … }
湾。 创建为模型类派生的 POJO,其中包含您计划存储在数据库中的每条记录的类。 每个这样的 POJO 都可以驻留在自己的表中。 应该使用注释来为每个存储的成员指定数据库字段的名称:
@Table(name = "Categories") public class UserDetails extends Model { @Column(name = "Name") public String name; @Column(name = "Address") public String address; @Column(name = "Age") public int age; }
如果您希望为成员设置索引,请使用以下注释:
@Column(name = "ID", index = true) public String userID;
C。 为了防止库迭代所有最经典的启动时间(这是默认行为),强烈建议您在以下清单部分中指定所有模型类:
<meta-data android:name="AA_MODELS" android:value=“com.myapp.MyModelA, com.myapp.MyModelB" />
注意:未出现在此列表中的模型类将不会被 ActiveAndroid 识别。
d。 写入数据库:
UserDetails usr = new UserDetails(); usr.save(); // RUNS ON A BACKGROUND THREAD
如果需要多次写入,更有效的方法是在单个事务中对它们进行批处理:
ActiveAndroid.beginTransaction(); try { for (UserDetails u: userList) item.save(); ActiveAndroid.setTransactionSuccessful(); } finally { ActiveAndroid.endTransaction(); }
e. 从数据库中读取 POJO:
new Select() .from(UserDetails.class) .where("name = ?", usr.getName()) .orderBy("Age") .executeSingle();
在我作为服务器端开发人员的日子里,ORM 是必备工具。 它进入 Android 领域的时间有点晚。 但是,最后,它是:数据库编程尽可能简单。 好好享受。
LibStreaming:无痛视频流
由于未记录的 API、跨 SDK 版本差异、反射使用等,实时视频流曾经是一个主要难题。
幸运的是,libStreaming 改变了这一切,它封装了大部分复杂的流式传输,并公开了一个简单友好的 API,允许您在几个小时内编写一个基本的流式传输应用程序。
要将其用于 H.264 和 AAC,您需要执行以下操作:
一种。 在您的主要活动的 onCreate 方法中初始化一个会话对象。 会话对象表示到对等点的媒体流:
protected void onCreate(Bundle savedInstanceState) { mSession = SessionBuilder.getInstance() .setCallback(this) .setSurfaceView(mSurfaceView) .setPreviewOrientation(90) .setContext(getApplicationContext()) .setAudioEncoder(SessionBuilder.AUDIO_NONE) .setAudioQuality(new AudioQuality(16000, 32000)) .setVideoEncoder(SessionBuilder.VIDEO_H264) .setVideoQuality(new VideoQuality(320,240,20,500000)) .build(); mSurfaceView.getHolder().addCallback(this); }
湾。 实际开始会话:
mSession.setDestination(destination_server_url); mSession.start();
C。 完成后暂停会话:
mSession.stop();
现在,请不要误会。 实时流本质上是混乱的,libStreaming 并没有消除这种复杂性。 但是,在大多数情况下,它确实可以很好地隐藏它。 在某些情况下,您需要处理复杂性,例如在选择对等信号策略、选择相机编码(您通常希望使用 MediaCodec/surface-to-buffer)或处理分组时。
不过,您会发现 libStreaming 背后的好人付出了更多努力,将这些复杂性平滑地合并到一个简单易用的 API 中。
LibStreaming 支持 Android 应用程序使用的大多数编码器,包括 H.264、H.263、AAC 和 AMR。
我用这个库获得了很好的结果。 一些最受欢迎的流媒体应用程序将其用作其基础架构的一部分。 如果您遇到需要,我相信它会让您的媒体流体验更加流畅。
LeakCanary:检测一行代码中的内存泄漏
让我们从这个库背后的动机开始:内存泄漏。 Android 应用程序很容易受到它们的影响,尤其是在您不小心编码的情况下。 实际上,创建内存泄漏非常简单。 您需要做的就是将活动引用存储在其上下文之外。 事实上,即使在其活动的上下文之外存储对单个视图对象的引用也会产生泄漏。
为什么? 因为视图(实际上是所有视图)在内部存储对其包含活动的上下文引用。 只要保留对视图的引用,垃圾收集器就无法回收其包含的活动以及其中的内容,包括可绘制对象、视图层次结构和资源。
保持对泄漏活动的引用作为静态参数并不总是显而易见的。 每当您在活动中创建内部类或生成线程时,都会创建对该活动的引用,并且在该内部类或线程完成之前,该活动可能不会被回收。
泄漏对单个资源密集型活动的引用有时足以使您的应用程序因“内存不足”异常而崩溃。
你怎么能防止他们? 当然,从严格的编码实践开始。 但并非所有人都是经验丰富的 Android 开发人员,即使是经验丰富的开发人员有时也会忘记规则。
强调内存泄漏的定期代码审查可能会有所帮助,但它们需要时间。 此外,一些泄漏是真正偷偷摸摸的,仅通过代码审查很难检测到。
随着时间的推移,使用 DDMS 的内存工具是了解您的应用程序是否泄漏的好方法。 你绝对应该使用它。 但是,它不会告诉您导致泄漏的原因。
来了leakCanary来救援。 它是目前最好的内存泄漏检测器,它为您的所有活动提供自动泄漏检测——就像在一两行代码中一样。
要使用它,只需使用您的应用程序对象onCreate()
初始化 leakCanary :
public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); LeakCanary.install(this); // more initialisations } }
你完成了。 LeakCanary 将监视内存泄漏,并在检测到内存泄漏时发送通知。
LeakCanary 通过在所有活动中自动注入一个名为ActivityRefWatcher
的对象并在调用onDestroy()
后监控它们的引用计数来实现这一魔力。 已破坏活动的引用计数 > 0 只能表示泄漏。
重要提示:泄漏检测仅适用于调试模式应用程序。 永远不要在发布模式 APK 中测试泄漏(好吧,不是用 LeakCanary)。
但是,如果我想测试系统的其他部分是否有泄漏怎么办? 在这里,LeakCanary 提供了一个名为 refWatcher 的对象,它实际上是初始化调用的返回值:
refWatcher = LeakCanary.install(this);
它可以用来观察即将被回收的值。 更准确地说,我认为很快就会恢复的价值观。 为此,请致电:
refWatcher.watch(my_soon_to_be_reclaimed_obj);
如果这个对象在 watch 调用后的短时间内没有被释放,库会通知你。
我在任何地方都找不到这个“短时间”的价值,但它可能并不那么重要。 使用leakCanary,一切正常。 无价。
概括
经验丰富的开发人员使用这些库将他们的编码和调试阶段缩短了数天甚至数周,因此您没有理由不能这样做。
总而言之,这是我选择的 Android 库可以为您做的事情:
ButterKnife – 自动注入代码将帮助您消除应用程序的大部分样板代码。 这是 Android 的终极代码注入。 需要我多说?
AndroidAnnotations – 使用超快的自动生成类和基于名称的代码注入来节省时间,而不会影响手动编码逻辑的性能。
EventBus – 解耦组件以获得更健壮的代码,跨组件通信从未如此简单。
OkHttp – HttpURLConnection 的巧妙替代品,支持异步网络、请求重定向路由查询、本地缓存查询等。
Picasso – 简化的图像处理,非常好,现在被 Google 使用。 在媒体繁重的项目和某些遗留项目中,它可以节省大量时间。
ActiveAndroid – ORM 变得简单,没有性能开销。
LibStreaming – 主要流媒体应用程序使用的实时视频流。
这些是唯一值得您花时间的 Android 库吗? 当然不是。 但我向你保证:在你的下一个项目中使用它们中的任何一个都会让你成为一个更好的开发人员。 如果您想查看它们的实际效果,请查看我的 GitHub。
如果您已经在使用其中的部分或全部,或者您正在使用替代库,我敦促您在下面的评论中分享您的经验。