2011年12月10日星期六

一直以来,我都以为UTF-16和UCS-2是一样的 囧。

UCS-2: 固定双字节表示一个字符,只能表示 UCS 的子集 BMP。
       (UCS-2 扩充后成为 UTF-16,这里我们依然将 UCS-2 指代扩充前的编码方式)
UCS-4: 固定四字节表示一个字符,能表示整个 UCS。
UTF-16: 双字节或者四字节表示一个字符,能表示整个 UCS。
        UCS-2 跟 UTF-16 在 BMP 范围兼容。 UCS-2 是废弃的。
UTF-8: 一种编码方式,一到四个字节(*)表示一个字符,能表示整个 UCS。
UTF-32: 同 UCS-4。

*现在的 Unicode 标准只有 21 位 0 ~ 0x10FFFF

2011年12月9日星期五

使用javamail生成带附件的html邮件

一般来说,带附件并且有图片的html的mime邮件形如以下的格式:

 multipart/mixed
    multipart/related
        multipart/alternative


JavaMail的FAQ没有详细解释如何发送这种多重嵌套multipart的mime邮件。关键是设置content-type为"message/rfc822",见下面示例代码的黑体部分。

public void createMail() {
 ......
 //这里只示例生成Multipart的代码,其他代码请参考JavaMail的相关文档
 Multipart mmp = null;
 mmp = getHtmlPart(plaintext, html);
 mmp = getRelatedPart(mmp, image);
 mmp = getMixedPart(mmp, attachment);
 ......
}

private Multipart getHtmlPart(String plaintext, String html) throws MessagingException {
 MimeMultipart mmp_alt = new MimeMultipart("alternative");
 MimeBodyPart mbp_text = new MimeBodyPart();
 mbp_text.setContent(plaintext, "text/plain;charset=UTF-8");
 mmp_alt.addBodyPart(mbp_text);
 MimeBodyPart mbp_html = new MimeBodyPart();
 mbp_html.setContent(html, "text/html;charset=UTF-8");
 mmp_alt.addBodyPart(mbp_html);

 return mmp_alt;

}

private Multipart getRelatedPart(Multipart aPart, String imagesPath) throws MessagingException, UnsupportedEncodingException {
 MimeMultipart mmp = new MimeMultipart("related");

 MimeBodyPart mbp = new MimeBodyPart();
 mbp.setContent(aPart, "message/rfc822");
 mmp.addBodyPart(mbp);

 String[] imageFiles = imagesPath.split(",");
 if (imageFiles.length != 0) {
  for (String file : imageFiles) {
   MimeBodyPart mbp_file = new MimeBodyPart();
   FileDataSource fds = new FileDataSource(file);
    mbp_file.setDataHandler(new DataHandler(fds));
   mbp_file.setFileName(MimeUtility.encodeText(fds.getName(), "gbk", "B"));
   File f = new File(file);
   mbp_file.setContentID("<" + f.getName() + ">");
   mmp.addBodyPart(mbp_file);
  }
 }
 return mmp;
}

private Multipart getMixedPart(Multipart aPart, String attachPath) throws MessagingException, IOException {
 MimeMultipart mmp = new MimeMultipart("mixed");

 MimeBodyPart mbp = new MimeBodyPart();
 mbp.setContent(aPart, "message/rfc822");
 mmp.addBodyPart(mbp);

 String[] files = attachPath.split(",");
 if (files.length != 0) {
  for (String file : files) {
   MimeBodyPart mbp_file = new MimeBodyPart();
   mbp_file.attachFile(file);
   mbp_file.setHeader("Content-Transfer-Encoding", "base64");
   mmp.addBodyPart(mbp_file);
  }
 }

 return mmp;
}

salt解释

以前翻阅文档是总会遇上salt这个词,但一直没有理解,今天看到这篇文章,才明白是怎么回事。
salt是一串随机(但是对每个用户是固定的,比如用户名、用户id等)的字符串,附加到密码上生成心得字符串,再用这个字符串生成md5,保存到数据库中。
使用salt和单纯的密码md5比较,有2个好处:
1. 增加破解的难度。数据库在被hack的情况下,如果不知道salt的生成规律,是无法简单地通过字典来暴力破解。
2. 增加破解的时间。即使知道salt生成规律,通过字典暴力破解也只能每次破解一个用户的密码,因为每个用户的salt是不一样的,最终生成的md5也不相同。
而如果只简单保存密码的md5,那么不同用户密码相同的时候,可以通过一次sql查询就破解。

