使用 AndroidWithoutStupid Java 库减轻 Android 编程的痛苦






4.79/5 (30投票s)
一句话方法,
引言
作为一个大部分编码工作都在 Visual Studio 中完成的人,我发现 Android Java 要求我编写大量本应是内部管道代码的东西,这很奇怪。于是,我开始将一行快捷方法放入 JAR 文件中。这个 JAR 文件随着时间的推移不断增长,我决定将其作为免费公共领域软件库在 GitHub 上发布。我最初将其命名为 MvUtils,但那是个愚蠢的决定。后来我将其重命名为 AndroidWithoutStupid
,因为这让所有人对其目的都一目了然。
显示“Hello World”消息
这是在 Android 中显示“
Hello World
”消息的常规“toast”版本。
Toast.makeText(
getApplicationContext(),
"Hello, World!",
Toast.LENGTH_SHORT).show();
而这是 AndroidWithoutStupid
版本
oink.showMessage("Hello, World!");
看到了吗,多么简洁?在大多数语言中,甚至在普通的 Java 中,你都是这样打印消息的。
好的。oink 是什么?嗯,它是 AndroidWithoutStupid
中 MvMessages
类的一个实例。与库中的其他类一样,你必须使用应用程序上下文初始化 MvMessages
的一个实例。因此,我用我当前的活动实例创建了 oink,它安全地转换为应用程序上下文。
MvMessages oink = new MvMessages(MyActivity.this);
此库中的许多类也包含 static
方法,你可以无需创建实例即可调用它们。
那么,MvMessages
还能做什么?显示提示和通知。
显示“是或否”提示
要显示这种提示,再次使用你的“oink”实例并调用其 showPrompt()
方法。这将返回一个 MvMessages.DialogInterfaceCaller
实例,你可以在其上加载你的“是”点击(onOkay)和“否”点击(onNotOkay
)监听器例程。
DialogInterfaceCaller oPrompt =
oink.showPrompt("File Download", "Do you want to download this file?");
oPrompt.onOkay =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
oink.showMessage("Starting download...");
// Other steps
}
};
oPrompt.show();
在状态栏中显示通知
通知工作量很大。有了 AndroidWithoutStupid
,一切都变得简单。由于显示通知需要一个图标文件,我在这里使用了启动器图标,但你应该使用一个更小更合适的图标。
oink.showNotification(
"New Message",
"5 unread e-mail messages ",
"Check mail",
R.drawable.ic_launcher);
MvMessages
有这个方法的多个重载,你可以用它们更新通知,并设置一个文件或一个应用程序在用户按下通知时启动。
显示列表菜单
要显示一个简单的选择菜单,你需要使用 MvMessages.showMenu()
方法的众多重载之一。像往常一样,代码非常简单。
final String[] saOptions = { "Red", "Blue", "Green"};
oink.showOptionsMenu(
"Select a color",
saOptions,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
oink.showMessage("You selected option " + (which+1) + " - " + saOptions[which]);
}
});
调用此方法会立即显示菜单。此方法有一个带 bAutoShow
布尔参数的重载,你可以将其设置为 false
以防止自动显示。该方法返回一个 AlertDialog.Builder
实例。你可以调用其 show()
方法来显示菜单。
交互式方法已经足够了。让我们尝试一些幕后操作。
从网上下载文件
如你所知,在你的 UI 线程中执行网络相关或其他长时间运行的任务会导致 Android 杀死你的应用程序。因此,文件下载必须异步进行。为此,AndroidWithoutStupid
有一个名为 MvAsyncDownload
的类。当你创建一个实例时,它会立即开始下载文件。MvAsyncDownload
扩展了 AsyncTask
类,你需要通过重写 AsyncTask
实现方法来跟踪下载。
MvAsyncDownload dl =
new MvAsyncDownload(
"http://www.example.com/",
"/sdcard/index.html") {
@Override
protected void onPostExecute(MvException result) {
if (result.mbSuccess) {
oink.showMessage("Downloaded");
} else {
oink.showMessage("Failed " + result.msProblem );
}
super.onPostExecute(result);
}
};
在此代码片段中,我们尝试下载 example.com 的主页(不是整个站点,放轻松)并将其保存到 SD 卡(“外部存储”)上的 HTML 文件中。[这需要你将 Internet 和存储写入权限添加到你的应用程序的清单文件中。没有它们,代码将无法工作。] 注意 onPostExecute
方法如何使用 MvException
参数。此异常类被库用作许多方法的返回值。这与不将异常带到太远处的既定智慧相悖,但我添加它是因为它能提供历史信息(*cough* getMessage()
)。另外,请注意,为了简单起见,我已将 HTML 文件的保存目标路径硬编码。这是不可取的。在这样做之前,你应该始终查询外部存储。
if (MvFileIO.getExternalStoragePath().mbSuccess) {
MvAsyncDownload dl =
new MvAsyncDownload(
"http://www.example.com/",
MvFileIO.getExternalStoragePath().moResult.toString() + "/index.html") {
@Override
protected void onPostExecute(MvException result) {
if (result.mbSuccess) {
oink.showMessage("Downloaded");
} else {
oink.showMessage("Failed " + result.msProblem );
}
super.onPostExecute(result);
}
};
}
getExternalStoragePath()
方法有点过时,因为在较新版本的 Android 中,内置支持识别多个存储设备,而不仅仅是第一个。
MvAsyncDownload
还有另一个构造函数重载,它会根据 MIME 类型或 Web 服务器发送的文件名标头猜测文件名。这意味着你无需在下载任何内容之前始终手头有目标文件名。
在服务中,与活动不同,文件下载可以或需要按顺序进行。异步任务可能是一个错误的选择。对于这种情况,你可以使用静态 MvGeneral.startSyncDownload()
方法。
MvGeneral
有几个这样的方法——将文本复制到剪贴板,通过包名启动应用程序,播放音频资源等。 同样,MvFileIO
类有检查外部存储、复制文件、创建目录、删除目录、检查文件或目录是否存在、将文本保存到文件等方法。
解析 RSS Feed
我创建的一个应用程序是网页浏览器,我希望将 RSS/ATOM feed 阅读器集成到其中。RSS 和 ATOM feed 是 XML 文件,但是推荐的用于解析 XML 文件的类让我头晕目眩。我不得不编写自己的 feed 解析器。而且,尽管 RSS/ATOM feed 是 XML 文件,但大多数网站生成的质量都很差。大多数 feed 使用来自多个规范的标签。许多使用自定义标签。你需要一个极其强大的解析器来处理它们。如果你严格遵守规范,你将不得不拒绝许多 feed 作为无效 feed,你的用户会不喜欢这样。
要从 RSS、ATOM 或 RDF feed 加载数据,请使用包含已下载 feed 的文件创建 MvNewsFeed
类的实例。为了确保解析后的 feed 完全自包含,请将 feed 的原始 URL 传递给构造函数。
if (MvFileIO.getExternalStoragePath().mbSuccess) {
final String sFeedUrl =
"https://codeproject.org.cn/WebServices/ArticleRSS.aspx";
final String sFeedFilePath =
MvFileIO.getExternalStoragePath().moResult.toString() + "/codeproject_rss.xml";
MvAsyncDownload dl =
new MvAsyncDownload(sFeedUrl, sFeedFilePath) {
@Override
protected void onPostExecute(MvException result) {
if (result.mbSuccess) {
MvNewsFeed oFeed = new MvNewsFeed(sFeedFilePath, sFeedUrl);
if (oFeed.moMessages.size() > 0) {
oink.showMessage(
"The feed \"" + oFeed.msFeedTitle + "\" contains " +
oFeed.moMessages.size() + " articles.");
} else {
oink.showMessage("Not a valid feed");
}
} else {
oink.showMessage("Failed " + result.msProblem );
}
super.onPostExecute(result);
}
};
}
在 MvNewFeed
实例加载 feed 内容后,它们可以直接从 Java 访问。例如,feed 标题在 oFeed.msFeedTitle
字段中可用。feed 中的文章在 oFeed.moMessages
数组列表中可用。此数组列表包含 MvNewsFeed.MvNewsFeedMessage
类的实例。MvNewsFeedMessage
实例包含文章标题、URL、播客链接等。
整合
现在,我们将结合其中几个类,为 CodeProject 创建一个 RSS feed 阅读器。
if (MvFileIO.getExternalStoragePath().mbSuccess) {
final String sFeedUrl =
"https://codeproject.org.cn/WebServices/ArticleRSS.aspx";
final String sFeedFilePath =
MvFileIO.getExternalStoragePath().moResult.toString() + "/codeproject_rss.xml";
// Download the feed to a file
MvAsyncDownload dl =
new MvAsyncDownload(sFeedUrl, sFeedFilePath) {
@Override
protected void onPostExecute(MvException result) {
if (result.mbSuccess) {
// After downloading, parse the feed file
MvNewsFeed oFeed = new MvNewsFeed(sFeedFilePath, sFeedUrl);
if (oFeed.moMessages.size() > 0) {
// Show how many articles are available in the feed - better not
oink.showMessage("The feed \"" + oFeed.msFeedTitle + "\" contains " +
oFeed.moMessages.size() + " articles.");
// Load the article titles and URLs in array lists
ArrayList<String> oArticleTitlesList = new ArrayList<String>();
final ArrayList<String> oArticleUrlList = new ArrayList<String>();
for (int i =0; i < oFeed.moMessages.size(); i++) {
oArticleTitlesList.add(oFeed.moMessages.get(i).msMessageTitle);
oArticleUrlList.add(oFeed.moMessages.get(i).msMessageLink);
}
// Create an intent for launching the link of the
// article selected by end-user
final Intent oUrlLaunchIntent = new Intent(Intent.ACTION_VIEW);
// Show the article titles in a menu
oink.showOptionsMenu(
"CodeProject RSS Feed",
oArticleTitlesList,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Launch the URL in a browser
oUrlLaunchIntent.setData(Uri.parse(oArticleUrlList.get(which)));
try {
startActivity(oUrlLaunchIntent);
} catch (Exception e) {
oink.showMessage(
"An error occurred when showing this article. -" +
e.getMessage());
}
}
}
);
} else {
oink.showMessage("Not a valid feed");
}
} else {
oink.showMessage("Failed " + result.msProblem );
}
super.onPostExecute(result);
}
};
}
更多用户界面
Android 中一个令人烦恼的问题是,有时当你试图将工作卸载到另一个应用程序时,你自己的应用程序会出现在可以执行该任务的意图列表中。因此,我创建了一个名为
MvSimilarIntentsInfo
的类,你可以在其中查询意图,但也可以显式地将特定包从列表中排除。我在我的浏览器应用程序中使用了这个类来实现其“打开方式”功能。它允许浏览器在其他已安装的浏览器中打开当前页面,我通过指定我自己的浏览器包名来将其排除。
此类提供了可以执行作业的应用程序的类名、应用程序图标和包名列表。有了这些信息,创建应用程序启动器就很容易了。而 MvMessages
恰好有一个用于此目的的方法——showOptionsMenuWithIcons
。此方法需要一个布局 XML 文件,定义菜单中一行的外观。布局应包含一个用于选项图标的 ImageView
和一个用于 option
标签的 TextView
。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/black"
android:orientation="horizontal" >
<ImageView
android:id="@+id/menu_option_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:baselineAlignBottom="true"
android:padding="5dip" />
<TextView
android:id="@+id/menu_option_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:background="@android:color/transparent"
android:paddingLeft="5dip"
android:paddingRight="5dip"
android:textColor="@android:color/white"
android:textSize="22dip" />
</LinearLayout>
我将此 XML 文件命名为 app_menu_row_layout.xml,并在 showOptionsMenuWithIcons
方法中引用了它。
Intent oGenericLauncherIntent = new Intent(Intent.ACTION_MAIN);
oGenericLauncherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
final MvGeneral util = new MvGeneral(MyActivity.this);
final MvSimilarIntentsInfo oSimilarIntentsInfo =
new MvSimilarIntentsInfo(MyActivity.this);
if (oSimilarIntentsInfo.getSimilarIntentsInfo(
oGenericLauncherIntent,
this.getPackageName()
).mbSuccess) {
oink.showOptionsMenuWithIcons(
// Menu title
"Launch",
// List of menu option icons
oSimilarIntentsInfo.mAppDrawableList,
// List of menu option text
oSimilarIntentsInfo.mAppNameList,
// Menu option layout
R.layout.app_menu_row_layout,
// ID of option icon's ImageView in the layout
R.id.menu_option_icon,
// ID of option icon's TextView in the layout
R.id.menu_option_title,
// Option click handler
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Launch app by package name
util.launchApp(oSimilarIntentsInfo.mPackageNameList.get(which));
oink.showMessage(oSimilarIntentsInfo.mPackageNameList.get(which));
}
},
true);
}
最后说明
AndroidWithoutStupid
拥有比这里提到的更多的功能。我已经使用这个库发布了几个应用程序。它们都运行良好。但是,我并非专业的 Java 程序员,因此我欢迎 GitHub 上对代码的所有改进。本文中使用的源代码和 AndroidWithoutStupid JAR 文件可从 https://codeproject.org.cn/KB/android/801927/CodeProject_AndroidWithoutStupid.zip 获取
此库以无版权形式发布,是免费的公共领域软件。它可以在任何类型的免费或商业软件中使用,无需署名。你可以按原样使用该库,也可以剪切粘贴你认为有用的任何代码。