为什么80%的码农都做不了架构师?>>>   

简介: 您的很多 Android 应用程序都需要与 Internet 数据交互,这些数据采用各种格式。本文将带您构建一个 Android 应用程序,它可以使用两种流行数据格式 —XML 和 JavaScript Object Notation (JSON)— 以及来自 Google 的比较奇异的 protocol buffers 格式。您将了解到与每个格式相关的性能和编码利弊。
常用缩略词
Ajax: Asynchronous JavaScript + XML
API:应用程序编程接口
CSV:逗号分隔值
CSS:层叠样式表
DOM:文档对象模型
HTML:超文本标记语言
HTTP:超文本传输协议
IDL:接口描述语言
JSON:Javascript 对象标识
SAX:XML 简单 API
SDK:软件开发包
UI:用户界面
URL:统一资源定位符
XML:可扩展标记语言
3G:第三代手机技术标准
Android 应用程序必须访问位于 Internet 上的数据,而 Internet 数据可以有几种不同的格式。本文将介绍在 Android 应用程序中如何使用三种数据格式:
XML
JSON
Google 的 protocol buffers
首先开发一个 Web 服务,将 CSV 数据转换成 XML、JSON 和 protocol-buffers 格式。然后构建一个样例 Android 应用程序,可以从 Web 服务中以任何一种格式提取数据并将其解析并显示给用户。
要进行本文中的练习,您需要最新的 Android SDK(参见 参考资料)和 Android 2.2 平台。SDK 还要求您安装一个 Java™ 开发包(JDK);本文中使用了 JDK 1.6.0_17。您不需要有 Android 物理设备;所有代码都将在 SDK 的 Android 仿真器中运行。本文并没有教您如何进行 Android 开发,因此建议您熟悉 Android 编程。当然,只凭借 Java 编程语言的知识也可以完成本文的学习。
您还需要一个 Java web 应用程序服务器来运行 Android 应用程序使用的 Web 服务。此外,也可以将服务器端代码部署到 Google App Engine。参见 下载 部分获得完整的源代码。
Day Trader 应用程序
您将开发一个简单的 Android 应用程序,叫做 Day Trader。Day Trader 允许用户输入一个或更多的股票代码并获取其所代表股票的最新价格信息。用户可以指定数据使用的格式:XML、JSON 或 protocol buffers。实际的 Android 应用程序通常不会提供此选择,但是通过实现此功能,您可以了解如何让您的应用程序处理每一种格式。图 1 展示了 Day Trader 用
户界面:
图 1. 运行中的 Day Trader 应用程序
文本框及其旁边的 Add Stock 按钮允许用户输入感兴趣的每支股票的代码。用户按下 Download Stock Data 按钮后,会从服务器请求所有这些股票的数据,在应用程序中解析并显示在屏幕上。默认情况下,获取的是 XML 数据。通过菜单,您可以在 XML、JSON 或 protocol buffers 数据格式间切换。
清单 1 显示用于创建 图 1 中所示 UI 的布局 XML:
清单 1. Day Trader 布局 XML

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="fill_parent"android:layout_height="fill_parent"><LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"><EditText android:id="@+id/symbol" android:layout_width="wrap_content"android:layout_height="wrap_content" android:width="120dip"/><Button android:id="@+id/addBtn" android:layout_width="wrap_content"android:layout_height="wrap_content" android:text="@string/addBtnLbl"/></LinearLayout><LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"><TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/symList" /><Button android:id="@+id/dlBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/dlBtnLbl"/></LinearLayout><ListView android:id="@android :id/list" android:layout_height="fill_parent" android:layout_width="fill_parent"android:layout_weight="1"/>
</LinearLayout>
清单 1 中的大部分代码都简单明了。可以看到几个小部件创建了 图 1 所示的输入和按钮。还会看到一个 ListView,Android 小部件中真正的瑞士军刀。此 ListView 将用从服务器下载的股票数据填充。清单 2 显示了控制该视图的 Activity:
清单 2. Day Trader 主活动
public class Main extends ListActivity {private int mode = XML; // default@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);final EditText input = (EditText) findViewById(R.id.symbol);final TextView symbolsList = (TextView) findViewById(R.id.symList);final Button addButton = (Button) findViewById(R.id.addBtn);final Button dlButton = (Button) findViewById(R.id.dlBtn);addButton.setOnClickListener(new OnClickListener(){public void onClick(View v) {String newSymbol = input.getText().toString();if (symbolsList.getText() == null || symbolsList.getText().length() == 0){symbolsList.setText(newSymbol);} else {StringBuilder sb = new StringBuilder(symbolsList.getText());sb.append(",");sb.append(newSymbol);symbolsList.setText(sb.toString());}input.setText("");}});dlButton.setOnClickListener(new OnClickListener(){public void onClick(View v) {String symList = symbolsList.getText().toString();String[] symbols = symList.split(",");symbolsList.setText("");switch (mode){case JSON :new StockJsonParser().execute(symbols);break;case PROTOBUF :new StockProtoBufParser().execute(symbols);break;default :new StockXmlParser().execute(symbols);break;}}});}
}

