Blog


二维码扫描开源库ZXing定制化

最近在用ZXing这个开源库做二维码的扫描模块,开发过程的一些代码修改和裁剪的经验和大家分享一下。

建议:

如果需要集成到自己的app上,而不是做一个demo,不推荐用ZXing的Android外围开发模块,只用核心的core目录的代码就好了。android和android-core的代码设计的不好,扩展性太差了(我在后期开发新需求的时候改修改了很多CameraManager的逻辑)。只使用core目录的集成方法很简单,参考:

https://github.com/zxing/zxing/blob/master/android/src/com/google/zxing/client/android/DecodeHandler.java

中的decode函数,把摄像头数据转换成二值化图像,然后传入MultiFormatReader解码。

 

以下是正文:

我的代码库:(基于官方3.2.0)

https://github.com/SickWorm/ZXingDialog

代码没有在github维护,所以没有log。但是所有修改的地方我都加上了“@ch”的注释,以方便定位

官方源码:

https://github.com/zxing/zxing

 

实现功能:

1、功能裁剪(只保留QRCode二维码扫描功能,去掉条形码等其他码扫描功能)

2、移除资源依赖,提供Dialog形式的扫码功能

3、API兼容(源码只兼容4.0以上,现兼容至2.1)

4、转换为竖屏(源码为横屏)

5、扫码速度优化(主要分三点,现只完成了一点)

6、设备兼容(针对低分辨率设备)

本文还会提到:

7、自定义界面

8、优化调试方法

 

1、建立工程

ZXing源码并没有提供一个完整的实例工程给我们使用,构建一个工程我们需要源码下的三个文件夹的文件:

core/

android-core/

android/

大概步骤如下:

1、创建一个新工程

2、把android目录下的所有文件覆盖到新工程(内含有资源文件和AndroidManifest.xml等构建app所需的文件)

3、把android-core所有Java文件拷入到src目录下(注意!android-core中的src文件夹需要进行一些改动,原来路径是android-coresrcmainjavacomgooglezxingclientandroidcamera,我们要把中间的mainjava两层文件夹去掉,不然在Eclipse中无法识别包路径)

4、把core目录下的所有Java文件拷入到src目录下(注意!和步骤3一样需要去掉mainjava两层文件夹)。这样ZXing已经可以运行了,我的src目录是这样的:

可以直接运行,效果还不错。如果你遇到一些错误,有可能是编译的JDK版本低于1.7导致的。源码里使用了ArrayList<>这样的写法,1.7以前是不支持的。你可以选择修改源码或者提高编译JDK版本。

但你可能不满足于这个界面,扫描框太大了,而且是横屏全屏的,还要求API 15(Android 4.0.3)。下面我们会对这些需求进行修改。

2、代码优化

1、功能裁剪(只保留QRCode二维码扫描功能,去掉条形码等其他码扫描功能)

我的目标是只保留二维码识别,不需要其他多余的功能。这一部分的步骤我不打算详细说明,因为我已经不记得了。。大家可以直接看我的代码的结果。

可以直接删掉的是:

com.google.zxing.aztec.**  aztec格式的二维码

com.google.zxing.client.android.book.*  Google 图书相关的功能

com.google.zxing.client.android.clickboard.*  不清楚,复制黏贴?

com.google.zxing.client.android.encode.*  用于生成各种码

com.google.zxing.client.android.history.*  保存扫码记录

com.google.zxing.client.android.result.**  扫码应用功能相关的功能性代码

com.google.zxing.client.android.share.*  分享功能

com.google.zxing.client.android.wifi.*  WiFi相关,不清楚具体用途

com.google.zxing.datamatrix.**  datamatrix格式二维码

com.google.zxing.maxicode.**  maxicode格式二维码

com.google.zxing.multi.**  貌似是用于多格式支持的?我没有用到这个包,如果有了解的麻烦告知

com.google.zxing.oned.**  one dimension一维码,也就是条形码(你去百度搜oned会发现奇怪的东西。。)

com.google.zxing.pdf417.**  PDF417格式条形码

需要修改的是:

com.google.zxing.client.android.CaptureActivity:去掉其他功能的相关代码,只保留核心功能,即扫码功能。界面为一个FrameLayout里面包含一个SurfaceView。代码移除就不详细说了,直接看上传的代码吧,这个文件我参考了http://www.cnblogs.com/keyindex/archive/2011/06/08/2074900.html这个链接里的CaptureActivity的修改。

com.google.zxing.MultiFormatReader:这个是指定支持解码的格式,需要把除QR_CODE以外的格式全部去掉,否则会因为删掉了解码包而报错。具体也请看上传的代码。

com.google.zxing.client.result.ProductResultParser:parse函数中,同上。

com.google.zxing.client.android.DecodeThread:构造器中,同上。

另外:

com.google.zxing.client.result 我没有删减这个包的代码,应该也是能优化的

2、移除资源依赖,提供Dialog形式的扫码功能

经过了第1步的精简,其实只剩下了2个地方需要修改:

1.扫描界面

2.扫描成功时播放的beep声音文件

 

1:去除了其余功能后,对于核心功能我们只需要一个SurfaceView和一个画界面的View就可以了。代码如下:

/** * use Java code to build layout instead of xml file * @ch */
private void buildLayout() {
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    FrameLayout layout = new FrameLayout(this); 
    LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 
    layout.setLayoutParams(params); 
    surfaceView = new SurfaceView(this); 
    surfaceView.setLayoutParams(params); 
    layout.addView(surfaceView); 
    viewfinderView = new ViewfinderView(this, null); 
    layout.addView(viewfinderView); 
    setContentView(layout); 
}

ViewfinderView是ZXing自带的View,如果要修改界面,直接修改它就可以了,我们第7点会提到。

2:由于我最终的目的是能打包成jar包,所以beep文件不能放在res里,而是放在assets里。

//待补充

 

3、API兼容(源码只兼容4.0以上,现兼容至2.1)

这部分修改在源码中标记为//@ch api compatible。

CaptureActivity.java:

if (VERSION.SDK_INT < 11) {
    //surfaceview will push buffer automatically
    surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } 
    //API 11之前需要手动设置,否则会无法显示。API11之后为默认设置。
    CameraConfigurationManager.java: //@ch api compatible
    if (VERSION.SDK_INT < 13) { 
        theScreenResolution.x = display.getWidth(); 
        theScreenResolution.y = display.getHeight(); } else { display.getSize(theScreenResolution); 
    } 
    //getSize是API 13之后的新API,之前需要用getWidth和getHeight。

AutoFocusManager.java:

if (VERSION.SDK_INT > 11) { 
    newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 
} else { 
    newTask.execute(); 
} 

// 源码中这里设置了多线程的模式,THREAD_POOL_EXECUTOR表示这个task最多只有5个线程同时运行,超过5个的就要等待。在低于API 11的版本中,此为默认选项。其实这里只有单线程,所以随便执行吧。

OpenCameraInterface.java:

if (VERSION.SDK_INT < 9) { 
    return openWithLowApi(); 
} ...... /** * for lower than API 9 * @ch api compatible */
    
public static Camera openWithLowApi() { 
    //If the device does not have a back-facing camera, this returns null
    Camera camera = Camera.open(); 
    return camera; 
} 
//源码的打开摄像头是能区分前后摄像头的,然而API 9之前并没有前置摄像头这个概念,所以做了一下处理

就几个地方,不过也找了我个把小时了。

 

4、转换为竖屏(源码为横屏)

ZXing默认是横屏,但是我们一般的APP都会做成竖屏,如果扫码的时候强制切换成横屏那样体验就不好了。在修改ZXing的竖屏的时候,我按照的是一般APP的竖屏设置方法,结果发现没有源码的效果好,需要把码放到很小才能完成。后面在调试过程中发现扫码解析的区域和屏幕画出来的区域不一样,才知道这部分的修改出了问题。然后我搜索找到一篇前辈的文章,参考了一下发现没有改完全。附上文件链接:

http://blog.csdn.net/aaawqqq/article/details/24804939

其中第五点我没有修改,文章中的源码可能比较旧,并不适合替换。第5点不替换替换是没有问题的。

此外还有一点需要修改的是:

CameraManager.java:

//@ch change 240 to 120 for 320x240 machine
private static final int MIN_FRAME_WIDTH = 120; private static final int MIN_FRAME_HEIGHT = 120; //@ch change to vertical
private static final int MAX_FRAME_HEIGHT = (int) 1080; 
private static final int MAX_FRAME_WIDTH = (int) 1080; 
//此处为扫描框最大边界和最小边界的界定。但因为我最后做成了正方形,所以这里数值是一样的。如果为矩形,需要把两个值交换一下。 //设置最小值是为了保证解码的成功率,毕竟分辨率太小就没法识别了。最大值是为了保证解码速度。其实最大值应该通过插值来重新构图,不然框的大小不一致体验就不好了。不过这样的分辨率起码是2K屏以上了,所以最大值设定不会有什么影响

5、扫码速度优化(主要分三点,现只完成了一点)

1.二值化算法优化。ZXing一共提供了两种二值化算法,一种是HybridBinarizer,另一种是GlobalHistogramBinarizer,默认使用的是HybridBinarizer。我搜集资料的时候发现,HybridBinarizer算法用了更高级的算法,运算要求更高,而总体来讲GlobalHistogramBinarizer识别率要更高。在我的实际测试中(我用的Nexus5,Android 5.0.1),GlobalHistogramBinarizer效果确实是要好不少。

这里的修改很简单,换一个类就可以了:
DecodeHandler.java:

private void decode(byte[] data, int width, int height) {
    ...... 
    //@ch you can use HybridBinarizer or GlobalHistogramBinarizer 
    //but in most of situations HybridBinarizer is shit
    BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source)); 
    ...... 
}

2.对焦优化。ZXing中的对焦功能在AutoFocusManager.java中,功能非常简单,设置自动对焦并2秒对焦一次。但自动对焦可能会带来一个问题,如下图:

jingshen

(图片源自网络)

把二维码当作图中的那朵花,自动对焦则容易使摄像头对焦到背景(图中女性)中去。我在测试中使用三星S4的自动对焦经常对不了二维码。这里我们可以使用区域对焦的方法(对焦的区域即是扫描框的区域):

//定点对焦的代码 
private void pointFocus(int x, int y) {
    if (cameraParameters.getMaxNumMeteringAreas() > 0) { 
        List<Camera.Area> areas = new ArrayList<Camera.Area>(); 
        Rect area = new Rect(x - 100, y - 100, x + 100, y + 100); 
        areas.add(new Camera.Area(area, 600));
        cameraParameters.setMeteringAreas(areas); 
    } 
    mCamera.cancelAutoFocus(); 
    cameraParameters.setFocusMode(Parameters.FOCUS_MODE_AUTO); 
    mCamera.setParameters(cameraParameters); 
    mCamera.autoFocus(autoFocusCallBack); 
}

区域对焦需要API 14以上。关于对焦暂时没有更多的想法,但我觉得确实是有优化的余地的。

3.算法优化。算法主要分两部分,第一部分是二值化,第二部分是提取码值。第二部分又分为1.寻找定位符,2.寻找校正符,3.转换矩阵。在测试过程中,影响识别的最大问题就是找不到定位符,即二维码左上角、右上角、左下角的三个黑白相间的矩形点。比较大的原因可能是二值化部分的问题。这一部分暂时也还没有深入。

6、设备兼容(针对低分辨率设备)

CameraManager.java中有设置最小扫描框大小的参数MIN_FRAME_WIDTH和MIN_FRAME_HEIGHT。默认是320×240。这对于屏幕分辨率为320×240的设备,扫描框就会变成全屏的。这里我改成了120×120,实际在屏幕分辨率为320×240的设备上也可以扫到二维码。

 

7、自定义界面

界面写在ViewFinderView.java中。扫描框大小由CameraManager决定。修改的时候需要注意和CameraManager的配置关联起来,否则会出现扫描框和实际解码的区域不一致。(ZXing的android外围模块代码默认使用的是全屏,如果你想改为非全屏(比如加一个action bar),肯定会造成扫描框区域和实际解码的区域不一致的问题。这也是文首建议只使用core模块的原因)

 

8、优化调试方法

为了应对7可能带来的问题,我自己做了一个调试的方法,以保证扫描框内容和实际解码内容一致。首先在CaptureActivity.java初始化cameraManager的地方,把

cameraManager = new CameraManager(getApplication())

改为cameraManager = new CameraManager(this)

这样做的目的是:传入了activity给CameraManager,使得后面在CameraManager中的调试内容可以直接输出在activity上(dialog也一样,改为getContext()即可)。但注意这样会带来Activity生命周期问题和因为互相引用导致内存泄漏的问题。所以只能在调试阶段修改成上面的写法。

然后在CameraManager.buildLumianceSource中加入:

if (context instanceof Activity) {
    final Bitmap bitmap = Bitmap.createBitmap(BitmapUtil.createBitmapfromYUV420(data, width, height), rect.left, rect.top, rect.width(), rect.height()); 
    CaptureActivity activity = ((CaptureActivity) context); 
    final ViewfinderView view = activity.getViewfinderView(); 
    activity.runOnUiThread(new Runnable() { 
        @Override 
        public void run() { 
            view.drawResultBitmap(bitmap); 
        } 
    }); 
}

这样,屏幕上的扫描框中就会显示出实际解码的图像,你可以通过比对预览图像查看是否对齐。

记得调试完改回来getApplication()

 

版权所有,转载请注明出处:

http://sickworm.com/?p=41




【Android XML】Android XML 转 Java Code 系列之 style(3)

最近一个月把代码重构了一遍, 感觉舒服多了, 但总体开发进度没有变化.. 今天聊聊把style属性转换成Java代码的办法

 

先说结论: 引用系统style是无法完美的实现的, 我们如果有写成Java代码的需求, 请尽量避免使用系统style. 自定义style没问题.

 

style是什么?

(参考链接) http://developer.android.com/guide/topics/resources/style-resource.html

” A style resource defines the format and look for a UI. A style can be applied to an individual View (from within a layout file) or to an entireActivity or application (from within the manifest file).”

更通俗的理解是, style其实是放置一组attribute的宏, 在控件中指定这个style, 将在xml解析时将style的一组属性应用到该控件中. 举个栗子:

这里有个style, 定义在res/value中:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="CustomText" parent="@style/Text">
        <item name="android:textSize">20sp</item>
        <item name="android:textColor">#008</item>
    </style>
</resources>

然后我们在layout中的某个控件引用了这个style:

<?xml version="1.0" encoding="utf-8"?>
<EditText style="@style/CustomText" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Hello, World!" />

 

实际上, 他是等于:

<?xml version="1.0" encoding="utf-8"?>
<EditText android:textSize="20sp" android:textColor="#008" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Hello, World!" />

有点宏替换的味道, 如果接触过html的同学可以很容易想到CSS这种东西, 两个想要做的基本是一样的.

需要指明的是: style的属性的优先级是最低的, 会被控件的其他相同属性覆盖掉. 这是理所当然的, 不然控件里的attribute要不服了(你都把我写出来了, 居然和我说我没用, 要被style那货覆盖掉?!)

 

让我们再回到这个被我们定义的style上, 会发现他还有一个叫parent的属性. 这里我们用面向对象的父类来理解, 就是子style会继承父类的属性. 同样的, 父类的属性要比子类属性优先级要低. (你都把我写出来了, 居然和我说我没用, 要被老爸的style覆盖掉?!)

 

style不是什么?

style不属于android Namespace中的属性 (不以android:开头), 没有对应的setStyle方法可以使用, 我的理解是它属于xml预加载的一种”机制”.

 

自定义style的翻译:

那么对于自定义的style, 翻译就很简单了.

对于一个style, 将其替换成style中的attribute集合, 并向上 (父类) 继续增加父类集合, 并注意优先级 (子类可以覆盖父类attribute).

 

系统style的翻译:

系统style的翻译主要存在两个问题:

1. 系统style是内置在系统内部的, 不同的系统style并不一样, 特别是深度定制化的如MIUI, Flyme等操作系统. 如果只是使用一些简单的比较标准的style, 如progressbar等, 可以通过SDK Manager下载最新Android版本的API, 然后在路径platformsandroid-20dataresvalues中找到标准的系统style, 或直接下载ASOP(路径framework/base/core/res/res/values/styles.xml. (我在项目中用到的是4.4的style文件, 5.0的sytle文件分成了多个, 而且里面的命名有点不规范)

有些同学会发现, 如progressbar里面的style: android:attr/progressBarStyleSmall, 是用android:attr索引的. android:attr其实是在theme中定义的属性, apk的theme在AndroidManifest.xml中定义. 在解析xml过程中, 遇到android:attr的时候, 他就会在apk指定的theme中找到相应的item. 这个item对应的值就是style的值, 如:

style=?android:attr/progressBarStyleSmall 对应 最基本的Theme中的 <item name="progressBarStyleSmall">@android:style/Widget.ProgressBar.Small</item>

然后你再去styles.xml中寻找Widget.ProgressBar.Small. (这里还有个要注意的是, 就是系统的style不会声明parent属性, 但是以点”.”做层级, 比如这里的Widget.ProgressBar.Small父类是Widget.ProgressBar)

这样带来的另一个问题, 深度定制的操作系统各种主题也会有改动, 这样造成更大的偏差.

 

2. 系统style用到的资源, 有些是我们从外部无法获取的. 还是举Widget.ProgressBar.Small的例子:

    <style name="Widget.ProgressBar.Small">
        <item name="android:indeterminateDrawable">@android:drawable/progress_small_white</item>
        <item name="android:minWidth">16dip</item>
        <item name="android:maxWidth">16dip</item>
        <item name="android:minHeight">16dip</item>
        <item name="android:maxHeight">16dip</item>
    </style>

他里面用到了@android:drawable/progress_small_white这个系统资源, 但是这个资源不是public的, 我们实现的时候没办法把这个资源调用出来. 有些同学会想到反射, 但反射也是不行的. 因为所有的系统资源在ASOP编译的时候会生成类似于R.java这样的资源索引文件, 这些文件用于我们使用系统资源的id来调用系统资源. 而非public是不会声明出来的, 也就是说你连id都无法知道, 也就无法获取对应资源了. 我曾大致阅读了一下xml的解析实现, 发现到最后都进入了native层, 在native层用c/c++实现, 这让我望code兴叹..

不过ProgressBar的各种样式应该可以通过api来设置出来, 这个我还没证实. 可以参考: [Android实例] Android 在Java代码中设置style属性–使用代码创建ProgressBar对象

对这个非public系统资源获取有经验的同学欢迎交流.

 

项目代码:

我在项目中实现了构建style元素, 使用style元素替换为attritutes, 以及style元素之间的关系, 包括对theme的android:attr寻找:

https://github.com/SickWorm/AndroidXMLToJava/blob/master/src/com/excelsecu/androidx2j/AX2JStyle.java

 

版权所有,转载请注明出处:

http://sickworm.com/?p=36




【Android XML】Android XML 转 Java Code 系列之 Selector(2)

今天我们要把drawable下的selector的XML文件转换成Java代码。(打包进jar,不依赖apk)

在转换工具中的代码为:

https://github.com/SickWorm/AndroidXMLToJava/blob/master/src/com/excelsecu/androidx2j/SelectorTranslator.java

 

Selector是什么?就是给Button等控件使用的一个根据状态改变控件颜色或背景的状态器,它一般放在drawable目录下。

Selector分两种,一种是指定color和alpha的状态器,XML形式如下:

 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_focused="true" android:color="@color/testcolor1"/>
   <item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" />
   <item android:state_enabled="false" android:color="@color/testcolor3" />
   <item android:color="@color/testcolor5" android:alpha="0.5" />
 </selector>

另一种是指定drawable的状态器,XML形式如下:

 <selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_focused="true" android:drawable="@drawable/testdrawable1"/>
   <item android:state_pressed="true" android:state_enabled="false" android:drawable="@drawable/testdrawable2" />
   <item android:state_enabled="false" android:drawable="@drawable/testdrawable3" />
   <item android:drawable="@color/testdrawable5"/>
 </selector>

 

知道了XML格式,我们如何去找到对应实现的类呢?这时候就应该上万能的http://developer.android.com/(科学上网,我用红杏我自豪)寻找selector。点击第一个链接:

http://developer.android.com/guide/topics/resources/color-list-resource.html

在这个文档上我们可以明显看到第一个XML对应的类就是ColorStateList(里面介绍的第一个就是)。而第二个XML,我一开始找了很久都没找到,直到我点开文档最下方的State List Drawable,发现赫然写着:

StateListDrawable(原谅我激动的心情)

点进这个类看到对应的XML布局,确定这就是第二个XML布局啦。

 

找到了对应的类,接下来要开始正式的工作了。我们先从ColorStateList下手。

所谓转换,其实就是构造一个对应类的对象,然后把XML的属性对应的内容用Java代码的方式“填充”进这个对象中便完成了。在使用到这个XML的地方,也用对应的设置方法把该成员设置进去就可以了。

那首先找到ColorStateL的构造函数:ColorStateList(int[][] states, int[] colors);

一开始便难倒我了。。这诡异的两个int数组是shenmegui?看看人家控件家族(Button, TextView等等)只需要一个context就可以构造了。根据名字猜测,这两个数组估计是对应的各种状态和颜色。那我们接下去认证这个猜想。

说时迟那时快,我发现了另一个函数:createFromXml(Resources r, XmlPullParser parser);

该函数就是给我们用Java代码动态解析XML用的方法。里面肯定有对XML各参数解析的过程,赶紧看一下源代码(作为一个开源操作系统的程序猿,一定要下一份源码)。

    /** * Create a ColorStateList from an XML document, given a set of {@link Resources}. */
    public static ColorStateList createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException { final AttributeSet attrs = Xml.asAttributeSet(parser); int type; while ((type=parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException("No start tag found"); } return createFromXmlInner(r, parser, attrs); }

我们跟着这个XmlPullparser走,因为他里面藏着XML信息。结果发现他调用了createFromXmlInner。继续跟,发现调用了colorStateList.inflate。看inflate就知道是解析函数啦!那我们来详细分析这个inflate:

(下面是我分析inflate函数时的笔记,应该算比较详细了)

    /** * Fill in this object based on the contents of an XML "selector" element. */
    //解析XmlPullparser, (剧透)最终得到 mColors 和 mStateSpecs, 这两个参数用于构造ColorStateList
    private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { int type; final int innerDepth = parser.getDepth()+1; int depth; int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20); int[] colorList = new int[stateSpecList.length]; int listSize = 0; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT && ((depth=parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { if (type != XmlPullParser.START_TAG) { continue; } //只解析item项
            if (depth > innerDepth || !parser.getName().equals("item")) { continue; } int alphaRes = 0; float alpha = 1.0f; int colorRes = 0; int color = 0xffff0000; boolean haveColor = false; int i; int j = 0; final int numAttrs = attrs.getAttributeCount(); int[] stateSpec = new int[numAttrs]; //对item项的各个属性做分析
            for (i = 0; i < numAttrs; i++) { //获取属性名对应的id, 如果没有则返回0, 其实就是把字符匹配的工作交出去, 提高代码灵活性
                final int stateResId = attrs.getAttributeNameResource(i); if (stateResId == 0) break; //如果是alpha属性则赋值alpha. alpha是一个透明度的值
                if (stateResId == com.android.internal.R.attr.alpha) { //获取属性值对应的资源id, 就是我们熟悉的R.java里面的东西啦, 比如R.drawable.button1 = 0x7f040001这样的值
                    alphaRes = attrs.getAttributeResourceValue(i, 0); //如果不是资源id, 是常数, 则直接翻译
                    if (alphaRes == 0) { alpha = attrs.getAttributeFloatValue(i, 1.0f); } //如果是color属性, 也做类似的处理. color就是对应指定状态下的背景或颜色
                } else if (stateResId == com.android.internal.R.attr.color) { colorRes = attrs.getAttributeResourceValue(i, 0); if (colorRes == 0) { color = attrs.getAttributeIntValue(i, color); haveColor = true; } //不是上面两个条件的话, 只能是设置状态的布尔值了, 如android:state_pressed="true"
                } else { //此时会把属性名对应的id存进数组, 并根据是否设置正负, 这样selector的各种状态条件就构建出来了 //具体的id值是多少, 是R.attr中的属性值吗(如state_pressed就是android.R.attr.state_pressed)?我们下面再仔细分析
                    stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) ? stateResId : -stateResId; } } //这个trimStateSet的用处是重新指定此int数组的长度, 也就是j, stateSpec初始化的时候是使用了属性数量作为长度, 在这里会被重新设置, 以节省空间. //因为上一条语句会做j++, 所以永远空出一个位置给下一个属性添加 //数组很小, 所以不会很费时间, 牺牲一点性能换取简单的写法也是可取的. 如果是我我会在循环开始的时候先找出有多少个设置状态的属性.
            stateSpec = StateSet.trimStateSet(stateSpec, j); //这里对刚才获取的资源值做获取, 并做一些结构完整性检查, 如果一个item连color都没有, 那真是坑爹了
            if (colorRes != 0) { color = r.getColor(colorRes); } else if (!haveColor) { throw new XmlPullParserException( parser.getPositionDescription() + ": <item> tag requires a 'android:color' attribute."); } //alpha就可有可无了, 如果没有, 默认是1 (初始化的值float alpha = 1.0f;), 也就是100%不透明
            if (alphaRes != 0) { alpha = r.getFloat(alphaRes); } //稍微转换一下, 把color算出来 // Apply alpha modulation. //算出alpha. Color.alpha(color)看源码, 就是 color >>> 24. 这个运算是算出color的alpha值 (int color 长度为4个字节, 3个字节分别代表RGB, 还有一个最高位字节的alpha喔) //比如#AAFFFFFF的alpha值就是AA. 但要注意的是#FFFFFF的alpha值是FF, 因为Android的Color.parse会自动给没填写alpha的值的最高位填上FF(具体看源代码).        //如果你使用int形式, 如0xaaaaaa, alpha值就是0, 这样写是会全透明的. 用int形式一定要写上alpha值, 如全黑是0xff000000. //所以一个item真正的alpha值是由color自带的alpha和android:alpha的值共同决定的 //MathUtils.constrain这个函数不用看都能知道是百分比缩放的函数, 比如0.5, 0, 255, 的出来的就是(255 - 0) * 0.5
            final int alphaMod = MathUtils.constrain((int) (Color.alpha(color) * alpha), 0, 255); //再把这个0~255的值塞回color的最高字节里面去 (折腾啊)
            color = (color & 0xFFFFFF) | (alphaMod << 24); //如果只有这个android:color这个属性, 没有表示状态的属性, 就认为他是默认色
            if (listSize == 0 || stateSpec.length == 0) { mDefaultColor = color; } //把color 和 stateSpec塞进数组里面去. 比用ArrayList要节省点空间. 看来Android在节省内存方面上很卖力啊 //append能猜到又是重新定义数组长度这种事情, 不同的是会加一个数组进去
            colorList = GrowingArrayUtils.append(colorList, listSize, color); stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec); listSize++; } //所有的item都跑完了, 可以把结果存给全局变量mColors 和 mStateSpecs了.
        mColors = new int[listSize]; mStateSpecs = new int[listSize][]; System.arraycopy(colorList, 0, mColors, 0, listSize); System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize); }

