yichao firstname, zeaster nickname, zhang lastname

Showing posts with label google. Show all posts
Showing posts with label google. Show all posts

tips on learning android

目前Android的源代码完全开放给了社区. 由于它的开放性, 易移植性, 它将不仅仅是一个手机操作系统, 近日社区中已经有人把Android移植到Asus Eee PC 1000H以及HP mini note 2133上. 其中蓝牙和无线也可以正常驱动.

可以预见的, Android将在更多的设备上生根发芽!

对于那些希望锻炼技术的同学们, Android的源代码是一个难得的学习机会!
整套Android源代码包括2部分, 一是platform部分, 另外一个是tools部分.
git://android.git.kernel.org/platform/manifest.git
git://android.git.kernel.org/tools/manifest.git

整个Android系统是由OHA各个厂商贡献的小系统组成的一个大系统.
Platform就是这个大系统, 目前它由16个核心系统, 61个外部系统, 21个内置的android应用程序或服务组成.每个小系统的功能简介可以参考http://source.android.com/projects

tools部分就是用来管理这些小系统的工具集, 可以方便小系统各自独立开发, 或者日后替换掉某个小系统, 又可以方便的把各个小系统集成到一起.
它包括了一个repo工具, 用来更方便的管理git代码库. 一个基于Rietveld的Gerrit代码review在线工具.

'''我很想知道这2个工具名字的来历?!
难度是为了纪念Gerrit Rietveld, 这位荷兰家具设计师?!
'''

啰唆了这么多, 才回到主题上, 学习android的tips,
一个tip就是善用这个Gerrit代码review在线工具. 通过它可以查看Android的code changes以及code review过程中的代码点评. 从中可以学习很多大师们的编程技巧. 所有open changes在这里
http://review.source.android.com/

比如change-6647, http://review.source.android.com/6647
是memcached以及MogileFS的作者Brad Fitzpatrick提交的. 他希望把系统是否在24小时模式这个boolean变量缓存下来,而不是每次都调用开销比较大的方法去判断.
这是代码的diff输出
http://review.source.android.com/6647/diff/1/za3656e3b0c4d095fd434b4c6a3dd70f07501f8f3
为了缓存这个boolean变量, Brad Fitzpatrick增加了2个static变量,
private static Context m24HourLastContext = null;
private static boolean mCached24HourMode;
但Code reviewer Romain Guy 不同意这次change, 原因也记录到了Gerrit上.
因为增加的static变量m24HourLastContext会造成内存泄露, 并且Romain给了一篇介绍类似内存泄露的blog.
http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/
在这篇blog中, 可以看到更多的Android实现细节. 比如android.graphics.drawable.Drawable对象由于callback方法而对其所属android.view.View的引用, 可以更加清晰的理解为什么Android平台上会出现这种内存泄露.
另外, Romain也说明了
(a)Android内置的Home Screen应用程序是如何解决Drawable对象对View的引用问题的.
(b)以及常见的2种避免类似内存泄露的方法.

Romain Guy这个同学也是个UI高手, 他学生时代在SUN参与了JDK中Swing的开发, 毕业后去了Google.

还有一些change可以反映出一些细节问题, 比如, http://review.source.android.com/6601
在Reviewer的评语中看到, 他要求能重用StringBuilder的地方绝不新建一个StringBuilder对象. 可见其对代码控制之严, 之细.
精致的软件就需要这样精致的控制.

以上这些仅仅是我看过5,6个chagnes后总结的, 相信如果阅读更多, 可以从中学习到更多的tips.

PS1,
Steve Guo同学的blog中有数十篇其工作中对android底层代码的分析, 值得推荐
http://letsgoustc.spaces.live.com/