此 Activity 设置了 清单 1 中 XML 文件的布局,它将几个事件处理程序连接起来。首先,对于 Add Stock 按钮而言,代码读取文本框中的代码并将其添加到 symList TextView 中,用逗号分隔每个代码。接下来,对于 Download 按钮而言,处理程序从 symList TextView 中读取数据,然后 —基于 mode 变量— 使用三个不同的类之一从服务器下载数据。菜单设置 mode 变量的值;这个代码不是很重要,因此我在 清单 2 中省略了它。在了解各种数据下载/解析类之前,我先为您展示一下服务器如何提供此数据。

提供股票数据
应用程序服务器需要能够做两件事。第一,它必须获取股票代码列表并检索它们的数据。然后,它需要接受一个格式参数并基于该格式编码数据。对于 XML 和 JSON 格式而言,该服务器将返回作为文本的串行化的股票数据。对于 protocol buffers 而言,它必须发送二进制数据。 
清单 3 显示了处理这些步骤的 servlet:
清单 3. Stock Broker servlet

public class StockBrokerServlet extends HttpServlet {public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {String[] symbols = request.getParameterValues("stock");List<Stock> stocks = getStocks(symbols);String format = request.getParameter("format");String data = "";if (format == null || format.equalsIgnoreCase("xml")){data = Stock.toXml(stocks);response.setContentType("text/xml");    } else if (format.equalsIgnoreCase("json")){data = Stock.toJson(stocks);response.setContentType("application/json");} else if (format.equalsIgnoreCase("protobuf")){Portfolio p = Stock.toProtoBuf(stocks);response.setContentType("application/octet-stream");response.setContentLength(p.getSerializedSize());p.writeTo(response.getOutputStream());response.flushBuffer();return;}response.setContentLength(data.length());response.getWriter().print(data);response.flushBuffer();response.getWriter().close();}public List<Stock> getStocks(String... symbols) throws IOException{StringBuilder sb = new StringBuilder();for (String symbol : symbols){sb.append(symbol);sb.append('+');}sb.deleteCharAt(sb.length() - 1);String urlStr = "http://finance.yahoo.com/d/quotes.csv?f=sb2n&s=" + sb.toString();URL url = new URL(urlStr);HttpURLConnection conn = (HttpURLConnection) url.openConnection();BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));String quote = reader.readLine();List<Stock> stocks = new ArrayList<Stock>(symbols.length);while (quote != null){String[] values = quote.split(",");Stock s = new Stock(values[0], values[2],Double.parseDouble(values[1]));stocks.add(s);quote = reader.readLine();}return stocks;}
}
这是一个简单的 Java servlet,只支持 HTTP GET 请求。它读入股票的值和格式请求参数。然后调用 getStocks() 方法。该方法调用 Yahoo! Finance 获取股票数据。Yahoo! 只支持 CSV 格式的数据,因此 getStocks() 方法将其解析到一个 Stock 对象列表。
清单 4 展示了这个简单的数据结构:
清单 4. 股票数据结构

