xiaoguozi's Blog
Pay it forword - 我并不觉的自豪,我所尝试的事情都失败了······习惯原本生活的人不容易改变,就算现状很糟,他们也很难改变,在过程中,他们还是放弃了······他们一放弃,大家就都是输家······让爱传出去,很困难,也无法预料,人们需要更细心的观察别人,要随时注意才能保护别人,因为他们未必知道自己要什么·····
android软件破解的工具:
smali.jar——google官方提供,主要作用是把smali文件打包成class.dex文件
baksmali.jar——google官方提供,主要作用是把*.odex文件反编译为smali文件
apktool.jar——第三方提供?用于解析*.apk文件,生成smali文件和解析资源文件
signapk.jar——apk签名工具
ddms——ADT中的一个调试工具


破解工具的使用方法:
http://sin90lzc.iteye.com/blog/1198173


除了这些破解工具之外,还需要对smali语法有一定的了解。下面的网址对smali的语法有详细的说明:
http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html


最后,最好对Android的应用开发有一定的了解,至少对Android的四大组件有深刻的认识:Activity,Service,ContentProvider,BroadcastReceiver


Android应用程序在某些机器上不能运行、崩溃的原因不外乎以下几点:
原因一:该应用程序需要依赖于生产厂商的框架(像三星,它对android的framework做过大量的修改)
错误提示:找不到field,找不到对应的方法,找不到相应的类
解决途径:
1.在smali中尝试屏蔽掉相应的field,method,或类的调用,然后进行大量测试,确保不影响正常使用
2.反编译framework,找到缺少的field,method,类的相应smali文件,根据smali文件编写java源码。(当smali文件比较小的时候,这个方法才可行,否则尽量避免用这种方法)
3.实在无招的情况下,只能把第三方的framework的smali文件复制到自己的framework里面(非常槽糕的方法)。
4.对于应用程序需要依赖第三方低层的功能实现时,而我们的低层却没有这样的功能(比如说视频通话等),此时可以使用上面的方法一解决,也可以参考技术四。


原因二:资源文件不存在,像color,drawable,string等等的资源
错误提示:资源文件找不到
解决途径:
在技巧一中有详细说明。


原因三:应用程序需要一些函数库的支持(在目录/system/lib中缺少相应的函数库)。
错误提示:一般都会抛出UnSatisfiedException异常,后面紧跟所缺函数库的库名。
解决途径:
1.在第三方的函数库中找到对应的so文件,然后复制到自己的函数库中。
2.如果函数库与函数库之间有着各种耦合的时候,方法一可能就会不适用了(看运气呗)。这时候就只能反编译so文件了(这个是C/C++的反编译的应用了,需要再学习)


原因四:应用程序没有相应的权限
错误提示:nopermission
解决途径:
1.在AndroidMenifest.xml文件中添加<user-permision />添加相应的权限


原因五:由于应用程序本身的一些安全机制或条件判断影响程序的表现
解决途径:
1.反编译成smali文件,使用技巧三的方法跟踪程序的运行,耐心地分析程序的逻辑,找到可疑smali代码,屏蔽或修改代码。


原因六:数据库的结构不一样
错误提示:缺少某个字段或类型不匹配
场情:比如android的音乐播放器与三星的音乐播放器,它们的数据库结构由MediaProvider.apk这个包提供。然而两者在MediaProvider.apk中关于数据库结构的定义是不一样的,三星音乐播放器需要更多的字段去保存信息。
解决途径:
1.拿上面场情为例,反编译MediaProvider.apk,从smali文件中找到组件ContentProvider的定义(smali文件), 在该smali文件中找到关于SQL生成表结构的字符串(如:create table...),修改该SQL语句来适合三星音乐播放器的需要,然后重新打包回apk文件。


原因七:应用程序需要引用第三方提供商自定义的框架(比如,三星的/system/framework目录下有twframework.jar、twframework-res.apk,这个就是三星的UI框架)

错误提示:
解决途径:
1.屏蔽AndroidMenifest.xml文件中<uses-library android:name="sec_feature" />的代码,一般这样改动是不可行的。
2.在/system/etc/permission目录下添加库的声明,如添加touchwiz.xml文件,文件内容如下:
<?xml version="1.0" encoding="utf-8"?>
<permissions>
    <library name="touchwiz" file="/system/framework/twframework.jar"/>