PS2,
上面那个Dalvik中Drawable对象内存泄露的问题, 实际上是由于Dalvik默认就给Drawable增加了callback功能而引起的内存泄露.
由此联想到Sun的JDK中java.io.ObjectOutputStream的一个设计问题.
详见http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4363937
其中也是由于ObjectOutputStream默认就认为其write的objects有依赖关系而将它们都保存到一个table中, 所以在传输大量对象时, 如果不间断的调用reset方法,就会导致OutOfMemoryError.
这些JVM设计者默认就给这些类设计了超级完备的功能, 而实际上大多数情况并用不上这些功能, 比如, drawable的callback功能, ObjectOutputStream输出对象的依赖关系.
如果默认使用最简单常用的功能, 而在特殊情况下, 由调用jdk的开发人员根据实际需要配置复杂的功能, 那么不仅可以提高运行效率, 也可以降低出现OutOfMemoryError的几率.
那么为什么他们还都要往复杂上设计?!

Google Android Custom Widget RichEdit - 1

I developed two custom widgets for ZInfo.

One is called RichText that can show links with custom display text, rather than the link itself.
The other is a more powerful dialog. It will be discussed later.

Android provides a widget called TextView with auto link feature.
That means you can show this:
This is http://zeaster.com/blog/

but, how to show this:
This is my blog.

How to show a link with custom display text?
RichText helps us in this way:

RichText rt = (RichText) findViewById(R.id.rich_text);
StringBuilder sb = new StringBuilder();
sb.append("This is a demo about how to use RichText.\n");
sb.append("a custom android widegt show links with custom display text, rather than the link itself.\n");
sb.append("This is <a href='http://zeaster.com/blog'>my blog</a>\n");
sb.append("This is <a href='http://infosword.com/zinfo'>ZInfo</a>\n");
rt.setRichText(sb.toString());


With this code, you got:
http://lh4.ggpht.com/yichao.zhang/SA9r6PZYGWI/AAAAAAAAAGo/mTDyAvex4MQ/device.png

SpannableString is used in RichText to show custom style.
For more details, check its source code.

ZInfo - Information sharing platfrom on Android

ZInfo is finally released for Android Developer Challenge after our many hard working days.
It's
an information sharing platform based on your contacts and location.
The video demo can be found at http://infosword.com/zinfo/video.html.

We gave a presentation about ZInfo on Beijing Open Party - Before the Rain.
It makes me the most excited that I meet a lot of open source followers.
They are very friendly, full of passion, enjoy coding! I learned a lot from them.

Special thanks to cleverpig!
He is so kind and want to sponsor us a server to host ZInfo.
And he understands us fully when we decided to put off releasing publicly recently.

It's so great to meet you guys in the community!

Inconvenienced when using Contact Content Provider on Android

These days, I've been developing an application that reads and writes entries from/into Android Contacts.
Everything goes fine until I need to insert email entry into Android Contact by Contact Content Provider API.

This is how to insert a people or phone entry into contact (people, phones table):
getContentProvider().insert(People.CONTENT_URI, values);
getContentProvider().insert(Phones.CONTENT_URI, values);

That's fine according to
http://code.google.com/android/devel/data/contentproviders.html

However when I insert an email entry into contact (in contact_methods table) using:
ContentURI uri = getContentProvider().insert(ContactMethods.CONTENT_URI, values);

It just returns null, nothing inserted, and nothing shows up on logcat.
Why? the link above says it should be fine!
To find the answer, I post a thread in Google Group:
http://groups.google.com/group/android-developers/browse_thread/thread/9ed4ff7fc1338b7c
However no response.
then I have been diving into build-in Contacts Application for 2 days to find how Google used their own api.
Luckily, I got the key.
see details on my another post:
http://zeaster.blogspot.com/2007/11/how-to-decompile-dex-file-on-android_28.html

You should insert an email like this:
ContentURI uri = People.CONTENT_URI.addId(user_id).addPath("contact_methods");
ContentValues values = ...
uri = getContentResolver().insert(uri, values);

why? I guess Google should provide one same way to insert content.

That's the main inconvenience.
I guess what's even worse is Google does not provide enough docs or log warnings for this tip.
Here are the others:

2.
when insert an email for a not-created-yet user id, It's still inserted successfully.
when the user id created in the future, the user would have the email address.
I guess Google should prevent this happen.
Actually many checks Google need to do.That's really a beta SDK.

3.
The content provider API is not Object-Oriented enough.
I feel it tedious to use.