public class Stock {    private final String symbol;private final String name;private final double price;//getters and setters omittedpublic String toXml(){return "<stock><symbol>" + symbol +
"</symbol><name><![CDATA[" +name + "]]></name><price>" + price +
"</price></stock>";}public String toJson(){return "{ 'stock' : { 'symbol' : " +symbol +", 'name':" + name + ", 'price': '" + price + "'}}";}public static String toXml(List<Stock> stocks){StringBuilder xml = new StringBuilder("<stocks>");for (Stock s : stocks){xml.append(s.toXml());}xml.append("</stocks>");return xml.toString();}public static String toJson(List<Stock> stocks){StringBuilder json = new StringBuilder("{'stocks' : [");for (Stock s : stocks){json.append(s.toJson());json.append(',');}json.deleteCharAt(json.length() - 1);json.append("]}");return json.toString();}
}
每个 Stock 都有三个属性— symbol、name 和 price — 和几个便捷的方法,以便将其自己转换成 XML 字符串或 JSON 字符串。它提供了一个工具方法,用于将 Stock 对象列表转换成 XML 或 JSON。回到 清单 3,根据格式请求参数,Stock 对象列表被转换成 XML 或 JSON 字符串并被发送回客户端。
XML 和 JSON 用例非常类似和直接。对于 protocol buffers,您必须生成 protocol buffers 格式的代码读写对象。为此,您需要使用 protocol buffers 规范格式定义数据结构。
清单 5 展示了一个示例:
清单 5. 股票的 Protocol buffers 消息

package stocks;option java_package = "org.developerworks.stocks";message Quote{required string symbol = 1;required string name = 2;required double price = 3;
}message Portfolio{repeated Quote quote = 1;
}
protocol buffers 消息格式类似于接口描述语言 (IDL),它与语言无关,因此可以将其与各种语言一起使用。在本例中,运行 protocol buffers 编译器(protoc)将 清单 5 中的代码编译成要用于客户端和服务器的 Java 类。有关将 protocol buffers 消息编译成 Java 类的详细信息,请参阅 Protocol Buffers Developer Guide(参见 参考资料)。
在 清单 3 中,一个名为 toProtoBuf() 的方法将 Stock 对象列表转换成一个 Portfolio 消息。
清单 6 展示了该方法的实现:
清单 6. 创建组合消息

public static Stocks.Portfolio toProtoBuf(List<Stock> stocks){List<Stocks.Quote> quotes = new ArrayList<Stocks.Quote>(stocks.size());for (Stock s : stocks){Quote q = Quote.newBuilder().setName(s.name).setSymbol(s.symbol).setPrice(s.price).build();quotes.add(q);}return Portfolio.newBuilder().addAllQuote(quotes).build();
}
清单 6 中的代码使用了从 清单 5 中的消息生成的代码 — Quote 和 Portfolio 类。只需构建来自每个 Stock 对象的 Quote,然后将其添加到 清单 3 中返回到 servlet 的 Portfolio 对象即可。在 清单 3 中,servlet 直接打开到客户端的流并使用生成的代码编写到流的二进制协议 buffers 数据。
现在,您了解了服务器如何创建要发送到 Android 应用程序的数据。接下来将学习应用程序如何解析此数据。

使用数据格式
清单 2 中的主 Activity 需要使用服务器可以发送的各种格式的数据。它还需要请求适当格式的数据并且数据一旦解析,就用它来填充其 ListView。因此,无论数据格式是什么,大部分功能都是通用的。
首先,创建一个抽象的基类,封装此通用功能,如 清单 7 所示:
清单 7. 数据解析器基类