</permissions>
最后,还需要把三星的twframework.jar、twframework-res.apk复制到/system/framework目录下

反编译或破解的技巧总结:
一、对无应用程序源码的情况下,对资源文件的增删改。
概述:在无程序源码,不重新编译的情况下,删除或修改资源文件都是非常简单的一件事情,网上也有很多的文章提到过。但是网上却找不到在不重新编译的情况下添加资源文件的方法。
在不重新编译的情况下添加资源文件的步骤:
1.按正常的应用程序开发添加资源。比如,要添加一个string资源,在values/strings.xml上加上:
<string name="newstring">content</string>


2.一个编译后的apk会在values目录下多生成了一个public.xml文件,这个文件记录了每个资源的引用编号。以添加string资源为例, 在public.xml文件中找到<public type="string" ...>中最后一个元素,在这个元素后添加
<public type="string" name="newstring" id="0x7f0700a0" /><!--此时id就是string资源newstring的引用编号,注意该id应该是public.xml文件中是唯一值-->


3.修改smali文件,使用新增的资源
invoke-virtual {p0}, Lcom/sini/SfsdfsActivity;->getResources()Landroid/content/res/Resources;


move-result-object v0


const v1, 0x7f0700a0


invoke-virtual {v0, v1}, Landroid/content/res/Resources;->getString(I)Ljava/lang/String;


二、编写smali文件
概述:自己一手一脚去写smali文件是件超级困难的事情,如何快速地得到smali代码呢?
技巧:使用eclipse开发工具,新建一个Android项目,用正常的开发方式写一段java代码(这个代码就是你想在smali中完成的东西)。使 用eclipse的run->Android Application选项 生成apk文件,再把apk文件反编译,找到需要的smali代码,然后复制这段代码到需要的地方。NOTIC:复制的时候需要注意smali的变量 v0,v1...的正确性。


三、跟踪程序运行
概述:没源码=寸步难行!为了能跟踪无源码的apk程序的运行,只能辛苦地修改smali添加跟踪信息了。值得庆幸的是,android本身自带有方便的日志API(Log.i()\Log.w()...);
技巧:按技巧二的方法得到Log.i()的smali代码,然后把这段代码插入到你想跟踪信息的地方。


四、只修改smali中影响UI的代码,保留功能代码(适用于必须得把smali写回java源码的情况)
概述:这里最好的例子就是破解三星的通话界面。三星的通话界面程序Phone.apk中包含了视频通话的功能,这个功能又需要依赖三星的一些低层实现。而 我们的系统中没有视频通话的功能,也没有相应的低层实现。但Phone.apk的主要功能是打电话,而这个功能android本身就自带的。也就是说三星 的Phone.apk与android的Phone.apk的核心功能是一样的,只是UI不太一样而已。此时应该只修改smali中的影响UI的代码,屏 蔽掉
smali中对视频通话的调用。
技巧:找到程序中影响UI的Actvity,然后根据smali文件的内容,写java源码
posted @ 2013-02-17 14:52 小果子 阅读(3310) | 评论 (0)编辑 收藏
原文:
http://1622511.blog.51cto.com/1612511/581011

如果你要定制一个Android系统,你想用你自己的Launcher(Home)作主界面来替换Android自己的Home,而且不希望用户安装的Launcher来替换掉你的Launcher.
我们可以通过修改Framework来实现这样的功能。

这里以Android2.1的源代码为例来实际说明。

1)首先了解一下Android的启动过程。
  Android系统的启动先从Zygote开始启动,然后......(中间的过程就不说了).....一直到了SystemServer(framework)这个地方,看到这段代码:

      /**
     * This method is called from Zygote to initialize the system. This will cause the native
     * services (SurfaceFlinger, AudioFlinger, etc..) to be started. After that it will call back
     * up into init2() to start the Android services.
     */
    native public static void init1(String[] args);

    public static void main(String[] args) {
        if (SamplingProfilerIntegration.isEnabled()) {
            SamplingProfilerIntegration.start();
            timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    SamplingProfilerIntegration.writeSnapshot("system_server");
                }
            }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
        }

        // The system server has to run all of the time, so it needs to be
        // as efficient as possible with its memory usage.
        VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);

        System.loadLibrary("android_servers");
        init1(args);
    }

    public static final void init2() {
        Log.i(TAG, "Entered the Android system server!");
        Thread thr = new ServerThread();
        thr.setName("android.server.ServerThread");
        thr.start();
    }
}

