Android Manifest.xml中的meta-data属性

语法

<meta-data android:name="string"
           android:resource="resource specification"
           android:value="string" />

这是该元素的基本结构.可以包含在activity, activity-alias, service, receiver四个元素中。

这个名字值是额外的任意的可以提供给父组件的数据。一个组件元素能够包含任意数量的meta-data子元素。它们所有的值都会被收集在Bundle对象中并且使其可以作为组件的 PackageItemInfo.metaData 字段。

一般的值可以通过value属性来指定,但是如果要指定一个资源id作为一个值,那么就要用resource属性来代替。例如:下面的代码就是指定存储在@string/kangaroo 资源中的zoo名字。

<meta-data android:name="zoo" android:value="@string/kangaroo" />

另一方面,利用resource属性将指定zoo的资源id号,并不是存储在资源中的资源值。

<meta-data android:name="zoo" android:resource="@string/kangaroo" />

当要给组件提供多个复杂的数据时,在这里并不推荐使用多重meta-data元素,推荐你存储这些数据在一个资源文件中并且利用resource属性来通知它的id给组件。

android:name

元数据项的名字,为了保证这个名字是唯一的,采用java风格的命名规范。例如: com.example.project.activity.fred

android:resource

资源的一个引用,指定给这个项的值是该资源的id。该id可以通过方法Bundle.getInt()来从meta-data中找到。

android:value

指定给这一项的值。可以作为值来指定的数据类型并且组件用来找回那些值的Bundle方法列在了下面的表中。

工具类

自己写了个工具类来获取meta-data字段值:

public class Util {
	public static String getMetaValue(Context context, String metaKey) {
		Bundle metaData = null;
		String metaValue = null;
		if (context == null || metaKey == null) {
			return null;
		}
		try {
			ApplicationInfo ai = context.getPackageManager().getApplicationInfo(
					context.getPackageName(), PackageManager.GET_META_DATA);
			if (null != ai) {
				metaData = ai.metaData;
			}
			if (null != metaData) {
				metaValue = metaData.getString(metaKey);
			}
		} catch (NameNotFoundException e) {

		}
		return metaValue;
		}
}

Android中ScrollView嵌套ListView

这几天项目需要在ScrollView中放入ListView,一开始还以为很轻松的,就是一个xml的布局问题。但是实际操作才发现问题,会发现ListView会显示不完全,它的高度始终有问题。网上同样有很多人遇到这样的问题,大多数人不推荐这样的设计,因为默认情况下Android是禁止在ScrollView中放入另外的ScrollView的,它的高度是无法计算的。

但是既然已经有这样的需求,就要实现。StackOverFlow真是个好东西,发现已经有牛人解决了这个问题,经过试验是可以解决这个问题的,它的思路就是在设置完ListView的Adapter后,根据ListView的子项目重新计算ListView的高度,然后把高度再作为LayoutParams设置给ListView,这样它的高度就正确了,以下是源码:

public class UIHelper {
    public static void setListViewHeightBasedOnChildren(ListView listView) {
	    ListAdapter listAdapter = listView.getAdapter();
	    if (listAdapter == null) {
	        // pre-condition
	        return;
	    }

		int totalHeight = 0;
		for (int i = 0; i < listAdapter.getCount(); i++) {
			View listItem = listAdapter.getView(i, null, listView);
			listItem.measure(0, 0);
			totalHeight += listItem.getMeasuredHeight();
		}

		ViewGroup.LayoutParams params = listView.getLayoutParams();
		params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
		listView.setLayoutParams(params);
    }
}

只要在设置ListView的Adapter后调用此静态方法即可让ListView正确的显示在其父ListView的ListItem中。但是要注意的是,子ListView的每个Item必须是LinearLayout,不能是其他的,因为其他的Layout(如RelativeLayout)没有重写onMeasure(),所以会在onMeasure()时抛出异常。

在ScrollView中嵌套ListView(或者ScrollView)的另外一个问题就是,子ScrollView中无法滑动的(如果它没有显示完全的话),因为滑动事件会被父ScrollView吃掉,如果想要让子ScrollView也可以滑动,只能强行截取滑动事件,有牛人在论坛中发过代码说可以。虽然我没有亲自试过,但估计是可行的。