abstract class BaseStockParser extends AsyncTask<String, Integer, Stock[]>{String urlStr = "http://protostocks.appspot.com/stockbroker?format=";protected BaseStockParser(String format){urlStr += format;}private String makeUrlString(String... symbols) {StringBuilder sb = new StringBuilder(urlStr);for (int i=0;i<symbols.length;i++){sb.append("&stock=");sb.append(symbols[i]);}return sb.toString();}protected InputStream getData(String[] symbols) throws Exception{HttpClient client = new DefaultHttpClient();HttpGet request = new HttpGet(new URI(makeUrlString(symbols)));HttpResponse response = client.execute(request);return response.getEntity().getContent();}@Overrideprotected void onPostExecute(Stock[] stocks){ArrayAdapter<Stock> adapter = new ArrayAdapter<Stock>(Main.this, R.layout.stock, stocks );setListAdapter(adapter);}
}
清单 7 中的基类扩展了 android.os.AsyncTask。这是一个常用的用于异步操作的类。它抽象出线程和处理程序的创建,用于请求主 UI 线程。它是基于其输入和输出数据类型参数化的。对于所有解析器而言,输入总是一样的:股票代码字符串。 输出也是一样的:Stock 对象数组。基类获取 format,这是一个指定了要使用的数据格式的字符串。然后提供一个方法,发出适当的 HTTP 请求并返回一个流响应。最后,它覆盖 AsyncTask 的 onPostExecute() 方法并使用从解析器返回的数据为 Activity 的 ListView 创建一个Adapter。
现在看到三个解析器的功能是通用的。我将为您展示更具体的解析代码,从 XML 解析器开始。

用 SAX 解析 XML
Android SDK 提供了几种使用 XML 的方式,包括标准 DOM 和 SAX。 对于一些对内存密集型情况,可以使用 SDK 的 pull-parser。大部分时候,SAX 是最快的方式。Android 包括一些便捷的 API 使得使用 SAX 更轻松。
清单 8 显示了 Day Trader 应用程序的 XML 解析器:
清单 8. XML 解析器实现

private class StockXmlParser extends BaseStockParser{public StockXmlParser(){super("xml");}@Overrideprotected Stock[] doInBackground(String... symbols) {ArrayList<Stock> stocks = new ArrayList<Stock>(symbols.length);try{ContentHandler handler = newHandler(stocks);Xml.parse(getData(symbols), Xml.Encoding.UTF_8, handler);} catch (Exception e){Log.e("DayTrader", "Exception getting XML data", e);}Stock[] array = new Stock[symbols.length];return stocks.toArray(array);}private ContentHandler newHandler(final ArrayList<Stock> stocks){RootElement root = new RootElement("stocks");Element stock = root.getChild("stock");final Stock currentStock = new Stock();stock.setEndElementListener(new EndElementListener(){public void end() {stocks.add((Stock) currentStock.clone());}});stock.getChild("name").setEndTextElementListener(new EndTextElementListener(){public void end(String body) {currentStock.setName(body);}});stock.getChild("symbol").setEndTextElementListener(new EndTextElementListener(){public void end(String body) {currentStock.setSymbol(body);}});stock.getChild("price").setEndTextElementListener(new EndTextElementListener(){public void end(String body) {currentStock.setPrice(Double.parseDouble(body));}});return root.getContentHandler();}
}

清单 8 中的大部分代码都在 newHandler() 方法中,该方法创建一个 ContentHandler。如果熟悉 SAX 解析, 会知道 ContentHandler通过响应 SAX 解析器触发的各种事件创建解析数据。newHandler() 方法使用 Android 便捷 API 指定使用事件处理程序的ContentHandler。代码只是侦听在解析器遇到各种标记时触发的事件,然后选取数据,放到 Stock 对象列表中。 创建ContentHandler 后,调用 Xml.parse() 方法来解析基类提供的 InputStream 并返回 Stock 对象数组。这是快速解析 XML 的方法,但是 —即使使用 Android 提供的便捷 API— 它也是非常冗长的。