我们现在来看下ColorStateList的构造函数:

    public ColorStateList(int[][] states, int[] colors) { mStateSpecs = states; mColors = colors; if (states.length > 0) { mDefaultColor = colors[0]; for (int i = 0; i < states.length; i++) { if (states[i].length == 0) { mDefaultColor = colors[i]; } } } }

inflate得到的两个最终产物 mStateSpecs 和 mColors 就是我们想要的构造函数!我们只要照葫芦画瓢,照inflate画数组,就可以把这两个数组写出来了。ColorStateList被降服了!

喔,忘了还有一个遗留问题:

穿山甲到底说了什么?

stateResId到底是什么?

再看分析:

//首先这个id是从AttributeSet中得到的,AttributeSet是一个interface,实现它的是XmlPullAttributes(为什么?因为搜遍了源码就他一个...),但可惜的是XmlPullAttributes没有实现getAttributeNameResource() //继续找儿子。BridgeXmlPullAttributes 继承了XmlPullAttributes,而且他实现了getAttributeNameResource()
    BridgeXmlPullAttributes extends XmlPullAttributes //This methods must return com.android.internal.R.attr.<name> matching the name of the attribute.
        BridgeXmlPullAttributes.getAttributeNameResource(int index) -> Bridge.getResourceId(ResourceType.ATTR, name); ->->        Integer v = Bridge.getResourceId(ResourceType.ATTR, name); ->->-> sRevRMap.get(type); if (v != null) { return v.intValue(); } //这里写的有点乱,“->”的意思是深入一层,同样的梯度表示在同一个函数中,也就是说getAttributeNameResource调用了Bridge.getResourceId调用了sRevRMap.get,最后Bridge.getResourceId返回了sRevRMap.get的int值。 //这个sRevRMap是在哪初始化的呢?搜源码发现在init中()
 init(...) { ... Class<?> r = com.android.internal.R.class; //这里这里!init函数把这个class里面的所有子类声明的值put进去sRevRMap了。 //这个com.android.internal.R.class是在Android ROM编译的时候生成的一个类,就类似我们写应用时自动生成的R.java,里面全部是声明的值。 //而我们getResourceId的时候传的type是ResourceType.ATTR。所以就是ATTR的id值。这个值在http://developer.android.com/reference/android/R.attr.html中有详细列表。 //实际上我们使用只需要给对应的属性名加上前缀android.R.attr即可。如android:state_pressed对应android.R.attr.state_pressed。 //至于为什么初始化的时候是com.android.internal.R,使用的时候是android.R,应该是涉及到Android系统编译的东西,我没有深入了解。

            for (Class<?> inner : r.getDeclaredClasses()) { String resTypeName = inner.getSimpleName(); ResourceType resType = ResourceType.getEnum(resTypeName); if (resType != null) { Map<String, Integer> fullMap = new HashMap<String, Integer>(); sRevRMap.put(resType, fullMap); for (Field f : inner.getDeclaredFields()) { // only process static final fields. Since the final attribute may have // been altered by layoutlib_create, we only check static
                        int modifiers = f.getModifiers(); if (Modifier.isStatic(modifiers)) { Class<?> type = f.getType(); if (type.isArray() && type.getComponentType() == int.class) { // if the object is an int[] we put it in sRArrayMap using an IntArray // wrapper that properly implements equals and hashcode for the array // objects, as required by the map contract.
                                sRArrayMap.put(new IntArray((int[]) f.get(null)), f.getName()); } else if (type == int.class) { Integer value = (Integer) f.get(null); sRMap.put(value, Pair.of(resType, f.getName())); fullMap.put(f.getName(), value); } else { assert false; } } } } } ... } 

这里“com.android.internal.R,使用的时候是android.R”,说服力可能不足,但这个表http://developer.android.com/reference/android/R.attr.html应该是可靠的,Google不会做两个表出来吧。。

是com.android.internal.R,使用的时候是android.R,

 

接下来还有一个

StateListDrawable(怎么还是这么大)

这个东西就简单的多了,构造器不需要参数,直接new StateListDrawable()即可。添加状态使用addState(int[] stateSet, Drawable drawable)。stateSet和ColorStateList的规则是一样的,drawable就是XML文件里面指定的drawable资源。这里就不多赘述了。

 

注意:

虽然ColorStateList对应的selector XML放在drawable文件夹里面,但他本身是一个自定义类!父类是Object!和

StateListDrawable(啊啊啊啊吓死我了)

不一样,这货父类是Drawable,可以用getDrawable()获取。如果你用getDrawable(R.drawable.colorStateList)获取对应资源的话是会崩溃的。为什么要提到这点呢?因为转换工具(详细介绍看第一篇文章)有一个仿照getResources()专门管理资源的类(叫AXMLResources),因为这个ColorStateList的特殊原因,就要另外用函数处理了。

 

版权所有,转载请注明出处:

http://sickworm.com/?p=34 




【Android XML】Android XML 转 Java Code 系列之 介绍(1)

最近在公司做一个项目,需要把Android界面打包进jar包给客户使用。对绝大部分开发者来说,Android界面的布局以XML文件为主,并辅以少量Java代码进行动态调整。而打包进jar包的代码,意味着无法通过常规的getResources(),getString()等方法来快速的获取资源,因为这些资源都是在apk安装的时候初始化生成的。为了满足客户的需求,笔者开始在网上寻找各种解决方案。结果如下:

 

1.apk 主体包方案

实现方法:安装一个新的apk,新apk和主apk使用android:sharedUserId连接起来,这样主apk就可以获取新apk的所有res资源。需要注意的是,R.id.xxx的值为新apk中的R.java的值。因此需要获取到新apk的R.class,或保证新apk和主apk的R.java中的资源的值是完全一致的,否则会出现获取资源对不上号的情况。
结论:可以实现,但需要多安装一个apk。静默安装apk需要root权限,否则会弹出安装界面。还没有找到不需要安装apk就能获取资源的方法。
参考链接:http://blog.csdn.net/lg707415323/article/details/7709076