4.
Maybe this is greedy.
I hope there is API to manipulate database that's similar to ActiveRecord for Rails.
Maybe I will implement one like that if time permits.

How to decompile .dex file on Android

Lucky to see the Google Android build-in Contacts is also developed by Android SDK.
This article will take it for example and discuss these How-To's:

1 How to find the Contacts App file on Google Android
2 How to find the Contacts App's classes.dex on Google Android
3 How to dump the Contacts App's classes
4 How to decompile the dumped file

{
Actually, I think the Contacts Content Provider that Google provides has some inconvenience to use.
And I will post another blog to list what's inconvenience and request some improvement from Google.
The blog link is here:
http://zeaster.blogspot.com/2007/11/inconvenienced-when-using-contact.html
}

1 How to find the Contacts App on Google Android
Using adb tool

$ adb shell
# cd /system/app
cd /system/app
# ls
ls
-rw-r--r-- root root 25519 2007-11-14 20:40 ContactsProvider.apk
-rw-r--r-- root root 7544 2007-11-14 20:40 GoogleAppsProvider.apk
-rw-r--r-- root root 16198 2007-11-14 20:40 ImProvider.apk
-rw-r--r-- root root 20308 2007-11-14 20:40 MediaProvider.apk
-rw-r--r-- root root 21272 2007-11-14 20:40 TelephonyProvider.apk
-rw-r--r-- root root 11809 2007-11-14 20:40 SettingsProvider.apk
-rw-r--r-- root root 418688 2007-11-14 20:41 Browser.apk
-rw-r--r-- root root 68077 2007-11-14 20:41 Contacts.apk
-rw-r--r-- root root 96287 2007-11-14 20:41 Development.apk
-rw-r--r-- root root 44790 2007-11-14 20:41 GoogleApps.apk
-rw-r--r-- root root 8637 2007-11-14 20:41 Fallback.apk
-rw-r--r-- root root 99431 2007-11-14 20:41 Home.apk
-rw-r--r-- root root 171614 2007-11-14 20:41 Maps.apk
-rw-r--r-- root root 424601 2007-11-14 20:41 Phone.apk
-rw-r--r-- root root 192119 2007-11-14 20:41 XmppService.apk
-rw-r--r-- root root 6614 2007-11-14 20:41 XmppSettings.apk
#

The Contacts.apk is the Contacts App and ContactsProvider.apk is the app that provides the Contact Content Provider for reading and writing "content://contacts/" resources.
we can see many system apps developed by Android Java SDK are put here.
However what is inside *.apk file. Actually it is a .zip file. It is easy to unzip and get the answer.
take Contacts.apk for example, it contains:

META-INF\
res\
AndroidManifest.xml
classes.dex
resources.arsc

The classes.dex file contains all compiled Java code.

2 How to find the Contacts App's classes.dex on Google Android
No need to unzip Contacts App's classes.dex file from Contacts.apk file.
The Dalvik VM has a cached file for us. Go head to find it.

$ adb shell
# cd /data/dalvik-cache
cd /data/dalvik-cache
# ls
ls
-rw-rw-rw- root root 73852 2007-11-27 05:16 system@app@Contacts.apk@classes.dex
-rw-rw-rw- app_0 app_0 64172 2007-11-27 05:17 system@app@ContactsProvider.apk@classes.dex
-rw-rw-rw- root root 15204 2007-11-27 05:17 system@framework@am.jar@classes.dex
-rw-rw-rw- app_3 app_3 3012 2007-11-27 07:33 system@app@Fallback.apk@classes.dex
-rw-rw-rw- root root 7252804 2007-11-27 05:16 system@framework@core.jar@classes.dex
....and many other cached files.

3 How to dump the Contacts App's classes
Unfortunately the classes.dex is not a .jar file. It's Dalvik executable format that is optimized for efficient.
so we can not pull the .class files as .jar format. However Google Android provides a tool named dexdump included in the emulator to dump .dex file. Here is how to use it.
# dexdump
dexdump: no file specified
dexdump: [-f] [-h] dexfile...