使用 JSON
XML 是 Android 上的一等公民,鉴于依赖于 XML 的 Web 服务的数量,这是个好事。很多服务还支持另一个流行格式 JSON。它通常比 XML 简洁一些,但也是人们可读的,使得它更易于使用,并且可以更轻松地将其用于调试使用它的应用程序。Android 包括一个 JSON 解析器。(您可以从 JSON.org 网站获得该解析器,只是要去除几个手机不需要的类)。 
清单 9 显示了使用中的解析器:
清单 9. JSON 解析器实现

private class StockJsonParser extends BaseStockParser{public StockJsonParser(){super("json");}@Overrideprotected Stock[] doInBackground(String... symbols) {Stock[] stocks = new Stock[symbols.length];try{StringBuilder json = new StringBuilder();BufferedReader reader = new BufferedReader(new InputStreamReader(getData(symbols)));String line = reader.readLine();while (line != null){json.append(line);line = reader.readLine();}JSONObject jsonObj = new JSONObject(json.toString());JSONArray stockArray = jsonObj.getJSONArray("stocks");for (int i=0;i<stocks.length;i++){JSONObject object = stockArray.getJSONObject(i).getJSONObject("stock");stocks[i] = new Stock(object.getString("symbol"), object.getString("name"), object.getDouble("price"));}} catch (Exception e){Log.e("DayTrader", "Exception getting JSON data", e);}return stocks;}
}
可以看到在 Android 中使用 JSON 解析器是多么简单。您将来自服务器的流转换成传递给 JSON 解析器的字符串。您遍历对象图并创建 Stock 对象数组。如果使用过 XML DOM 解析,这看起来很类似,因为编程模型几乎一样。
像 DOM 一样,JSON 解析器可以用于内存密集型应用。在 清单 9 中,所有来自服务器的数据都表示为字符串,然后作为JSONObject,最后作为 Stock 对象数组。换句话说,同一数据通过三种不同的方式表示。可以看到,对于大量数据而言,这可能是个问题。当然,一旦到达方法末尾,这三种数据表示方式中的两种都会落在范围之外,被垃圾回收器回收。但是,只是触发更频繁的垃圾回收可能会对用户体验带来负面影响,造成处理速度下降。如果内存效率和性能很重要,使用 protocol buffers 的解析器可能是个较好的选择。

使用 protocol buffers 处理二进制
Protocol buffers 是一个由 Google 开发的与语言无关的数据串行化格式,旨在比 XML 更快地通过网络传送数据。它是 Google 用于服务器对服务器调用的事实 标准。Google 将该格式及其用于 C++、Java 和 Python 编程语言的绑定工具以开源方式提供。
在 清单 3 和 清单 6 中看到 protocol buffers 是二进制格式。如您所料,这使得数据很简洁。如果在客户端和服务器端启用 gzip 压缩,在使用 XML 和 JSON 时通常也可以得到类似的消息大小,但是 protocol buffers 仍然有一些大小上的优势。它还是一种可以迅速解析的格式。最后,它提供了一个相当简单的 API。
清单 10 显示了一个示例解析器实现:
清单 10. Protocol buffers 解析器实现