2.LayoutInflater 方案

实现方法:通过LayoutInflater类解析外部XML资源,并转换为所需布局。
分析:Android解释因为性能原因,LayoutInflater只支持解析已经预编译在apk包里的xml。所以没办法解析外部导入的xml布局资源。

3.Android XML 转 Java Code方案  (开源/自研)

在线的Android XmToJaval转换服务,免费,支持各种控件(http://www.xmltojava.com/page/controls/ ),支持还算丰富,但转换完还需要一些简单的手动编辑。不支持drawable和values目录下的资源转换。
结论:能减轻手动翻译的工作量,但不能完全解决需求。drawable和string需要自己手动翻译实现。
个人开源项目,第一个项目用php写的,代码量很少,只更新过一次,应该没法用。第二个项目是Androidy应用动态解析,刚刚建立起来的,支持的字段很少,应该也没法用。完成度都很低。在Google和Github上都没有找到成熟的方案。
结论:不可用

4.zip包方案

只能替换图片资源,没办法解析Xml布局。(桌面应用的换肤功能都基于zip或apk这两个方案)
结论:不可行

5.ClassLoader方案

把apk包用ClassLoader加载进来,并通过反射的方法获取Activity或其他组件的各种方法,从而获得资源。
分析:实践发现,反射调用Activity
总结论:方案1最方便,但客户不一定能接受;方案3比较稳妥,但是耗费人力大。

 

由于方案1需要安装apk,如果要静默安装还需要root权限。这种方案过于累赘,只能选择Android XML翻译原生代码的方法。于是就有了AndroidXMLToJava管理工具的诞生。

接下来几篇会分享该转换工具的一些解决过程。项目快完善的时候会放上博客,现在暂时不放出来。

 

 

版权所有,转载请注明出处:

http://sickworm.com/?p=32




【MT8382/8121】为MTK的工厂测试添加测试项

摘要:

本文介绍添加MTK工厂测试项的步骤及调试技巧。

纲要:

1. 描述添加MTK工厂测试项的步骤

2.调试小技巧

1. 描述添加MTK工厂测试项的步骤

以添加红外测试为例:

 

1. mediatek/factory/inc/common.h

enum枚举添加ITEM_IR_TEST到最下面的ITEM_MAX_IDS上面,即倒数第2个;

 

2. mediatek/factory/inc/uistrings_chn.h

添加 #define uistr_ir_test “红外”;

这个字符串用于匹配factory.ini配置文件的测试项,只有两者一致的时候才会添加。注意这个文件是gbk编码。

unistrings.h是选择了英文才会使用,我们现在都使用的是中文,所以会匹配uistring_chn.h

 

3. mediatek/factory/src/factory.c

ftm_test_items[]添加item(ITEM_IR_TEST, uistr_ir_test);(在手动测试和单项测试中显示,必添加)

ftm_auto_test_items[]添加item(ITEM_IR_TEST, uistr_ir_test);(在自动测试中显示,尚不清楚机制,可选)

这两个参数就是步骤1 2添加的项目,名字要一致。

这里一般都会使用宏来控制是否添加,除非是都测的项目,以免运行出错。

 

4. mediatek/factory/src/test/ftm_irtest.c

添加ftm_irtest.c文件。实现ir_test_init(),  ir_test_entry()两个函数。函数名没有要求。具体实现参考ftm_rftest.c。(rftest是通过代码判断是否测试通过的,如果需要手动确认是否通过,参考ftm_otg.c。主要区别在entry函数)

ir_test_init()中需要调用ftm_alloc,这个会注册一个ITEM_IR_TEST相关的结构体。然后调用ftm_register,把ir_test_entry和这个结构体绑定。

ir_test_init()在factory初始化过程中会调用。

ir_test_entry()在项目被点击的时候调用。需要实现一些UI相关的绑定,以及具体的验证方法。通常会启动一个线程如ir_update_thread来测试,entry通过全局变量ir_factory检测测试结果。

 

5. mediatek/factory/src/test/ftm_mods.c

添加extern int ir_test_init();

在ftm_init_funcs[]的倒数第二个(NULL上面)添加ir_test_init。这样在factory启动的时候就会自动调用ir_test_init,从而将ITEM_IR_TEST测试项与ir_test_entry绑定起来。

至此,所有添加项都关联起来了。

 

6.mediatek/factory/Android.mk

将ftm_ir_test.c加进TEST_SRC_FILES中。

同样用宏控制是否添加,这里用到CVTE_IRREMOTE_APP_SUPPORT的宏来控制。

 

7.factory.ini

添加MenuItem=红外;需要和uistr_ir_test 名字一致,否则无法匹配。

这个文件会先去/sdcard目录寻找,再去/etc目录寻找,我们调试的时候直接放进sdcard目录就可以了。

 

8.mediatek/custom/active_1051j/factory/factory.ini

把修改的ini文件放入custom目录下,使用mk文件将其拷贝至/etc目录下。

custom的factory目录会存在factory.ini和factory_chn.ini两个文件。如果是中文,则把factory_chn.ini拷进去,并重命名为factory.ini;英文则直接拷贝。也可以只留一个factory.ini文件,里面是中文。factory程序只会解析名为factory.ini的文件

 

调试小技巧:

1. 烧录eng版本软件,打开USB调试。

2. 重启进入MTK工厂测试。

3. 需要烧录新factory程序的时候,push进/system/bin并chmod 777 /system/bin/factory。

4. 执行ps factory查询当前正在运行的测试程序,kill掉。

5. 在shell中输入factory重新启动MTK工厂测试程序。

6. 再次需要烧录新的factory程序的时候,ctrl+C即可停掉当前的factory程序,然后重新执行步骤3,5。 无需重启。

 

版权所有,转载请注明出处:

http://sickworm.com/?p=30 




【Linux驱动学习】SD卡规范学习

摘要:

学习SD卡的相关规范,包括定义,硬件特性,数据传输,命令系统等。不涉及代码。

文章针对Linux驱动开发而写,以助于理解SD卡驱动,不会涉及过多硬件内容。

纲要:

1. SD卡介绍

2. SD卡硬件规范

3. SD卡指令规范

4. SD卡寄存器

 

1. SD卡介绍

1.1 各类型储存卡/接口

首先了解一下我们在SD卡驱动学习中会碰到的主要几个储存卡名词:

SD:Security Digital Memory Card,新一代多媒体储存卡,高速,安全(但安全机制貌似很少用到)
MMC:Multimedia Card,SD卡的上一代多媒体储存卡,已基本被SD卡代替
eMMC:Embedded Multimedia Card,内嵌式存储器,一般焊在PCB上。内置主控制器,以实现统一MMC接口(在传统MMC接口上拓展,集成了整套理论),Nand Flash就是eMMC
SDIO:Secure Digital Input and Output Card,SD标准上定义了一种外设接口,有很多设备模块采用。如Wifi,GPS,Bluetooth

1.2 SD卡特性

以下是SD卡的部分特性。

  • 正向兼容MMC卡:能插SD卡的接口也可以插MMC卡
  • 最大10个堆叠的卡
  • SD模式和SPI模式
  • 可变时钟(0~25MHz),可变电压(2.0~3.6V)
  • 带电插拔保护
  • 安全系统,双方认证和“新的密码算法”技术

更多的特性请阅读SD卡官方规范。

2. SD卡硬件规范

2.1 SD卡物理接口

下图是SD卡和MMC卡的针脚:

SD,MMC卡针脚

可以看到,SD卡在MMC卡基础上增加了8、9两个针脚,这两个针脚将被用作数据传输,以支持SD传输模式。SD卡支持SD模式(4数据线)和SPI模式(2数据线),MMC卡只支持SPI模式。

SD卡针脚对应的功能:(SD模式)

SD卡针脚对应功能

 

SD模式:数据并行传输,2地,1电源,1时钟,1命令,4数据线(4出入)(SD模式的命令通过命令线传输)
SPI模式:数据串行传输,2地,1电源,1时钟,1片选,2数据线(1入1出)(SPI模式的命令通过数据线传输)

以下内容,如无分开说明,默认指SD模式。(本文不会涉及SPI模式学习)

2.2 SD卡与主机的连接

SD模式和SPI模式中与主机的连接拓扑图如下:

image

在SD模式中,数据线和命令线是分开连接到主机各GPIO口中的。在SPI模式中,片选线分别连接到主机各GPIO口,数据线在同一条总线上。

因为SPI模式的数据线在同一总线上,所以需要片选来选择不同的储存卡;SD模式分别连接到主机,不需要片选线。

3. SD卡命令规范

3.1 命令类型

SD卡有数十种指令,但无非都是一些获取信息,数据传输的功能,并不会很难理解。规范书上有详细的状态转换图,下面会有介绍。

下面是4种指令类型:

  • 广播指令,无应答(代号bc):发送完此类命令后,并不会有反馈,但操作已经生效。
  • 广播指令,有应答(代号bcr):发送完此类命令后,SD卡会给予反馈。可能是一些寄存器信息,可能是
  • 寻址(点对点)指令(代号ac):发送完此类命令后,只有指定地址的SD卡会给予反馈(地址通过命令请求SD卡发布,是唯一的)。此时DAT线上无数据传输。
  • 寻址(点对点)数据传输指令(adtc):发送完此类命令后,只有制定地址的SD卡会给予反馈。此时DAT线上有数据传输。

3.2命令表

官方文档将命令分成了好几种功能。下面将所有命令列出,仅作查阅了解用,不需要每个命令都记住:

基础命令:用于重置、切换SD卡状态,获取相关信息

SD卡命令表class0

读块命令:读单个、多个块数据,设置块长度

SD卡命令表class2

写块命令:写单个、多个块数据,设置块长度

SD卡命令表class4

擦除块命令:把对应的块数据擦除

SD卡命令表class5

写保护命令:设置、取消对应地址的数据的写保护,可以使其他程序无法写入指定的地址,达到保护目的。用的情况不太多。

SD卡命令表class6

锁卡命令:设置、取消锁卡。锁卡后需要密码才能访问SD卡。

SD卡命令表class7

应用特殊命令:CMD55,使用ACMD前必须先发送的命令;CMD56是标准的读、写命令,会读、写一个block的数据。

SD卡命令表class8_thumb[1]

SDIO命令:预留给SDIO设备使用(CMD5也是预留给SDIO设备),在SD卡官方文档中没有说明具体用途

SD卡命令表class9

SD卡专用命令:MMC卡无法使用这些命令,里面包括如设置数据总线位宽,获取SD卡信息(寄存器)。

SD卡命令表ACMD1

SD卡命令表ACMD2

3.2 命令/数据传输方式

命令的传输协议大致如下:

  • 0开头,1结尾
  • 大端传输:先MSB,最后LSB
  • CRC校验

下面这幅图是无响应和无数据两种命令的传输情况:

SD卡命令传输方式(无数据传输)

非常清晰易懂,就不赘述了。

下面这幅图是多块数据读的数据传输情况:

SD卡命令传输方式(有数据传输)

主机发送多块读命令时,首先sd卡会做出回应,同时准备数据。数据准备完成后开始发送,并在每个block传输完成后加入crc校验码。传输完一个block和crc后紧跟着下一个block的数据传输,直到传输完成,或主机发送了新的命令。

SD模式有4根数据线,一次可以传输半个字节,两次一个字节。他们的传输方式如下图:

SD卡命令传输方式

同样是先传MSB,再传LSB,一次传半个字节,这样做可以方便主机做位移组合成一个字节。如果每条线单独传一个字节,则需要移位8次才可以获得一个完整的byte。

3.3 状态转换

下图为SD卡状态转换图。重新上电时为Idle状态:

SD卡状态转换图1

SD卡状态转换图2

看起来这个状态图很复杂,其实我们要走的流程并不复杂。Linux驱动对SD卡做初始化会经过如下步骤:

CMD0上电重置到idle状态(防止一些机型关机不掉电,如某些FPGA平台)->ACMD41获取SD卡支持的电压信息(还需要通过主机控制器设置电压)->CMD2获取卡商信息->CMD3请求SD卡发布相对地址->CMD9获取CSD寄存器,即卡的电气特性数据(需要使用SD卡相对地址)->CMD7通过相对地址选择对应的SD卡,该卡进入数据传输Transfer State状态->各种CMD进行block读写

3.4 流程差异

不同种类的卡初始化过程是不一样的,通过流程差异我们可以判断不同类型的卡。

SDIO:CMD0之后执行CMD5,CMD5只有SDIO类型才会有响应。

MMC:ACMD 41换为CMD1,ACMD类命令只有SD或SDIO卡才有响应。所以要先检测是否是SDIO,再检测是否是SD,最后检测是否是MMC(core层代码中也是这个顺序),否则会出现误判。

 

4. SD卡寄存器

SD卡一共有6个寄存器,我们用的对多的是CID(卡商信息),RCA(相对地址)和OCR(电压信息):

SD卡寄存器

CID:卡信息:生产商,OEM,产品名,版本,出产日期,CRC校验(所有寄存器都有,下同),常用
RCA:卡地址:在初始化时发布,用于与host通信,0x0000表示与所有卡通信,常用
DSR:驱动相关,总线电流大小,上升沿时间,最大开启时间,最小开启时间
CSD:数据传输要求:包括读写时间,读写电压最大最低值,写保护,块读写错误
SCR:特性支持,如CMD支持,总线数量支持
OCR:支持的电压,常用
SSR:特有特性,卡类型(OTP,SD等),一次擦除块数量
CSR:R1返回指令的卡状态,此寄存器用与传输卡状态给host

命令系统中有对应的指令获取这6个寄存器。

 

版权所有,转载请注明出处:

http://sickworm.com/?p=28 




Ubuntu Touch环境搭建

最近搞了一下Nexus 5的MultiRom Manger,体验了一把Ubuntu Touch和Android L,总体感觉还不错,不过Android L的NFC驱动还有问题,Ubuntu Touch优化还不足,画面有点卡,而且无法关背光。于是萌生了参与Ubuntu Touch驱动开发的念头,也算是把工作当成一种兴趣吧。

Ubuntu Touch for Nexus 5是非官方的,官方的只有Nexus 4,7,10。我们从MultiRom Manager可以直接下载到for Nexus 5的最新版本。

鉴于中文博客中并没有相关教程,只能参照 Ubuntu Touch 环境搭建的官方文档:

https://wiki.ubuntu.com/Touch/Building

 

以下记录我搭建Ubuntu Touch的过程(跟着上面的wiki走)。

Building Ubuntu Touch Android pieces from source

Whether you want to build Ubuntu Touch for the currently supported Nexus devices or want to port it to a new target, you need to set up your working environment to build Android from source. This setup is more or less the same whether you are building AOSP or a project based on it such as CyanogenMod, SEAndroid or Ubuntu Touch.

If you are new to building Android sources you may want to check out this document and others in the Getting Started section on the Google documentation site, as it covers the basics and terminology of AOSP building. While Ubuntu Touch uses some helper scripts as detailed below, if will nonetheless be helpful to understand what is going on under the hood especially if you want to work on this part of the project.

http://source.android.com/source/initializing.html

For Ubuntu Touch, you can find all the needed Android git repositories at https://code-review.phablet.ubuntu.com/#/admin/projects/. This is basically a mirror of AOSP 4.4.2_r1, but containing only the needed low level services used by Android (e.g. no Dalvik at all).

For any Android related project on our git server, you’ll find a branch named phablet-4.4.2_r1. This branch contains a static known git HEAD and the required changes needed for Ubuntu, including our custom Android manifest.

从源码编译Ubuntu Touch的Android部分

无论你想编译现在已经支持的Nexus设备,还是想为一个新设备移植Ubuntu Touch,你都需要的搭建编译环境来编译Android源码。该教程大致和你编译AOSP(Android Open Source Project)或者基于AOSP的项目是一样的,如CyanogenMod,SeAndroid或者Ubuntu Touch。

如果你是第一次编译Android源码,你可能需要下面这个文档,和Google文档网站Getting Started章节上的其他相关文档,文档讲解了AOSP编译的基础和一些术语。在下面的环节中Ubuntu Touch会使用一些辅助脚本。如果你打算在项目上做相关开发,通过浏览这些文档你会更明白接下来的每一步是在做什么。

http://source.android.com/source/initializing.html

你可以在https://code-review.phablet.ubuntu.com/#/admin/projects/找到所有搭建Ubuntu Touch需要的Android git repositories(repo是一个多git项目管理工具,这个东西在天朝不稳定的网络中显得非常难用)。这是一个基于AOSP 4.4.2_r1的镜像,但只包含了低层Android运行所需要的服务(例如没有Dalvik)。

在我们的git服务器上任意一个Android相关的项目,你都会找到一个叫phablet-4.4.2_r1的分支,这个分支包含了一个git HEAD和包括custom Android manifest(Ubuntu Touch的repo的Manifest,包含了所有Ubuntu Touch的基础git项目)等对于Ubuntu需要修改的代码。(这段不太明白想说什么)

该段只是介绍,没有需要执行的部分。

Set up your development environment

At this moment we’re using the Android code base provided by Android AOSP.

Everything we take from Android is just C/C++, so you’ll notice that your Android build environment will be way smaller than when comparing to the traditional Android builds.

For development you can run any 64-bit Desktop version of Ubuntu between 12.04 LTS and 13.04.

It’s not strictly necessary, but it’s helpful to install ccache. (http://source.android.com/source/initializing.html#ccache in the general Android Setup guide should help with this.)

Additional packages which are used to build the host tools:

  • $ sudo apt-get install git gnupg flex bison gperf build-essential   zip bzr curl libc6-dev libncurses5-dev:i386 x11proto-core-dev   libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-glx:i386   libgl1-mesa-dev g++-multilib mingw32 tofrodos   python-markdown libxml2-utils xsltproc zlib1g-dev:i386 schedtool

On Utopic (and maybe other) the 4.8 version of g++ is needed:

  • $ sudo apt-get install g++-4.8-multilib

Before 14.04 Trusty you’ll also need to set up the tools PPA.

Then you need to install phablet-tools:

  • $ sudo apt-get install phablet-tools

This will also install the repo tool, used to sync the Android repositories. Learn more about the repo tool. If you already have a repo tool in your $PATH from previous android development, be sure to remove it or make sure it’s after the system repo command in the path. To check that the right repo command is used, you can issue $ which repo. This should return /usr/bin/repo.

You can check out the source code using the repo and git tools already familiar to Android ROM developers, as described here

https://wiki.ubuntu.com/Touch/AndroidDevel

Alternately, all the Android code can be downloaded using the phablet-dev-bootstrap tool provided by the phablet-tools package installed in the previous step. This tool is a Python wrapper around repo and used to also check out bzr repositories before all code was managed by repo and git. For the purposes of getting the source code for development it is no longer needed.

To get the code setup run:

  • phablet-dev-bootstrap [target_directory]

If for some reason the sync ends midway, you can continue the sync with the -c switch, so the command would be:

  • phablet-dev-bootstrap -c [target_directory]

Alternatively, if you are just building an image for an already supported device, you can specify the -v switch:

  • phablet-dev-bootstrap -v [device codenames] [target_directory]

The phablet-dev-bootstrap command will automatically use the repo tool with the Ubuntu Touch Preview custom manifest to download all the git repositories and needed data. Be aware that this step takes a long time and requires at least 15GB (plus 2-3GB for the binary output).

搭建你的编译环境

我们将使用Android基础组件(基于Android AOSP)来编译。

我们只会用到Android中的C/C++代码,所以你会发现你的编译环境会比传统的Android编译环境要小。

你可以使用Ubuntu 12.04LTS到13.04的64位桌面版来开发Ubuntu Touch。(博主使用Mint 14.04,基于Ubuntu 14.04)

我们可以安装ccache(ccache会加快Android编译速度),但不是必需的。(http://source.android.com/source/initializing.html#ccache Android配置向导会指导你如何安装)

我们还需要这些安装包来编译主机上的编译工具:

$ sudo apt-get install git gnupg flex bison gperf build-essential  zip bzr curl libc6-dev libncurses5-dev:i386 x11proto-core-dev  libx11-dev:i386 libreadline6-dev:i386 libgl1-mesa-glx:i386  libgl1-mesa-dev g++-multilib mingw32 tofrodos  python-markdown libxml2-utils xsltproc zlib1g-dev:i386 schedtool

在Utopic(Ubuntu 14.10)(或者其他)中,还需要安装g—4.8

$ sudo apt-get install g++-4.8-multilib

在Ubuntu 14.04稳定前你还需要安装PPA工具(只需要把“Add PPAs (pre Trusty only)”那一步执行就ok了。)

然后安装phablet-tools:

$ sudo apt-get install phablet-tools

这会安装repo工具,repo用于同步Android的repositories。 点击了解repo工具。如果你已经有了repo工具且已经在之前的Android开发中把执行路径加入到$PATH中了,请把repo删除,或保证在$PATH中repo的路径在系统的repo command的路径(如/bin/repo)之后。你可以使用$ which repo验证repo路径是否正确。如果正确,会返回/usr/bin/repo。

(这段还没弄清楚什么意思,因为我还没编译过,应该保证是/usr/bin/repo就可以了。到时候回来补充)

然后使用repo和git(只需要安装git,不需要实际执行,repo会调用git工具),从以下网址获取Android源码。这一步对于Android Rom开发者应该很熟悉。

https://wiki.ubuntu.com/Touch/AndroidDevel

(此链接会另写一篇文章,下载源码的工作都在这个链接中)

使用之前步骤已经安装了的phablet-dev-bootstrap,就可以一个个项目地下载所有的Android代码(大概可有640多个)。这个工具使用Python将repo封装了起来。在所有的代码被repo和git管理之前,这个工具也可以用于下载bzr repositories(和repo相似的一个管理工具)的工程。不过只用于获取开发用的源码的话,已经不需要这个功能了。

接下来让代码跑起来:

phablet-dev-bootstrap [target_directory]

如果发生某些错误导致sync中途停止了,你可以使用-c参数继续同步:

phablet-dev-bootstrap -c [target_directory]

如果你只是编译一个已经被支持的设备的烧录文件,你可以使用-v参数:

phablet-dev-bootstrap -v [device codenames] [target_directory]

phablet-dev-bootstrap命令会使用repo工具自动下载manifest(项目列表)然后下载所有的git项目和需要的数据。注意这个步骤要很长的时间,而且需要至少15GB(加上2-3GB的二进制文件输出)的空间。

 

版权所有,转载请注明出处:

http://sickworm.com/?p=21




Settings点击Location(位置)后右上角的开关button不会消失

MT8121/8382平台:

 

前几天又遇到一个源码的bug。在10寸平板上,进入设置界面,点击Location(位置)项,右上角Title处会显示一个开关button,用来开关定位服务。但点完Location项再点其他项的时候,button并不会消失。直到你点到Developer(开发者选项),再点击其他项那个button才会消失。Developer项本身右上角也有一个开关button。

于是很自然地想到,在切换设置项时,Location页面在退出时没有把button去掉。

结果一看果然是。

 

--- a/packages/apps/Settings/src/com/android/settings/location/LocationSettings.java +++ b/packages/apps/Settings/src/com/android/settings/location/LocationSettings.java @@ -100,6 +100,9 @@ public class LocationSettings extends LocationSettingsBase super.onPause(); mValidListener = false; mSwitch.setOnCheckedChangeListener(null); +        getActivity().getActionBar().setCustomView(null);

 

版权所有,转载请注明出处:

http://sickworm.com/?p=19




Launcher3自定义壁纸旋转后拉伸无法恢复

MTK8382/8121平台。

 

描述:将自定义图片设置成壁纸后,横屏显示时,旋转为竖屏,图片由于分辨率过小,会拉伸;再旋转为横屏,拉伸不恢复。

这两天正在解这个问题,研究了很久,走了不少弯路,最后发现是Launcher读取SharePreferences时的一个bug。

 

bug是这样产生的:

Launcher3设置完自定义壁纸(系统自带壁纸不会记录)的时候,会在com.android.launcher3.WallpaperCropActivity.xml中记录被设置壁纸的分辨率,并提交分辨率给WallpaperManager(通过suggestWallpaperDimension())。具体函数是:WallpaperCropActivity.java中的updateWallpaperDimensions(),它被WallpaperCropActivity.java的setWallpaper()调用;

Launcher3每次旋转后会重新执行onCreate(),同时会提交当前壁纸的分辨率给WallpaperManager,提交分辨率的函数在Workspace.java中的setWallpaperDimension()中。问题在这里:setWallpaperDimension()无法获取之前updateWallpaperDimensions()修改的SharedPreferences,导致它提交的是默认的壁纸分辨率1920×1080,从而导致低分辨率的壁纸拉伸。

解决此问题的方法是:修改Workspace.java中的setWallpaperDimension()中的getSharedPreferences()的flag,把MODE_PRIVATE改为MODE_MULTI_PROCESS。修改后成功访问。

 

我的问题是:

根据Android Developer的解释:

MODE_PRIVATE:

File creation mode: the default mode, where the created file can only be accessed by the calling application (or all applications sharing the same user ID).

即MODEL_PRIVATE只能被同一个application或者同一个userID的application调用。按这个说法,这两个Activity应该是可以共同访问的。(Workspace.java使用的是Launcher.java的context,两个Activity pid不一样,uid一样)

同时我还看了MODE_MULTI_PROCESS的解释:

MODE_MULTI_PROCESS:

SharedPreference loading flag: when set, the file on disk will be checked for modification even if the shared preferences instance is already loaded in this process. This behavior is sometimes desired in cases where the application has multiple processes, all writing to the same SharedPreferences file. Generally there are better forms of communication between processes, though.

This was the legacy (but undocumented) behavior in and before Gingerbread (Android 2.3) and this flag is implied when targetting such releases. For applications targetting SDK versions greater than Android 2.3, this flag must be explicitly set if desired.

意思是这个flag用于给拥有多个进程的application共同访问同一个SharedPreferences使用的。按照这个说法,似乎又确实应该使用MODEL_MULTI_PROCESS。

 

于是我想找到getSharedPreference实现代码,看看它怎么处理这几个flag。可恶的是,从Activity父类一级一级往上找,都找不到实现的方法,直到找到这篇文章:

http://blog.csdn.net/qinjuning/article/details/7310620

才知道ContextImpl实现了Context的具体方法,进而找到了答案:

ContextImpl类中有getSharedPreferences的实现。里面说明了在MODE_MULTI_PROCESS标志中,getSharedPreferences会进行reload。换言之MODE_PRIVATE不会重新读取SharedPreferences。

这里终于搞懂他的意思:在之前的bug,并不是SharedPreferences获取失败,而是因为没有reload所以没有获取到新写入的分辨率信息。因为之前没有注意到这个问题,所以走了弯路。

不过还有问题:每次Launcher旋转的时候都会重新启动Activity调用onCreate,为什么我getSharePreferences还是旧的呢?继续观察android.app.ContextImpl的getSharedPreferences:

@Override public SharedPreferences getSharedPreferences(String name, int mode) { Log.w("Launcher", "contextImpl: " + this, new RuntimeException("getSp").fillInStackTrace()); SharedPreferencesImpl sp; synchronized (ContextImpl.class) { if (sSharedPrefs == null) { Log.e("Launcher", "all init"); sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>(); } final String packageName = getPackageName(); ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName); if (packagePrefs == null) { Log.e("Launcher", "package init"); packagePrefs = new ArrayMap<String, SharedPreferencesImpl>(); sSharedPrefs.put(packageName, packagePrefs); } // At least one application in the world actually passes in a null // name. This happened to work because when we generated the file name // we would stringify it to "null.xml". Nice.
if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; } } sp = packagePrefs.get(name); if (sp == null) { File prefsFile = getSharedPrefsFile(name); sp = new SharedPreferencesImpl(prefsFile, mode); packagePrefs.put(name, sp); Log.e("Launcher", "new sp"); return sp; } Log.e("Launcher", "old sp"); } if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { // If somebody else (some other process) changed the prefs // file behind our back, we reload it. This has been the // historical (if undocumented) behavior.
sp.startReloadIfChangedUnexpectedly(); Log.e("Launcher", "reload"); } return sp; }