-d : disassemble code sections
-f : display summary information from file header
-h : display file header details
-C : decode (demangle) low-level symbol names
-S : compute sizes only

Now we dump the system@app@Contacts.apk@classes.dex and system@app@ContactsProvider.apk@classes.dex.

# dexdump -d -f -h -C system@app@Contacts.apk@classes.dex >> Contacts.apk.dump
# dexdump -d -f -h -C system@app@ContactsProvider.apk@classes.dex >> ContactsProvider.apk.dump

and then pull it out.
# exit
$ adb pull /data/dalvik-cache/Contacts.apk.dump ~/android
$ adb pull /data/dalvik-cache/ContactsProvider.apk.dump ~/android

Now we get the dumped files on ~/android folder.

4 How to decompile the dumped file
.dex file is optimized, so we can not decompile it as normal .class file.
Fortunately the dumped file is a bit readable for me.
As it is too big to analyze all the code, so I just take "how to create a contact" for example.
It's easy to get this info from the dumped file.
It has these classes:
...
Class name : 'com/google/android/contacts/AttachImage'
Class name : 'com/google/android/contacts/ContactEntryAdapter'
Class name : 'com/google/android/contacts/EditContactActivity'
Class name : 'com/google/android/contacts/EditContactActivity$EditEntry'
...

The EditContactActivity class has a private void create() method.
#3 : (in com/google/android/contacts/EditContactActivity)
name : 'create'
type : '()V'
access : 0x0002 (PRIVATE)

So all the code that create a contact is here from the dumped file, my comments inlined:
// private void create()
009b24: |[009b24] com/google/android/contacts/EditContactActivity.create:()V

// ContentValues v9 = new ContentValues();
009b28: 2109 3100 |0000: new-instance v9, android/content/ContentValues // class@0031
009b2c: 6f01 0f00 0900 |0002: invoke-direct {v9}, android/content/ContentValues.:()V // method@000f

// Entry v12 = this.getCurrentEntry();
009b32: f4fc a800 |0005: +iget-object-quick v12, v15, [obj+00a8]

// int v11 = v12.lines
009b36: f801 1500 0c00 |0007: +invoke-virtual-quick {v12}, [0015] // vtable #0015
009b3c: 0a0b |000a: move-result v11

// int v5 = 0
009b3e: 1205 |000b: const/4 v5, #int 0 // #0

// if v5 >= v11 goto 0046
009b40: 3ab5 3a00 |000c: if-ge v5, v11, 0046 // +003a

// EditEntry v8 = (EditEntry)this.getCurrentEntry();
009b44: f4fc a800 |000e: +iget-object-quick v12, v15, [obj+00a8]
009b48: f802 1a00 5c00 |0010: +invoke-virtual-quick {v12, v5}, [001a] // vtable #001a
009b4e: 0c0c |0013: move-result-object v12
009b50: 07c0 |0014: move-object v0, v12
009b52: 1e00 3d00 |0015: check-cast v0, com/google/android/contacts/EditContactActivity$EditEntry // class@003d
009b56: 0708 |0017: move-object v8, v0

// int v12 = v8.kind // The Instance field kind is defined in its superclass Entry. see details in dumped Entry and EditEntry sections.
009b58: f28c 1400 |0018: +iget-quick v12, v8, [obj+0014]

// int v13 = -3 // PHOTO_KIND = -3 defined in v8's superclass Entry
009b5c: 12dd |001a: const/4 v13, #int -3 // #fd

// if v12 != v13 goto 0020 // a photo entry, goto 0020
009b5e: 38dc 0500 |001b: if-ne v12, v13, 0020 // +0005

// int v5++;
009b62: d805 0501 |001d: add-int/lit8 v5, v5, #int 1 // #01

// goto 000c
009b66: 33ed |001f: goto 000c // -0013

// String v7 = v8.getData();
009b68: f801 0c00 0800 |0020: +invoke-virtual-quick {v8}, [000c] // vtable #000c
009b6e: 0c07 |0023: move-result-object v7