从SystemServer的main函数开始启动各种服务。
首先启动init1,然后启动init2.
从上面的注释可以看到:init1这个方法时被Zygote调用来初始化系统的,init1会启动native的服务如SurfaceFlinger,AudioFlinger等等,这些工作做完以后会回调init2来启动Android的service。

这里我们主要来关注init2的过程。
init2中启动ServerThread线程,
ServerThread中启动了一系列的服务,比如这些:

ActivityManagerService
EntropyService
PowerManagerService
TelephonyRegistry
PackageManagerService
AccountManagerService
BatteryService
HardwareService
Watchdog
SensorService
BluetoothService
StatusBarService
ClipboardService
InputMethodManagerService
NetStatService
ConnectivityService
AccessibilityManagerService
NotificationManagerService
MountService
DeviceStorageMonitorService
LocationManagerService
SearchManagerService
FallbackCheckinService
WallpaperManagerService
AudioService
BackupManagerService
AppWidgetService

这些大大小小的服务起来以后,开始
 ((ActivityManagerService)ActivityManagerNative.getDefault()).systemReady()
在systemReady后开始开始启动Launcher。

在寻找Launcher的时候是根据HOME的filter(在Manifest中定义的<category android:name="android.intent.category.HOME" />)来过滤。
然后根据filter出来的HOME来启动,如果只有一个HOME,则启动这个HOME,如果用户自己装了HOME,那就会弹出来一个列表供用户选择。

我们现在希望从这里弹出我们自己定制的Launcher,同时也不希望弹出选择HOME的界面,我们不希望用户修改我们的home,比如我们的home上放了好多广告,以及强制安装的程序,不希望用户把它干掉。

我们可以通过这样来实现:

2) 定义一个私有的filter选项,然后用这个选项来过滤HOME.
   一般情况下我们使用Manifest中定义的<category android:name="android.intent.category.HOME"来过滤的,我们现在增加一个私有的HOME_FIRST过滤。

     在Intent.java(frameworks/base/core/java/android/content/Intent.java)中添加两行代码

    //lixinso:添加CATEGORY_HOME_FIRST
    @SdkConstant(SdkConstantType.INTENT_CATEGORY)
    public static final String CATEGORY_HOME_FIRST = "android.intent.category.HOME_FIRST";

3)修改和CATEGORY_HOME相关的所有的地方,都改成HOME_FIRST,主要是framework中的这几个地方:

    frameworks/base/services/java/com/android/server/am/ActivityManagerService.java中
    //intent.addCategory(Intent.CATEGORY_HOME);
    改成intent.addCategory(Intent.CATEGORY_HOME_FIRST); //lixinso:
    //if (r.intent.hasCategory(Intent.CATEGORY_HOME)) {
    改成if (r.intent.hasCategory(Intent.CATEGORY_HOME_FIRST)) { //lixinso: Intent.CATEGORY_HOME -> Intent.CATEGORY_HOME_FIRST
 
   frameworks/base/services/java/com/android/server/am/HistoryRecorder.java中
   // _intent.hasCategory(Intent.CATEGORY_HOME) &&
   改成 _intent.hasCategory(Intent.CATEGORY_HOME_FIRST) && //lixinso: Intent.CATEGORY_HOME->Intent.CATEGORY_HOME_FIRST

   frameworks/policies/base/mid/com/android/internal/policy/impl/MidWindowManager.java中
   //mHomeIntent.addCategory(Intent.CATEGORY_HOME); 
   改成 mHomeIntent.addCategory(Intent.CATEGORY_HOME_FIRST); //lixinso
 
  frameworks/policies/base/mid/com/android/internal/policy/impl/RecentApplicationsDialog.java中
   //new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),0);
   改成 new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME_FIRST),0); //lixinso

  frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindowManager.java中
   //mHomeIntent.addCategory(Intent.CATEGORY_HOME);
   改成 mHomeIntent.addCategory(Intent.CATEGORY_HOME_FIRST); //lixinso

  frameworks/policies/base/phone/com/android/internal/policy/impl/RecentApplicationsDialog.java中
   //ResolveInfo homeInfo = pm.resolveActivity(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),0);
   改成 ResolveInfo homeInfo = pm.resolveActivity(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME_FIRST),0); //lixinso