里面Log.e(“Launcher”, …);是我自己加的调试信息。首先会判断sSharedPrefs是否有内容,然后从中获取对应package的prefsFile。如果sSharedPrefs找不到,才从xml文件中重新读取。最后加了一个判断,如果设置了MODE_MULTI_PROCESS变量,或者Android 2.2以下的系统,会默认从xml文件中重新reload,以保持最新的SharedPreferences数据。

注意这里的sSharedPrefs变量,它只在getSharedPreferences中有赋值,也就是说SharedPreferences的数据一直跟随着ContextImpl实例走,只从getSharedPreferences()方法中获取数据。也就是说,当旋转屏幕的时候,我们调用getSharedPreferences()获取的数据都是从这个sSharedPrefs变量中取出来的。实际从下面Log信息也可以看到,旋转并不会有”new sp”的Log打印,只有对Launcher3在设置中force stop和clear data的时候才会出现”new sp”。

旋转的Log提示:

09-11 08:47:19.599: E/Launcher(4628): launcher:com.android.launcher3.Launcher@42392ac8 09-11 08:47:19.599: E/Launcher(4628): mBase:android.app.ContextImpl@424c1898 09-11 08:47:19.608: E/Launcher(4628): old sp 09-11 08:47:19.727: E/Launcher(4628): old sp 09-11 08:47:19.731: E/Launcher(4628): reload 09-11 08:47:19.932: E/Launcher(4628): old sp

