65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.79/5 (30投票s)

2015年2月5日

CPOL

7分钟阅读

viewsIcon

43956

downloadIcon

452

一句话方法, 以供您学习。

引言

作为一个大部分编码工作都在 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 是什么?嗯,它是 AndroidWithoutStupidMvMessages 类的一个实例。与库中的其他类一样,你必须使用应用程序上下文初始化 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);
}

A menu with icons can be easily built with the showOptionsMenuWithIcons method.

最后说明

AndroidWithoutStupid 拥有比这里提到的更多的功能。我已经使用这个库发布了几个应用程序。它们都运行良好。但是,我并非专业的 Java 程序员,因此我欢迎 GitHub 上对代码的所有改进。本文中使用的源代码和 AndroidWithoutStupid JAR 文件可从 https://codeproject.org.cn/KB/android/801927/CodeProject_AndroidWithoutStupid.zip 获取

此库以无版权形式发布,是免费的公共领域软件。它可以在任何类型的免费或商业软件中使用,无需署名。你可以按原样使用该库,也可以剪切粘贴你认为有用的任何代码。

© . All rights reserved.