// boolean v12 = TextUtils.isEmpty(v7)
009b70: 7001 4200 0700 |0024: invoke-static {v7}, android/text/TextUtils.isEmpty:(Ljava/lang/CharSequence;)Z // method@0042
009b76: 0a0c |0027: move-result v12

if v12!=false/0 goto 0030
009b78: 3e0c 0800 |0028: if-nez v12, 0030 // +0008

// String v12 = v8.column // The Instance field column is defined in EditEntry. see details in dumped EditEntry section.
009b7c: f48c 2800 |002a: +iget-object-quick v12, v8, [obj+0028]

// v9.put(v12,v7) // v9 is a ContentValues, v12 is the column name, v7 is the value.
009b80: f803 0b00 c907 |002c: +invoke-virtual-quick {v9, v12, v7}, [000b] // vtable #000b

// goto 001d
009b86: 33ee |002f: goto 001d // -0012

// String v12 = v8.column // The Instance field column is defined in EditEntry. see details in dumped EditEntry section.
009b88: f48c 2800 |0030: +iget-object-quick v12, v8, [obj+0028]

// String v13 = "name";
009b8c: 180d 1c00 |0032: const-string v13, "name" // string@001c

// boolean v12 = v12.equals(v13);
009b90: ee02 0300 dc00 |0034: +execute-inline {v12, v13}, java/lang/String.equals:(Ljava/lang/Object;)Z // inline #0003
009b96: 0a0c |0037: move-result v12

// if v12 != false/0, goto 001d
009b98: 3d0c e5ff |0038: if-eqz v12, 001d // -001b

// String v12 = "EditContactActivity"
009b9c: 180c 0100 |003a: const-string v12, "EditContactActivity" // string@0001

// String v13 = "Name is required"
009ba0: 180d 0200 |003c: const-string v13, "Name is required" // string@0002

// Log.e(v12, v13); //log info using android/util/Log
009ba4: 7002 4700 dc00 |003e: invoke-static {v12, v13}, android/util/Log.e:(Ljava/lang/String;Ljava/lang/String;)I // method@0047
009baa: 0a0c |0041: move-result v12

// call this.XXX()
009bac: f801 8400 0f00 |0042: +invoke-virtual-quick {v15}, [0084] // vtable #0084

// return void
009bb2: 0e00 |0045: return-void

// ContentResolver v12 = this.getContentResolver();
009bb4: f4fc a400 |0046: +iget-object-quick v12, v15, [obj+00a4]

// CONTENT_URI v13 = People.CONTENT_URI;
009bb8: 610d 0300 |0048: sget-object v13, android/provider/Contacts$People.CONTENT_URI:Landroid/net/ContentURI; // field@0003

// CONTENT_URI v10 = v12.insert(v13, v9) // CONTENT_URI v10 = getContentResolver().insert(uri, values);
// insert people into contacts
009bbc: f803 1100 dc09 |004a: +invoke-virtual-quick {v12, v13, v9}, [0011] // vtable #0011
009bc2: 0c0a |004d: move-result-object v10

// List v12 = this.mContactEntries
009bc4: f4fc c000 |004e: +iget-object-quick v12, v15, [obj+00c0]
009bc8: 7001 8900 0c00 |0050: invoke-static {v12}, com/google/android/contacts/ContactEntryAdapter.countEntries:(Ljava/util/ArrayList;)I // method@0089
009bce: 0a06 |0053: move-result v6

// int v5 = v11 //contact Entry Count
009bd0: 01b5 |0054: move v5, v11

// if v5 >= v6, goto 009e
009bd2: 3a65 4900 |0055: if-ge v5, v6, 009e // +0049

// List v12 = this.mContactEntries
009bd6: f4fc c000 |0057: +iget-object-quick v12, v15, [obj+00c0]

// EditEntry v8 = (EditEntry) getEntry(v12, v5);
009bda: 7002 8b00 5c00 |0059: invoke-static {v12, v5}, com/google/android/contacts/ContactEntryAdapter.getEntry:(Ljava/util/ArrayList;I)Lcom/google/android/contacts/ContactEntryAdapter$Entry; // method@008b
009be0: 0c0c |005c: move-result-object v12
009be2: 07c0 |005d: move-object v0, v12
009be4: 1e00 3d00 |005e: check-cast v0, com/google/android/contacts/EditContactActivity$EditEntry // class@003d
009be8: 0708 |0060: move-object v8, v0