虽然在ScrollView中显示ScrollView在技术上的难题可以攻破,但是这样的设计却是非常差的用户体验因为用户会不容易看到和操作子ScrollView中的内容。比如好的设计是,父ListView的每个Item只显示概括性的描述,然后点击其Item会进入另外一个页面来详细描述和展示以及对这个Item的操作。

参考资料:http://stackoverflow.com/questions/3495890/how-can-i-put-a-listview-into-a-scrollview-without-it-collapsing


android中调用App市场对自身App评分

苹果的app评价很容易,直接请求AppStore的一个链接就好了,android市场这么多,请求一个链接肯定不行。之前一直以为很麻烦,也没有仔细研究,今天竟然发现原来很简单。上代码:

Uri uri = Uri.parse("market://details?id=" + context.getPackageName());
Intent goToMarket = new Intent(Intent.ACTION_VIEW, uri);
try {
    startActivity(goToMarket);
} catch (ActivityNotFoundException e) {
    Toast.makeText(context, "Couldn't launch the market !", Toast.LENGTH_SHORT).show();
}

Android Push

之前一直困扰着的android端推送服务这次终于解决了,这里总结下android平台下几种消息推送方案以及这次所采用的策略。

方案一:使用GCM服务(Google Cloud Messaging)

简介:Google在Android上标配了自己的推送GCM(Google Cloud Messageing),可以帮助开发人员给他们的Android应用程序发送数据。它是一个轻量级的消息,告诉Android应用程序有新的数据要获取从服务器,或者它可能是一个消息,其中包含了4KB的payload data(像即时通讯这类应用程序可以直接使用该payload消息)。GCM服务处理排队的消息,并把消息传递到目标设备上运行的Android应用程序。

优点:Google提供的服务、原生、简单,无需实现和部署服务端。

缺点:

1.GCM要求Android系统必须是2.2以上的版本,所以对于不少2.2以前的系统没法推送

2.国内服务不稳定。而且不少国内的终端厂商纷纷把Google的服务去掉,替换上自己的。

3.需要用户绑定Google账号,但不少国内用户没有Google账号。

方案二:使用XMPP协议(Openfire + Spark + Smack)

简介:XMPP是一种基于XML的协议,它继承了在XML环境中灵活的发展性,有很强的可扩展性。包括上面讲的GCM服务器底层也是采用XMPP协议封装的。

优点:协议成熟、强大、可扩展性强、目前主要应用于许多聊天系统中,且已有开源的Java版的开发实例androidpn。

缺点:协议较复杂、冗余(基于XML)、费流量、费电,部署硬件成本高。