force stop的提示:

09-11 08:48:29.456: E/Launcher(5271): launcher:com.android.launcher3.Launcher@4238f220 09-11 08:48:29.456: E/Launcher(5271): mBase:android.app.ContextImpl@42391ab8 09-11 08:48:29.489: E/Launcher(5271): all init 09-11 08:48:29.489: E/Launcher(5271): package init 09-11 08:48:29.493: E/Launcher(5271): new sp 09-11 08:48:29.679: E/Launcher(5271): new sp 09-11 08:48:29.766: E/Launcher(5271): old sp 09-11 08:48:29.767: E/Launcher(5271): old sp 09-11 08:48:29.790: E/Launcher(5271): old sp

里面三个连着的old sp,只有第二个是读取分辨率的,其他两个分辨是LauncherAppState的SharedPreferences。因为此时Launcher3代码已经被我修改为MODE_MULTI_PROCESS,所以旋转会打出”reload”信息。

 

也就是说,旋转的时候sSharedPrefs的值是一直保存着的。可是通过Log打印信息,我们发现,每一次的getSharedPreferences()的contextImpl都是不一样的!

这里要注意的是,执行方法的context是Activity的父类ContextThemeWrapper的mBase私有成员执行的,获取mBase可以在getSharedPreferences()中打印this出来,也可以在Activity中进行反射,这里用的是反射方法:

try { Log.e("Launcher", "launcher:" + this); java.lang.reflect.Field f = Activity.class.getSuperclass().getDeclaredField("mBase"); f.setAccessible(true); Context s = (Context) f.get(this); Log.e("Launcher", "mBase:" + s); } catch(Exception e) { e.printStackTrace(); }

contextImpl不一样了,但是sSharedPrefs数据还保存着,且sSharedPrefs不能通过其他方法赋值,只能猜测在旋转的时候通过原型模式把原来的context传进去了。Launcher3的旋转处理,我只找到ACTION_CONFIGURATION_CHANGE这个广播,但是我把里面的代码注释了,依然可以正常工作。估计是更底层的代码控制的。

 

具体代码还没找到,因为还要搬砖。有空再研究吧!

版权所有,转载请注明出处:

http://sickworm.com/?p=17




Launcher3无图标问题

 

MTK8382/8121平台。

 

机器(8寸,默认竖屏)第一次烧录完成后,以横放姿势启动,发现Launcher没有图标,而竖屏启动是没有这个问题的。在测试过程中发现,在设置中clear data后也会有这样的问题。因此可以初步判断是database初始化的问题,因为database存储着favorite icon和workspace的数据。

对比database,发现workspaces字段没有写入。查看输出的log,发现最大的不同在于,在step1的初始化过程中,出现了

setApplicationContext called twice!old=com.android.launcher3.LauncherApplication@41c76888 new=com.android.launcher3.LauncherApplication@41c76888

的warning。查找warning输出的位置,发现在onCreate中。也就是说在初始化过程中,Launcher3 onCreate了两次。

 

我们知道,为了重新布局,Launcher每次转屏都会重新onCreate的。而以横屏摆放启动,会导致在默认竖屏的状态下,很短时间内又进行了一次转屏。

看了一下AndroidManifest.xml,发现android:screenOrientation属性是nosensor,所以每次启动都是默认(竖屏)。改为sensor后,第一次初始化方向和实际方向一致,则不会出现执行两次onCreate()的情况。

 

在LauncherModel.java中,搜索“step 1”,可以找到初始化workspace的过程。而恰恰这个workspace初始化没有被synchronized保护起来。导致初始化database的过程中产生冲突,从而导致写入失败。

版权所有,转载请注明出处:

http://sickworm.com/?p=15