// int v12 = v8.kind // The Instance field kind is defined in its superclass Entry. see details in dumped Entry and EditEntry sections.
009bea: f28c 1400 |0061: +iget-quick v12, v8, [obj+0014]

// int v13 = -1 // CONTACT_KIND = -1 defined in v8's superclass Entry
009bee: 12fd |0063: const/4 v13, #int -1 // #ff

// if v12 == v13 goto 007e
009bf0: 37dc 1a00 |0064: if-eq v12, v13, 007e // +001a

// v9.clear() // ContentValues.clear()
009bf4: f801 1500 0900 |0066: +invoke-virtual-quick {v9}, [0015] // vtable #0015

// boolean v12 = v8.toValues(v9) // boolean EditEnty.toValues(ContentValues cv)
009bfa: f802 0d00 9800 |0069: +invoke-virtual-quick {v8, v9}, [000d] // vtable #000d
009c00: 0a0c |006c: move-result v12

//if v12 !=false/0, goto 007b
009c02: 3d0c 0e00 |006d: if-eqz v12, 007b // +000e

// ContentResolver v12 = this.getContentResolver();
009c06: f4fc a400 |006f: +iget-object-quick v12, v15, [obj+00a4]

// String v13 = v8.contentDirectory
009c0a: f48d 2c00 |0071: +iget-object-quick v13, v8, [obj+002c]

// v10.addPath(v13);
009c0e: f802 1800 da00 |0073: +invoke-virtual-quick {v10, v13}, [0018] // vtable #0018
009c14: 0c0d |0076: move-result-object v13

// CONTENT_URI v12 = v12.insert(v13, v9) // CONTENT_URI v12 = getContentResolver().insert(uri, values);
009c16: f803 1100 dc09 |0077: +invoke-virtual-quick {v12, v13, v9}, [0011] // vtable #0011
009c1c: 0c0c |007a: move-result-object v12

// int v5++;
009c1e: d805 0501 |007b: add-int/lit8 v5, v5, #int 1 // #01

// goto 0055
009c22: 33d8 |007d: goto 0055 // -0028

// String v7 = v8.getData();
009c24: f801 0c00 0800 |007e: +invoke-virtual-quick {v8}, [000c] // vtable #000c
009c2a: 0c07 |0081: move-result-object v7

// v9.lines
009c2c: f801 1500 0900 |0082: +invoke-virtual-quick {v9}, [0015] // vtable #0015

// boolean v12 = TextUtils.isEmpty(v7)
009c32: 7001 4200 0700 |0085: invoke-static {v7}, android/text/TextUtils.isEmpty:(Ljava/lang/CharSequence;)Z // method@0042
009c38: 0a0c |0088: move-result v12

if v12!=false/0 goto 007b
009c3a: 3e0c f2ff |0089: if-nez v12, 007b // -000e

// String v12 = v8.column // The Instance field column is defined in EditEntry. see details in dumped EditEntry section.
009c3e: f48c 2800 |008b: +iget-object-quick v12, v8, [obj+0028]

// v9.put(v12,v7) // v9 is a ContentValues, v12 is the column name, v7 is the value.
009c42: f803 0b00 c907 |008d: +invoke-virtual-quick {v9, v12, v7}, [000b] // vtable #000b

// ContentResolver v12 = this.getContentResolver();
009c48: f4fc a400 |0090: +iget-object-quick v12, v15, [obj+00a4]

// int v13 = 0
009c4c: 120d |0092: const/4 v13, #int 0 // #0

// int v14 = 0
009c4e: 120e |0093: const/4 v14, #int 0 // #0

009c50: 07c0 |0094: move-object v0, v12
009c52: 07a1 |0095: move-object v1, v10
009c54: 0792 |0096: move-object v2, v9
009c56: 07d3 |0097: move-object v3, v13
009c58: 07e4 |0098: move-object v4, v14