private class StockProtoBufParser extends BaseStockParser{public StockProtoBufParser(){super("protobuf");}@Overrideprotected Stock[] doInBackground(String... symbols) {Stock[] stocks = new Stock[symbols.length];try{Stocks.Portfolio portfolio = Stocks.Portfolio.parseFrom(getData(symbols));for (int i=0;i<symbols.length;i++){stocks[i] = Stock.fromQuote(portfolio.getQuote(i));}} catch (Exception e){Log.e("DayTrader", "Exception getting ProtocolBuffer data", e);}return stocks;}

如 清单 3 所示,您可以使用 protocol buffers 编译器生成的 helper 类。这与服务器使用的 helper 类相同。可以编译它一次,然后在服务器和客户端共享它。 这样,您可以更轻松地直接从服务器的流读取数据并将其转换成 Stock 对象数组。这种简单编程也具有非常出色的性能。现在看一下此性能与 XML 和 JSON 的比较。

性能比较
比较性能通常涉及某种微基准测试,此类基准测试很容易产生偏见或无意间得到不正确的结果。即使以公平方式设计微基准测试,很多随机因素也会对结果产生影响。 尽管有这些问题,我还是要使用这样的微基准测试来比较 XML(大约 1300 ms)、JSON(大约 1150 ms)和 protocol buffers(大约 750 ms)。基准测试向服务器发送了一个关于 200 个股票的请求并测量了从发出请求到用于创建 ListView 的 Adapter 的数据准备就绪所需的时间量。对每个数据格式在两个设备上进行 50 次这样的操作:一个 Motorola Droid 和一个 HTC Evo,两个都通过 3G 网络。
图 2 显示了结果:
图 2. 比较数据格式速度

图 2 显示出,在此基准测试中 protocol buffers(大约 750 ms)比 XML (大约 1300 ms)几乎快两倍。很多因素影响着数据通过网络和被手持设备处理的性能。一个明显的因素是通过网络的数据量。二进制格式的 protocol buffers 比文本格式的 XML 和 JSON 在通过网络时小得多。然而,文本格式可以使用 gzip 进行有效地压缩,这是 Web 服务器和 Android 设备都支持的标准技术。 
图 3 显示了在打开和关闭 gzip 时通过网络的数据大小:
图 3. 不同格式的数据大小

图 3 应该增加了您对 XML 和 JSON 之类的文本内容的压缩效果的喜爱(更不用说 Web 格式、HTML、JavaScript 和 CSS 了)。protocol buffers 数据(大约 6KB)比原始 XML(大约 17.5KB)或 JSON(大约 13.5KB)数据小得多。但是一旦进行了压缩, JSON 和 XML(都是大约 3KB)实际上比 protocol buffers 小很多了。在本例中,它们都接近于 protocol-buffers 编码消息大小的一半了。
回到 图 2,速度的不同显然不能由通过网络的消息大小解释。protocol-buffers 消息比 XML 或 JSON 编码的消息大,但是通过使用 protocol buffers,您仍然能够削减半秒钟的用户等待时间。 这是否意味着应该在 Android 应用程序中使用 protocol buffers 呢?这样的决定很少是固定的。如果要发送的数据量很小,则三种格式间的差异也不大。对于大量数据而言,protocol buffers 可能会有所不同。但是,像这样精心设计的基准测试无法替代对您自己的应用程序的测试。

结束语
本文介绍了如何使用 Internet 上流行的两种数据格式 XML 和 JSON 的方方面面。还讲到了第三种可能性,protocol buffers。像软件工程中的其他内容一样,选择技术主要就是权衡利弊。当您为一个局限的环境(比如 Android)开发时,这些决定的结果往往被放大了。我希望您现在拥有的关于这些后果的额外知识能够帮助您创建出色的 Android 应用程序。

关于作者
Michael Galpin 是 eBay 的一名架构师。他经常为 developerWorks 撰写文章,同时在 TheServerSide.com 和 Java Developer 期刊上发表文章,他还有自己的 博客。他从 1998 年开始做职业程序员,并拥有加州理工学院数学专业的学士学位。
例子文件: daytrader.stockbroker.zip

转载于:https://my.oschina.net/dsm/blog/75411

在 Android 应用程序中使用 Internet 数据相关推荐

  1. 在 Android 应用程序中使用 SQLite 数据库以及怎么用

    part one : android SQLite 简单介绍 SQLite 介绍 SQLite 一个非常流行的嵌入式数据库.它支持 SQL 语言,而且仅仅利用非常少的内存就有非常好的性能.此外它还是开 ...

  2. Android的5中主要的数据存储方式

    Android有5中主要的数据存储方式: 1.Android本身的数据库(SQLite数据库):SQlite是一个轻量级的.跨平台的数据库.占用内存小,支持基本的SQL语法,通常用于存储用户信息. 2 ...