4) 写一个自己的Launcher.
   可以参考android sample中的Launcher,或者android源代码中的 /packages/apps/Launcher 来写。
   在Launcher中标记其是不是Launcher的最关键的代码时Manifest中的filter:android:name="android.intent.category.HOME"
   现在我们定义了自己的filter,那么,我们在我们自己写的Launcher中将Manifest改为:
    <application  android:process="android.process.acore3" android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".FirstAppActivity"
                  android:label="@string/app_name">
            <intent-filter>
                                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.HOME_FIRST" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY" />
            </intent-filter>
        </activity>
    </application>

然后将编译好的apk放到/out/target/product/generic/system/app目录下。

5)将Android自带的Launcher删除掉,包括源代码(packages/apps/Launcher)和apk(/out/target/product/generic/system/app/Launcher.apk)。

6)
做完这些工作,就可以重新编译Android了,我们可以编译修改过的几个相关的包。
如果之前编译过了Android源码,可以用mmm命令来编译部分的改动。
这里需要这样编译:

$ . build/envsetup.sh 
$ mmm frameworks/base
$ mmm frameworks/base/services/java
$ mmm frameworks/policies/base/mid
$ mmm frameworks/policies/base/phone

7)
编译完成后重新生成img文件。
$ make snod

8) 现在可以启动Android模拟器来看效果了。
首先设置环境变量:
$ export ANDROID_PRODUCT_OUT= ./out/target/product/generic
然后切换到
$ cd ./out/host/linux-x86/bin
运行
$ ./emulator

这样我们启动的模拟器里面用的image就是我们刚才编译好的自己定制的东西了。
从模拟器上可以看到启动的Launcher是我们自己的Launcher,不会出现默认的Launcher了,也不会出现选择界面。

9)我们再验证一下,如果用户装上了一个其他的Launcher(Home)会怎么样。
  从网上找一个一般的Launcher或者自己写一个一般的Launcher装上去,重新启动,不会出现选择界面。
  按HOME键也不会出来两个HOME来选择。