顺便吐槽下,salt其实并不难理解,但是在看定义和用途的解释却让人很难理解。说的通俗点,密码是第一把保险锁,而salt则是网站提供的第二把保险锁。

2011年11月1日星期二

关于动态装载dex

Dalvik目前的实现中,每一个dex文件里最多只能有65536个方法引用。为了解决这个问题,有一种办法是把一个dex分成多个dex,其中一个dex作为APK的主dex,而其他的dex保存在assets中,通过DexClassLoader动态装载进来。
但我实际测试下来,发现并不实用。通过DexClassLoader装载后,每个类必须使用DexClassLoader.loadClass()取到后才能实例化。而代码中有很多使用new实例化的对象,这些类是系统的PathClassLoader来装载,仍然会抛出ClassNotFound异常。而系统的ClassLoader是无法被替换的。
所以看来只好等Google的Dalvik的更新了。

tip:
1. Android的ClassLoader一般是PathClassLoader,PathClassLoader的父classloader是BootClassLoader.
2. when are classes loaded? There are exactly two cases: when the new bytecode is executed (for example, FooClass f = new FooClass();) and when the bytecodes make a static reference to a class (for example, System.out).

2011年10月31日星期一

mistaken usage of a core class

Android工程在编译生成dex文件时dx有时候会出现下面的错误信息:
2.2-froyo中是
trouble processing "javax/Foo.class":
Attempt to include a core class (java.* or javax.*) in something other than a core library.......

2.3-gingerbread中是
trouble processing "javax/Foo.class":
Ill-advised or mistaken usage of a core class (java.* or javax.*)
when not building a core library.

原因是从安全性和完整性考虑,Android(Java有吗?)不允许从外部引用Java的核心库(java.*或javax.*)。更详细的解释参考http://code.google.com/p/android/issues/detail?id=13084#c2
但其实并不是所有的javax.*都不允许。dx的代码com/android/dx/command/dexer/Main.java中,保存了一个“黑名单”来过滤javax的包。

private static final String[] JAVAX_CORE = {
       "accessibility", "crypto", "imageio", "management", "naming", "net",
       "print", "rmi", "security", "sound", "sql", "swing", "transaction",
       "xml"
   };

那么如果Android并没有提供某些核心库而又必须要使用到这些库的情况下,该如何处理呢?一般来说都是通过repackage来重命名包。
1. 有源码的情况下,可以直接修改源代码。(通过ecllipse的改名重构是一个很好的方法)
2. 如果使用的第三方的jar包,则可以通过工具jarjar进行repackage。

2011年10月27日星期四

Android framework注册native方法流程

Android framework有许多使用JNI的地方,但与一般的应用程序注册方法有所不同,framework中通过startReg()统一进行注册。startReg()函数的实现在AndroidRuntime.cpp。
startReg()调用register_jni_procs(gRegJNI, NELEM(gRegJNI), env)。其中 gRegJNI是一个数组,保存各个java类对应的native犯法注册函数的指针。

以BinderProxy为例:
native方法的注册函数是android_util_Binder.cpp的register_android_os_Binder(),这个函数保存在gRegJNI中。经过层层调用后,最终使用jni的RegisterNatives()来完成注册。
android_util_Binder.cpp的register_android_os_Binder() --->
android_util_Binder.cpp的int_register_android_os_BinderProxy() --->
AndroidRuntime::registerNativeMethods() --->
JNIHelp.c的jniRegisterNativeMethods() --->
jni的RegisterNatives()

RegisterNatives所需要的参数是静态变量保存:
android_util_Binder.cpp中,
char* kBinderProxyPathName保存类名
JNINativeMethod gBinderProxyMethods[]保存类中方法的名字、signatures、native方法的指针。

2011年10月20日星期四

Function Object

Function Object(函数对象)顾名思义,就是用法上类似函数,但语法上来说,仍然是一个对象。
相对于函数指针,Function Object有2个优点:
1. 可以保存状态。作为一个对象,Function Object像普通的对象一样可以通过成员保存状态。
2。可以作为模板参数。

C++中,重载了operator()的类实例化的对象就是一个Function Object
例子:
class Sum { 		int val; 	public: 		Sum(int i) :val(i) { } 		operator int() const { return val; }		// extract value  		int operator()(int i) { return val+=i; }	// application 	};  	void f(vector v) 	{ 		Sum s = 0;	// initial value 0 		s = for_each(v.begin(), v.end(), s);	// gather the sum of all elements 		cout << "the sum is " << s << "\n"; 	 		// or even: 		cout << "the sum is " << for_each(v.begin(), v.end(), Sum(0)) << "\n"; 	} 
}