  3. java中使用es的dsl_基于DSL的基于图论的Java程序中输入图数据的方法

    java中使用es的dsl 我们大多数人已经编写了一些处理图论算法的程序,例如找到两个顶点之间的最短路径,找到给定图的最小生成树等等. 在这些算法的每一种中,表示图形的编程方式是使用邻接矩阵或邻接列表 ...

  4. 在MVC 6 .NET核心应用程序中添加种子数据

    目录 目标 介绍 使用的组件 创建项目 DbInitializer类 结论 目标 就像MVC 5一样,我试图启用自动迁移和Seeding默认数据,但似乎MVC 6中的行为已经改变,所以我想分享这个提示 ...

  5. android程序root权限,android应用程序中应用root权限

    android应用程序中使用root权限 要在android应用程序中使用root权限,那么运行程序的设备必须具有root权限. http://www.eoeandroid.com/code/2012 ...

  6. 绕过Android虚拟机,如何绕过android应用程序中的模拟器检测

    如何绕过android应用程序中的模拟器检测 介绍 在模拟器上测试一些android应用程序时,它总会显示应用程序与CPU不兼容,或者在模拟器上安装应用程序时会出现一些错误 我会告诉你如何轻松绕过 留 ...

  7. android 浏览器 cookie,从Android的浏览器中传递cookie数据到App中

    从Android的浏览器中传递cookie数据到App中 蒋彪@南京 2013-4-19 1.    需求 客户有一个需求, 在Android的浏览器上登录一个site, login之后,会在本地co ...

  8. 如何在微信小程序中爬取数据

    如何在微信小程序中爬取数据 下载Charles 在Charles中点击Help -> SSL Proxying -> Install Charles Root Certificate 然后 ...

  9. Android程序 地图,在定制的Android应用程序中使用谷歌地图

    我收到以下问题(显示在LogCat中).我希望any1可以帮助我,因为我是android应用程序开发的新手.在定制的Android应用程序中使用谷歌地图 11-17 16:50:38.087: E/A ...

最新文章

  1. 多个敏捷团队同时做一个项目_您说您的团队很敏捷……但是这个词可能并不代表您的想法。...
  2. freemarker的${!}
  3. SecureCRT安装的注意事项
  4. java update多个字段的值_SQL的update多个字段的写法
  5. pl/sql链接64位oracle数据库报错Could not initialize oci.dll
  6. OpenCV学习笔记之Mat
  7. James Shore:敏捷的衰落
  8. org.apache.hadoop.hive.metastore.api.InvalidObjectException: Role public already exists.
  9. win10下markdownpad2显示问题
  10. ATL offsetofclass 的工作原理
  11. nginx配置跨域、gzip加速、代理详细讲解
  12. java缓存内存泄漏_java – getMethod是缓存并导致内存泄漏
  13. 漫画:大厂总体 SaaS 化部署,到底什么是 IaaS、PaaS 和 SaaS?
  14. Bootstrap 异常
  15. 在面向服务的设计时有四个原则:
  16. ubuntu实现屏幕的旋转和开启自动旋转屏幕
  17. #define 喵 int_【吃鸡大作战第三季】第12集 告白小雪喵
  18. 『Java CVE』CVE-2022-33980: Apache Commons Configuration 读文件RCE
  19. 【Beta】Scrum Meeting 7 与助教谈话
  20. linux下退出mysql

热门文章

  1. LVS+Heartbeat+Ipvsadm+Ldirectord安装(四)
  2. Python才排第8名!2018增速最快TOP 10编程语言盘点
  3. android实现模拟自动点击_昆仑通态专题(十三):模拟实现全自动洗衣机系统的设计(案例)...
  4. 机器学习库一:scikit-learn
  5. Redis添加从节点
  6. 单机负载告警,数据库与应用分离
  7. Lambda表达式的注意事项【理解】
  8. 获取class文件对象三种方式
  9. 百万数据报表导出:使用SXSSFWorkbook完成百万数据报表打印
  10. ServletFileUpload API详解