// int v12 = v12.update(v1,v2,v3,v4) // getContentResolver(),update(uri, values, null, null)
// refer to this method, public final int update(ContentURI uri, ContentValues values, String where, String[] selectionArgs)
009c5a: f905 1400 0000 |0099: +invoke-virtual-quick/range {v0, v1, v2, v3, v4}, [0014] // vtable #0014
009c60: 0a0c |009c: move-result v12

// goto 007b
009c62: 33de |009d: goto 007b // -0022

// int v12 = 4
009c64: 124c |009e: const/4 v12, #int 4 // #4

009c66: f5fc c800 |009f: +iput-quick v12, v15, [obj+00c8]
009c6a: f7fa 9000 |00a1: +iput-object-quick v10, v15, [obj+0090]

// int v12 = -1
009c6e: 12fc |00a3: const/4 v12, #int -1 // #ff
009c70: f801 0700 0a00 |00a4: +invoke-virtual-quick {v10}, [0007] // vtable #0007
009c76: 0c0d |00a7: move-result-object v13
009c78: f803 7f00 cf0d |00a8: +invoke-virtual-quick {v15, v12, v13}, [007f] // vtable #007f

// goto 0045
009c7e: 339a |00ab: goto 0045 // -0066
exceptions : (none)
positions : 30
0x0000 line=471
.....
0x00a3 line=515
locals : 10
//the 10 local variables defined here
0x0000 - 0x00ac reg=15 this Lcom/google/android/contacts/EditContactActivity;
0x0002 - 0x00ac reg=9 values Landroid/content/ContentValues;
0x000b - 0x00ac reg=11 contactEntryCount I
0x000c - 0x00ac reg=5 i I
0x0018 - 0x0045 reg=8 entry Lcom/google/android/contacts/EditContactActivity$EditEntry;
0x0024 - 0x0045 reg=7 data Ljava/lang/String;
0x004e - 0x00ac reg=10 contactUri Landroid/net/ContentURI;
0x0054 - 0x00ac reg=6 entryCount I
0x0061 - 0x009e reg=8 entry Lcom/google/android/contacts/EditContactActivity$EditEntry;
0x0082 - 0x009e reg=7 data Ljava/lang/String;

blogsync 0.3 released, comments can be imported.

Firstly, the foregoing blogsync 2.0 should be blogsync 0.2. :-)
blogsync is a tool that import your wordpress to blogger from rss xml file or online

Now it's blogsync 0.3 release. and it can import comments from exported wordpress rss xml file to blogger site.
it can be downloaded from:
http://code.google.com/p/blogsync-java/downloads/list

however it can not preserve the published time of the original comments.
so the published time of your imported comment is just the time when you import.
more details can be found at
http://groups.google.com/group/bloggerDev/browse_thread/thread/15c9a45416305f99

blogsync 2.0 released, import your wordpress to blogger from rss xml file or online

It's open source now and comes with new feature.

1 project hosted:
at http://code.google.com/p/blogsync-java
blogsync 0.2 can be downloaded at
http://blogsync-java.googlecode.com/files/blogsync-0.2.zip

2 new feature:
In previous blogsync, it imports wordpress posts to blogger from your wordpress website online.
Now in blogsync 0.2, it can import wordpress posts to blogger from a rss xml file that exported by wordpress.

when blogsync 0.1, the common errors are such as:
a)[Fatal Error] :1444:21: Invalid byte 1 of 1-byte UTF-8 sequence.
org.apache.xmlrpc.client.XmlRpcClientException: Failed to parse
servers response: Invalid byte 1 of 1-byte UTF-8 sequence.
b)Fatal Error] :1:1: Content is not allowed in prolog.

this is because
a) the server response is not in utf-8 encoding. or
b) the server response is not a valid xml.

when your posts are too big, for example, http://i978.iyublog.com, when export all of this blog's posts, the exported rss xml file is 2064KB.
so when transferring these data through http online, the above errors are easy to reproduce.