而androidpn(Android Push Notification)就是基于 XMPP 开源组件的一套整合方案,服务端基于Openfire、客户端基于Smack。到AndroidPN项目主页( http://sourceforge.net/projects/androidpn/ ) 下载2个文件: androidpn-server-0.5.0-bin.zip 和 androidpn-client-0.5.0.zip 分别是服务器和客户端的代码。详细的实现方式网上有不少文章。

androidpn是韩国人放在sourceforge.net 的项目,已经有两年多没有更新了,项目应该是个人维护的,不是很成熟。有意思的是,网站上这个项目有82%的下载者的ip是中国的。androidpn有如下一些不足,开发的时候需要权衡:

1、androidpn服务端重启后客户端不会重连,这个非常悲剧

2、由于服务器不保存消息,造成了如果客户端当前离线就收不到消息。

3、androidpn发送完消息就不管了,所以没有消息回执报表之类,造成没法做应用后续的数据分析用户体验的改善,这对于企业级的应用是个致命伤。

XMPP协议比较费电费流量,这个对当前智能机的消耗太大,在窄带网络和不稳定的(手机)网络都不是最优的选择。但总体来说,XMPP协议还是比较成熟的。

方案三:使用MQTT协议(更多信息见: http://mqtt.org/

简介:轻量级的、基于代理的“发布/订阅”模式的消息传输协议。

优点:协议简洁、小巧、可扩展性强、省流量、省电,目前已经应用到企业领域(参考: http://mqtt.org/software),且已有C++版的服务端组件rsmb。

缺点:不够成熟、实现较复杂、服务端组件rsmb不开源,部署硬件成本较高。

方案四:使用HTTP轮循方式

简介:定时向HTTP服务端接口(Web Service API)获取最新消息。

优点:实现简单、可控性强,部署硬件成本低。

缺点:实时性差。

方案五:采用第三方服务

目前有不少第三方提供了类似服务,客户端只需要嵌入第三方提供的lib库,由第三方建立长连接,负责消息的接收/发送。同时对于消息都有比较详细的报表数据,可以用于做数据分析挖掘和用户体验的改善。目前国内提供推送服务的有好几家,比较成熟的主要有百度云推送, 极光推送, 个推服务

还是出于对大公司的信任吧,所以这次选择了百度的云推送,总的来说官方文档使用说明还是蛮详细的。百度的云推送共提供三种形式的推送:

1.顶部通知栏消息提醒,当然也是提供自定义;

2.消息,可以是无界面的,也可以是用户自己定义的消息处理方式;

3.富媒体推送,如图片,声音等。

值得说明的是第一种提醒方式是比较常见的,这次的app中也是这种方式,但是SDK提供的这种顶部通知栏消息点击事件每次都会重新打开一次app,哪怕之前你的app是打开过的。这种交互方法非常不友好,为了解决这个问题试了很久也没能找到相应的方法。

于是在第二种消息提醒方式时,才发现这种定制性更强,只是处理的时候有些麻烦。这里客户端拿到推送消息时手动处理使在顶部通知栏显示,并把这些提醒信息保存进客户端Sqlite文件里,从而使交互更加友好。

当然基于以后的推送新的需求可能会涉及到富媒体推送之类的,以后用到时再看下实现方式。当然以后有机会也会了解下其他的推送服务,来了解各自推送服务的优劣,从而能确定一个最优的服务。


SQLite加密--SQLCipher

最近项目中要预先放置一部分food的sqlite数据在程序里,android项目资源文件的破译非常简单,出于安全的考虑,要对sqlite文件进行加密处理,于是就用到了SQLCipher。

SQLCipher is an open source library that provides transparent, secure 256-bit AES encryption of SQLite database files.

虽然SQLCipher是开源的,但是仅仅是开源的而已——你要自己编译,不想自己编译就得付费购买已经编译好的二进制文件~~木有钱,只好自己编译了,这里来讲下编译过程。

1.下载源代码

官方源代码:https://github.com/sqlcipher/sqlcipher

2.编译

进入源代码目录:

./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" LDFLAGS="-lcrypto"
make

注意:由于SQLCipher是SQLite的另外一个版本,所以为了不影响系统的SQLite,和其他SQLCipher版本间的兼容问题,所以不能将编译生成的直接install到系统,可以做符号链接等方式来管理二进制版本。我这里建立了一个软链接:

ln -s /Users/storm/sqlcipher/sqlite3 /usr/bin/sqlcipher

3.验证编译是否成功

创建一个加密的数据,密码是aaa:

$ sqlcipher test.sqlite
SQLite version 3.7.14.1 2012-10-04 19:37:12
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> PRAGMA key = 'aaa';
sqlite> create table a(ind int);
sqlite> .tables
a
sqlite> .quit

尝试不输入密码,直接读取数据库,理论上是读不到数据,或者报错:

$ sqlcipher test.sqlite
SQLite version 3.7.14.1 2012-10-04 19:37:12
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
sqlite> .quit

尝试正确输入密码,应该成功读取:

$ sqlcipher test.sqlite
SQLite version 3.7.14.1 2012-10-04 19:37:12
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> PRAGMA key = 'aaa';
sqlite> .tables
a
sqlite> .quit

上面三个流程都过说明编译成功!

给现有数据进行加密

如何给现有的sqlite文件进行加密,没有别的简单的方法:

1.先把数据导出:

$ sqlite3 ifood.sqlite
>.output ifood.sql
>.dump

2.创建一个新的加密的数据库:

$ sqlcipher ifood_lock.sqlite
sqlite> PRAGMA key = 'abcdef'; # 设置密码

3.导入数据

>.read ifood.sql

如果项目中经常用到数据加密,可以写个脚步。

SQLCipher For Android

SQLCipher官网有详细的android项目中的使用教程,地址在:http://sqlcipher.net/sqlcipher-for-android/

实际项目中遇到的问题是密码设置不要含单引号,不然会解密不对。而且加密过后有一个弊端,就是程序会增加将近4M左右的大小,使你的apk包看起来比较大,但是为了数据安全,这点牺牲还是值得的!