这样我们就牢牢控制了用户的桌面。
只有我们自己定制的HOME才能装上。 这对于定制Android设备的厂商很有用处。
posted @ 2013-02-17 14:51 小果子 阅读(1404) | 评论 (0)编辑 收藏
要移植,就要了解系统的结构和打包解包:
打包很容易,只需要制作成ZIP格式的压缩包就行了,里面包含2个文件夹和1个boot.img文件,boot.img是内核,没有内核的系统是不能运行 的,另外两个文件夹一个是system,也就是Android系统所在地,另一个是META-INF,这里一般存放是刷机脚本,我们移植一般需要改的就是 system文件夹里的内容。
解包就是解压缩。
首先说结构,system下有:
App文件夹(存放内置软件的地方,每个软件都有自己的名字标注着,很容易识别,在移植上除USB.apk必须用咱们自己的版本以外,其他可以用同分辨率机型的APP直接替换)
Bin文件夹(本文件夹一般存放的是开机运行的脚本和一些系统必备代码,一般移植出了问题不能开机就是这里的问题,一般我们用702的或者720的ROM 里的,注意除了NETD需要用701的外,其他也可以直接用702或者720ROM的,不过一般系统升级不会修改这里,在移植的时候,要删除2nd- init以及所有.sh脚本文件,这些对701是没有效果的,如果电池显示?,那么可以通过替换battd文件来修複)
Etc文件夹(本文件夹里存放着这种配置文件,包括WIFI,蓝牙和基带{\etc\motorola\bp_nvm_default}配置文件,A- GPS的文件和Hosts文件也在这里,media_profiles.xml可以控制录像质量,cameraCalFileDef5M.bin与 cameraCalFileDef8M.bin是照相的数据库文件,都可以修改,子文件夹init.d文件夹下可以写入自己的开机脚本,来实现一些程序或 者脚本开机啓动,如果想要执行某模块,可以添加一个文件,不要有后缀名,格式爲00aaa  00是执行序号,系统开机时会按顺序执行,aaa是爲了区别 每个脚本的名称,可以随便写)
Font文件夹(字体文件夹,没什么好解释的,你可以替换爲你喜欢的字体)
Framework文件夹(系统框架结构文件目录,这个目录下是系统服务和系统界面的所在地,一般不可以单独替换,要移植的话必须全部替换,不然会卡 M,framework-res.apk文件就是系统语言,界面的所在文件,修改他就可以直接修改系统界面,framework-res.apk必须与 app文件夹中systemui.apk配套,不然开机会出现状态栏FC,不同版本不可以乱用,比如CM7.1.0.5不能用CM7.1.0.4的文件)
Lib文件夹(本文件夹是系统所需要用到的驱动,库文件的所在,如果某程序FC或者无法正常运转,可能是这里的问题,我把我知道的一些文件的用途说一下:
***Lib/dsp文件夹 DSP驱动所在,一般不需要改
***lib/egl 显示芯片驱动,一般不需要改
***libaudio.so 话筒及音频驱动 必须用701的
***libaudioflinger.so 音频附属驱动 必须用701的
***libbridge.so 相机方面
***libbattd.so  电池驱动
***libcamera.so 相机驱动 必须用701的
***libcameraservice.so 相机服务驱动
***libfmradio.so FM驱动
***libHPImgApi.so 图片接口驱动 必须用701的
***libLCML.so 未知驱动  必须用701的
***liboemcamera.so 相机驱动  必须用701的
***libOMX.TI.JPEG.Encoder.so 照片编码器  必须用701的 否则照片黑
***libOMX.TI.AAC.decode.so  此类型的都是编码器和解码器驱动(多媒体)
除上述红色文件必须用701外,其他文件可以直接用702或者720的。
Media文件夹(铃声及开机动画)
TTS文件夹(文字转语音文件夹)
Usr文件夹(键盘文件与各类数据库)
Xbin文件夹(系统工具文件夹)
Build.prop文件(系统参数文件,修改这里可以调节系统部分参数与性能)
如:默认虚拟机大小,铃声延迟,系统版本等
posted @ 2013-02-17 14:48 小果子 阅读(414) | 评论 (0)编辑 收藏

在项目中,我们经常要实现以下编辑器的所见所得效果,当然也要支持回车,在此做下记录,对正则表达式还不是很熟练。

将HTML换行标签替换为文本域换行符:
textereaContext = divHTML.replace(/(<br>)/g, “\r\n”);

将文本域换行符替换为HTML换行标签:
divHTML =  textereaContext .replace(/\n|\r|(\r\n)|(\u0085)|(\u2028)|(\u2029)/g, “<br>”);
posted @ 2013-02-04 15:41 小果子 阅读(4112) | 评论 (2)编辑 收藏

在PHP中查找中文字符,有两种方案。

1、中文字符是gbk(gb2312)

有两种解决方法

第一种:

将PHP保存为ASCII编码,然后使用strpos查找,如:

strpos($curl_res, ‘哈哈’)

第二种:

将PHP保存为UTF-8无BOM编码,然后转换字符串编码为UTF-8,再查找,如:

$curl_res = mb_convert_encoding($curl_res, ‘utf-8′, ‘gbk’);

mb_strpos($curl_res, ‘哈哈’);

2、中文字符是UTF-8

有两种解决方法

第一种:

将PHP保存为UTF-8无BOM编码,然后使用strpos查找,如:

strpos($curl_res, ‘哈哈’)

第二种:

将PHP保存为ASCII编码,然后转换字符串编码为gbk,再查找,如:

$curl_res = mb_convert_encoding($curl_res, ‘gbk’, ‘utf-8′);

mb_strpos($curl_res, ‘哈哈’);

应该可以看出一些规律,就是:函数中的中文字符串参数的编码和PHP文件保存格式的编码一致,在使用函数时要考虑到!


     我生成的那个html文件被EmEditor认为UTF-8 with Signature。而好用的那个html文件被EmEditor认为UTF-8 without Signature.
    对于这两种UTF-8格式的转换,我查看了网上信息,点击记事本,EmEditor等文本编辑器的另存为,当选择了UTF-8的编码格式时,Add a Unicode Signature(BOM)这个选项被激活,只要选择上,我的文件就可以存为UTF-8 with Signature的格式。可是,问题就在于,我用java怎么让我的文件直接生成为 UTF-8 with Signature的格式。
    开始上google搜索UTF-8 with Signature,BOM,Add a Unicode Signature等关键字。
http://www.unicode.org/unicode/faq/utf_bom.html#BOM
我大致了解了他们两个的区别。
Q: What is a BOM?

A: A byte order mark (BOM) consists of the character code U+FEFF at the beginning of a data stream, where it can be used as a signature defining the byte order and encoding form, primarily of unmarked plaintext files. Under some higher level protocols, use of a BOM may be mandatory (or prohibited) in the Unicode data stream defined in that protocol.
http://mindprod.com/jgloss/bom.html
BOM
Byte Order Marks are special characters at the beginning of a Unicode file to indicate whether it is big or little endian, in other words does the high or low order byte come first. These codes also tell whether the encoding is 8, 16 or 32 bit. You can recognise Unicode files by their starting byte order marks, and by the way Unicode-16 files are half zeroes and Unicode-32 files are three-quarters zeros. Unicode Endian Markers
Byte-order mark Description
EF BB BF UTF-8
FF FE UTF-16 aka UCS-2, little endian
FE FF UTF-16 aka UCS-2, big endian
00 00 FF FE UTF-32 aka UCS-4, little endian.
00 00 FE FF UTF-32 aka UCS-4, big-endian.
There are also variants of these encodings that have an implied endian marker.
Unfortunately, often applications, even Javac.exe, choke on these byte order marks. Java Readers don't automatically filter them out. There is not much you can do but manually remove them.


http://cache.baidu.com/c?word=java%2Cbom&url=http%3A//tgdem530%2Eblogchina%2Ecom/&b=0&a=1&user=baidu
c、UTF的字节序和BOM
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。 例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是 “奎”还是“乙”?

Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:

在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。

这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。

UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。

Windows就是使用BOM来标记文本文件的编码方式的。


原来BOM是在文件的开始加了几个字节作为标记。有了这个标记,一些协议和系统才能识别。好,看看怎么加上这写字节。
终于在这里找到了
http://mindprod.com/jgloss/encoding.html 
UTF-8 
8-bit encoded Unicode. neé UTF8. Optional marker on front of file: EF BB BF for reading. Unfortunately, OutputStreamWriter does not automatically insert the marker on writing. Notepad can't read the file without this marker. Now the question is, how do you get that marker in there? You can't just emit the bytes EF BB BF since they will be encoded and changed. However, the solution is quite simple. prw.write( '\ufeff' ); at the head of the file. This will be encoded as EF BB BF.
DataOutputStreams have a binary length count in front of each string. Endianness does not apply to 8-bit encodings. Java DataOutputStream and ObjectOutputStream uses a slight variant of kosher UTF-8. To aid with compatibility with C in JNI, the null byte '\u0000' is encoded in 2-byte format rather than 1-byte, so that the encoded strings never have embedded nulls. Only the 1-byte, 2-byte, and 3-byte formats are used. Supplementary characters, (above 0xffff), are represented in the form of surrogate pairs (a pair of encoded 16 bit characters in a special range), rather than directly encoding the character.
 
prw.write( '\ufeff' );就是这个。
于是我的代码变为:
public void htmlWrite(String charsetName) {
        try {
            out = new BufferedWriter(new OutputStreamWriter(
                        new FileOutputStream(outFileName), "UTF-8"));
            out.write('\ufeff');
            out.write(res);
            out.flush();

            if (out != null) {
                out.close();
            }
        } catch (Exception e) {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e1) {
                System.out.print("write errors!" + e);
            }

            System.out.print("write errors!" + e);
        }
    }
问题解决。

posted @ 2013-02-04 15:38 小果子 阅读(2905) | 评论 (0)编辑 收藏
仅列出标题
共58页: First 2 3 4 5 6 7 8 9 10 Last