Few days ago, I tried to export all posts to a rss xml file, then imports these posts locally from the exported xml file, the above error is hard to reproduce again.
the worst case is that your export file is still invalid xml file or wrong encoding, however it's easy to find these errors and you can try to export it again or edit it manually until you get a valid utf-8 xml file. Now you can import these posts to blogger from the valid utf-8 rss xml file by blogsync 2.0.

ps:
how to export your wordpress posts to a rss xml file?
in wordpress admin page, go to "Manage" page -> "Export" page, there is a link to export all of your posts.

ps2:
within 24 hours, Google allows us to submit at most 50 posts by googe data api.
so after 50 posts, the tool can not import any. only to wait for another 24 hours.

blogsync with gui coming, import your wordpress to blogger

blogsync with gui coming

and with some control function about which posts to import.

it can be downloaded from
http://yichao.zhang.googlepages.com/blogsync-with-gui.tar.gz

Readme.txt

0. What is it used for?
This is a simple tool to import all posts from wordpress to blogger/blogspot.

1. How to use it?
0) set java in your path.
for windows users, run run.bat
for unix/mac users, run run.sh
1) set up your wordpress and blogger account.
2) choose one import option from option panel.
3) read posts from wordpress.
4) check posts and remove any posts that you do not want to import by selecting and right-clicking.
5) import now by just clicking the import button.

2. what is my blogger blogid?
It's in your blogspot Dashboard url, for example: http://www2.blogger.com/posts.g?blogID=18083698
"18083698" is your blogid.

3. what may cause this error?
[Fatal Error] :1444:21: Invalid byte 1 of 1-byte UTF-8 sequence.
org.apache.xmlrpc.client.XmlRpcClientException: Failed to parse
servers response: Invalid byte 1 of 1-byte UTF-8 sequence.

One of your posts may be an invalid xml.
please correct it and make sure its source code is valid xml, then import again.

4. any other questions?
Welcome to send your feedback to Yichao.Zhang & gmail.com
or leave me a comment on http://zeaster.blogspot.com

two badly things about time difference or google

1
see my letter to Group Google BloggerDataAPI-owner:
Yesterday, I replied 2 msg to the thread "share a tool that imports
all posts from wordpress to blogger/blogspot"
however only 1 msg passed by moderator.
so I wonder the other msg is filtered by google anti-spam script or
moderator.
if it's script, that's a top priority bug to fix, I think.
if it's moderator, I wonder why it is filtered. Is it mistaken by a
duplicated one?

The following is the filtered msg. I'd like to re-publish it again.
Thanks!
"I imported about 55 posts from my local wordpress to blogger for one
time.
Today for test, I upgraded my wordpress to 2.1, created a 350kb txt
file, published all its content as a post to wordpress.
after that, python script reads all my 65 wp posts and drafts
properly.
so I wonder that's a matter of your data.
Are your wp hosted on your local Mac box?
or try to capture the response from server by httplook on a windows
box or ?? on Mac"

P.S.
I suggest this group only use a script to filter. because now I post
one msg and have to wait for 10+ hours to see it in group threads for
a time difference of 10+ hours between US and China. I guess many
other people have the same issue.
a script can work for 24 hours a day, a moderator can not. Thanks
again.


2
see google group thread or google gwt issue tracker
When I checked out google-web-toolkit source code and run the unit test, surprised to get a Failure in DateTest.java.
The reason is time difference! It only passes in GMT -1 ~ -12, and fails in GMT 0 ~ 14 countries
my fix is to comment all buggish method just like testGetHours().

the code is:
  /** Testing for public int java.util.Date.getDate()* */
  public void testGetDate() {
......
    Date accum1 = create(PAST);
    int a1 = accum1.getDate();
    assertEquals(4, a1);         ===========> it fails in GMT 0 ~ 14 countries
......
  }
.......
  Date create(String s) {
    if (s.equals(FUTURE)) {
      return new Date("12/30/2010 3:4:5 GMT");
    } else if (s.equals(PAST)) {
      return new Date("1/5/1880 GMT");
    } .......
  }

  /**
   * Testing for public int java.util.Date.getHours()
   */
  public void testGetHours() {
    // Cannot be done because each time zone will give a different
    // answer
  }