使用HierarchyViewer查看Android应用的布局

本文作者为华中科技大学OPhone俱乐部成员王云振,原文地址:http://blog.tianya.cn/blogger/post_read.asp?BlogID=2882548&PostID=25749444

在学习ViewGroup和Layout时我们可能会有一个疑问,如果我在Xml布局文件中不放置Layout,直接放TextView等组件的时候,它是用什么方式布局的?还有要学习别人优秀的布局怎么办?

HierarchyView.bat是Android SDK中自带了一个查看UI布局层级结构的工具。在模拟器运行的情况下,使用该工具可以将当前的Activity中的UI组件们以对象树的形式展现出来,每一个组件所包含的属性也能窥探得到。在对象树上的任意节点可以看到该节点及以下节点的显示效果。

使用HierarchyView能深入全面的理解xml布局文件,更可以通过它来学习别人优秀的布局技巧。下面是一些基本使用步骤:

  1. 启动模拟器
  2. 到{Android SDK}\tools\目录下,双击可以启动hierarchyviewerbat文件,打开一个图形界面
  3. 点击 load View hierarchy按钮,就可以捕获模拟器当前activity的画面布局信息


    Hierarchy Viewer

  4. hierarchy通过树形结构展示布局形式。双击树节点可以展示单独的UI部分

    Hierarchy Viewer

  5. 当模拟器activity画面改变后,点击““refresh””可以加载新的页面布局信息

通过Hierarchy Viewer你就可以学习别人优秀的布局方式,同时也更能更深入更全面更整体的把握xml布局文件,体会UI和代码(java code)以及资源(res)的相互分离。

Android开发中反编译APK的方法

本文作者为华中科技大学OPhone俱乐部成员胡毅鹏,原文地址:http://www.cnblogs.com/huyipeng/archive/2010/07/25/1784679.html

写个东西不容易啊!第一次写了一半,上传个文件后,结果网络崩溃了。把我气得……(实践了几次之后发现,在Chrome浏览器下上传图片是会崩溃的,换了搜狗,一切才正常。)其实按照我的札记的顺序,这次本应该写一写关于Android的基本的技术和相关的知识。毕竟这样比较符合逻辑。可是最近学习的时候碰到了关于反编译方面的知识,所以自己留心进行了一系列的实践并做了一下总结,希望能够为大家提供一些帮助。通过反编译,我们可以去看一下别人的一些Android优秀的应用程序是怎样写的,这样也是一种学习的过程(虽然这种学习有点“不道德”。)

ps:对于软件开发人员来说,保护代码安全也是比较重要的因素之一,不过目前来说Google Android平台选择了Java Dalvik VM的方式使其程序很容易破解和被修改,首先APK文件其实就是一个MIME为ZIP的压缩包,我们修改ZIP后缀名方式可以看到内部的文件结构,类似Sun JavaMe的Jar压缩格式一样,不过比较去别的是Android上的二进制代码被编译成为Dex的字节码,所有的Java文件最终会编译进该文件中去,作为托管代码既然虚拟机可以识别,那么我们就可以很轻松的反编译。所有的类调用、涉及到的方法都在里面体现到,至于逻辑的执行可以通过实时调试的方法来查看,当然这需要借助一些我们自己编写的跟踪程序。Google最然在Android Market上设置了权限保护app-private文件夹的安全,但是最终我们使用修改定值的系统仍然可以获取到需要的文件。

总结反编译主要的目的在于学习。利用反编译进行相关的汉化或修改,还是尽量不要吧,毕竟人家写个程序不容易啊!
我目前接触的反编译的方法总共有三种,分别如下:

第一种:利用模拟器自带的一个dexdump

反编译一个.apk文件,需要做以下几步:

  1. 找到.apk安装文件
    找到apk安装文件这个比较容易,把手机或者模似器安装好后,可以在eclipse的File Explorer下找到安装程序的apk译文件,也可以通过adb命令找到:

    1
    2
    3
    4
    
    $ adb shell
    # cd /system/app
    cd /system/app
    # ls
  2. 找到安装软件的*.dex文件
    找到安装软件的*.dex文件运行安装软件后,会在android文件系统下生成一个*.dex文件,一般在目录/data/dalvik-cache下,也可以通过adb命令找到:

    1
    2
    3
    4
    
    $ adb shell
    # cd /data/dalvik-cache
    cd /data/dalvik-cache
    # ls
    注意:一般来说,如果你能够通过File Exploere找到data/dalvik-cache文件,其实一二两个步骤可以跳过,直接进入adb shell后执行第三步;命令行中直接输入adb shell后执行的前提是你已经在系统的环境变量中配置了tools文件,否则你得先cd到tools文件中执行adb.exe后才能执行以后的命令。另外你得确保自己的虚拟机中安装了应用程序才能进行下一步的操作。对于系统自带的程序,我也试了进行反编译,结果输出的txt没有具体内容,没有成功。有兴趣的话,大家可以试一下。

  3. dump dex文件

    编译软件对应的dex文件,通过以下指令:
    1
    
    #dexdump -d -f -h /data/dalvik-cache/data@app@com.ophone.chapter6_4-1.apk@classes.dex > ophone.text
    指令参数解释:-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

    这执行完了后,大家应该可以在dalcik-cache文件夹下找到ophone.txt文件。如果没有的话,可以重新启动虚拟器。 之后我们可以将该txt文件导出到桌面上进行相关操作。(File Explorer中的按钮)

  4. 分析dex文件获取想要的代码
    打开刚才得到的编译出来的text文件,会看到形如以下的代码,总体来看,这种反编译的效果不太理想。

第二种:通过dex2jar工具进行反编译

首先要下载两个东西:

  1. dex2jar:http://code.google.com/p/dex2jar/downloads/list
  2. JdGUI:http://java.decompiler.free.fr/?q=jdgui (反编译Jar包,查看Jar包的源代码的GUI工具)

步骤如下:

  1. 下载一个.apk程序安装包,将其文件名*.apk改为*.zip后进行解压。我这儿是将南方周末的读报器进行反编译的。
  2. 把其中的class.dex拷贝到dex2jar.bat所在目录。运行dex2jar.bat class.dex,将会在其文件夹下生成classes.dex.dex2jar.jar。
  3. 运行JD-GUI工具(它是绿色无须安装的),打开上面的jar文件,即可看到源代码。

与第一种方法相比较,该方法生成的文件全面,代码整齐,虽然有些中文会出现乱码,id等全部是数字代替,但整体结构很完整,不错。

类似的工具还有Dedexer(http://sourceforge.net/projects/dedexer/files/)具体的应用代码为(先进入jar包所在路径):

1
java -jar ddx.jar -d  <directory>  <dex file>

可以再其中加入-o,这样可以生成一个介绍文件内部结构的日志文件。

第三种:用smali工具

下载地址:http://code.google.com/p/smali/downloads/list
反编译命令:

1
java -jar baksmali-1.2.jar  <dex文件>  -o  <新生成的文件名>

重新编译:

1
java -jar  smali-1.2.jar <需编译文件夹>

本人亲自实践了一下,确实新版本的baksmali可以直接对.apk文件进行直接的反编译。但是这种方法生成的文件也不是很好。

说到这儿,对于xml的文件的编译,需要工具AXMLPrinter2.jar(http://code.google.com/p/android4me/downloads/list)。具体的使用方法,与上面的差不多,可以运用命令:

1
java -jar AXMLPrinter2.jar main.xml > main.txt

进行相关的操作。大家可以参照这位仁兄的:http://hi.baidu.com/linrw/blog/item/cc3afd2233ab88a94723e878.html

写这篇不容易啊,希望能够给大家一点启示。

ps:还有个工具叫做APKTool也可以做同样的工作。

Android中数据持久化储存之二:SharePreference

本文作者为华中科技大学OPhone俱乐部成员熊磊,原文地址:http://blog.sina.com.cn/s/blog_648c68460100kjqs.html,原创文章,转载请注明来源!

Preference是一种轻量级的键值(key-value)储存方式,可以用它来持久化存储一些变量的值,这些变量必须是基本数据类型。Preference存储的数据以XML文件形式保存,存储在/data/data/<包名>/shared_prefs目录下。

下面示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.xionglei.android_practice4;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.Toast;
public class Practice4 extends Activity {
    private SharedPreferences settings;
    private RadioGroup radio;
    private EditText inputbox;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button btn_input = (Button)findViewById(R.id.btn_input);
        Button btn_display = (Button)findViewById(R.id.btn_display);
        Button btn_clear = (Button)findViewById(R.id.btn_clear);
        inputbox = (EditText)findViewById(R.id.inputbox);
        radio = (RadioGroup)findViewById(R.id.radio);
        String name_value;
        String radio_value;
        settings = this.getSharedPreferences("Demo",MODE_PRIVATE);
        name_value = settings.getString("name", "");
        radio_value = settings.getString("radio", "");
        inputbox.setText(name_value);
        if(radio_value.equals(getText(R.string.radiobtn1).toString())){
            radio.check(R.id.radiobtn1);
        }else if(radio_value.equals(getText(R.string.radiobtn2).toString())){
            radio.check(R.id.radiobtn2);
        }
 
        btn_input.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                SharedPreferences.Editor editor = settings.edit();
                String tmp = "";
                int id = radio.getCheckedRadioButtonId();
                if(id!=-1){
                    tmp = id == R.id.radiobtn1?getText(R.string.radiobtn1).toString():getText(R.string.radiobtn2).toString();
                }
 
                editor.putString("name", inputbox.getText().toString()).putString("radio", tmp).commit();
                Toast.makeText(Practice4.this, R.string.save_message, Toast.LENGTH_SHORT).show();
            }
        });
 
        btn_display.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Intent i = new Intent(Practice4.this, ShareValueActivity.class);
                i.putExtra("name", settings.getString("name", ""));
                i.putExtra("sex", settings.getString("radio", ""));
                startActivity(i);
            }
        });
 
        btn_clear.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                SharedPreferences.Editor editor = settings.edit();
                editor.clear().commit();
                Toast.makeText(Practice4.this, R.string.clear_message, Toast.LENGTH_SHORT).show();
            }
        });
    }
}

点击输入后,EditText和RadioGroup中的信息就会被储存到XML文件中,此文件及信息可以在对应目录中查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
D:\Users\XIONGLEI>adb shell
# cd data/data/com.xionglei.android_practice4/shared_prefs
cd data/data/com.xionglei.android_practice4/shared_prefs
# ls
ls
Demo.xml
# cat Demo.xml
cat Demo.xml
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<map>
<string name="radio">male</string>
<string name="name">xionglei</string>
</map>
#

点击清除后,Demo.xml文件里的信息即被清除,在此查询得如下结果:

1
2
3
4
5
6
7
8
9
10
11
D:\Users\XIONGLEI>adb shell
# cd data/data/com.xionglei.android_practice4/shared_prefs
cd data/data/com.xionglei.android_practice4/shared_prefs
# ls
ls
Demo.xml
# cat Demo.xml
cat Demo.xml
<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<map/>
#

可见通过Preference方式储存的键值对是以XML文件方式保存在data/data/com.xionglei.android_practice4/shared_prefs目录下的,具有持久性。

Android中数据持久化储存之一:文件储存

本文作者为华中科技大学OPhone俱乐部成员熊磊,原文地址:http://blog.sina.com.cn/s/blog_648c68460100kjpq.html,原创文章,转载请注明来源!

文件储存有三种方式:

  1. 储存至默认文件夹
  2. 储存至指定文件夹
  3. 储存至sd卡

现分别示例如下(含源码及运行结果,资源文件省略):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package com.xionglei.android_practice3;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class Practice3 extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        //储存到默认路径
        Button btnDefault = (Button)findViewById(R.id.btnDefault);
        btnDefault.setOnClickListener(
            new View.OnClickListener() {
                public void onClick(View v){
                    int id = 0;
                    EditText input = (EditText)findViewById(R.id.input);
                    byte[] buf = input.getText().toString().getBytes();
                    try{
                        saveToDefault(buf);
                        id = R.string.ok_message;
                    }catch(IOException e){
                        e.printStackTrace();
                        id = R.string.fail_message;
                    }
                    Toast.makeText(Practice3.this, id, Toast.LENGTH_SHORT).show();
                }
 
                private void saveToDefault(byte[] buf) throws IOException {
                    FileOutputStream fos = openFileOutput("test_1.txt", Context.MODE_APPEND);
                    fos.write(buf);
                    fos.close(); 
                }
            }
        );
 
        //储存到指定路径
        Button btnDirectory = (Button)findViewById(R.id.btnDirectory);
        btnDirectory.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v){
                int id = 0;
                EditText input = (EditText)findViewById(R.id.input);
                byte[] buf = input.getText().toString().getBytes();
                try{
                    saveToDirectory(buf);
                    id = R.string.ok_message;
                }catch(IOException e){
                    e.printStackTrace();
                    id = R.string.fail_message;
                }
                Toast.makeText(Practice3.this, id, Toast.LENGTH_SHORT).show();
            }
 
            private void saveToDirectory(byte[] buf) throws IOException {
                File textFile = new File("/data/data/com.xionglei.android_practice3/test_2.txt");
                //File textFile = new File("f:/test_2.txt");
                //文件不能存在项目之外的地方,因为一个应用只能访问自己应用的资源.
                FileOutputStream fos = new FileOutputStream(textFile_2);
                fos.write(buf);
                fos.close(); 
            }
        });
 
        //储存到SD卡
        Button btnSD = (Button)findViewById(R.id.btnSD);
        btnSD.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v){
                int id = 0;
                EditText input = (EditText)findViewById(R.id.input);
                byte[] buf = input.getText().toString().getBytes();
                try{
                    saveToSD(buf);
                    id = R.string.ok_message;
                }catch(IOException e){
                    e.printStackTrace();
                    id = R.string.fail_message;
                }
                Toast.makeText(Practice3.this, id, Toast.LENGTH_SHORT).show();
            }
 
            private void saveToSD(byte[] buf) throws IOException {
                String path = Environment.getExternalStorageDirectory().getPath();
                File textFile = new File(path+"/test_3.txt");
                FileOutputStream fos = new FileOutputStream(textFile_3);
                fos.write(buf);
                fos.close(); 
            }
        });
 
        //读取资源
        Button btnRead = (Button)findViewById(R.id.btnRead);
        btnRead.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                try{
                    InputStream is = getResources().openRawResource(R.raw.test);
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    byte[] buf = new byte[128];//设置缓冲区
                    int ch = -1;
                    while((ch = is.read(buf))!=-1){
                        baos.write(buf, 0, ch);
                    }
                    String result = new String(baos.toByteArray(),"UTF-8");
                    EditText input = (EditText)findViewById(R.id.input);
                    input.setText(result);
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        });
    }
}

分别执行以下三个操作后,可以在根目录下查看文件信息:

1
2
3
4
5
6
7
8
9
10
11
12
D:\Users\XIONGLEI>adb shell
# cd data/data/com.xionglei.android_practice3/
cd data/data/com.xionglei.android_practice3/
# ls
ls
test_2.txt
files
lib
# cat test_2.txt
cat test_2.txt
8735173541735471354713
gd#

此段信息证明2方式成功执行了,验证1、3方式可用类似方法,1方式文件应该存在于data/data/com.xionglei.android_practice3/files目录下,3方式文件应该存在于 /sdcard目录下,可自行查看。

需要注意的是,2方式中储存在指定目录,该指定目录仅限于本项目之内,及父目录必须为data/data/com.xionglei.android_practice3/,否则文件储存会失败。

另外,由于此方式需要挂载SD卡,在此顺便说说SD的创建及使用方法:
首先在windows命令行输入mksdcard 512M f:/mycard.img,此处SD卡的储存路径及文件名可以自设,此处mycard.img只是一个映像文件。然后就可以在模拟器的启动参数里加入-sdcard来载入它,具体命令为emulator -avd -sdcard f:/mycard.img这样就可以启动带有SD卡的模拟器了。

注意方法3中(即储存至SD卡):

1
String path = Environment.getExternalStorageDirectory().getPath();

这一语句,是为了获取SD卡在项目中的路径,而不是f:/mycard,项目中SD卡的路径是根目录下的sdcard文件夹。例如,如果要将本地文件copy至SD卡中,命令行为adb push f:\test.txt /sdcard/test.txt。

此外,在上面项目中还需要注意的一点就是向SD卡中写入文件需在AndroidManifest.xml中加入操作权限,否则,此操作不能成功:

1
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

OPhone俱乐部2010年暑期培训计划

经过前一段时间的招新报名和面试,俱乐部一共招募了2名研究生与16名本科生,暑假期间的培训内容与具体日程如下:

计划内容主讲人
培训一OPhone/Android特点与OPhone/Android开发环境搭建周亮
培训二OPhone/Android应用程序模型与开发、Activity与Intent刘淼
培训三Java语言语法概览、面向对象思想与Collections框架、匿名内部类与事件处理范欣欣
培训四创建OPhone/Android图形用户界面:布局、控件与图形用户界面中的事件响应周亮
训练一各种图形用户界面的练习
培训五Java网络访问与XML处理以及在OPhone/Android中的接口周亮
培训六数据库基础与SQLite使用及数据持久化存储范欣欣
训练二移动互联网前景调查与移动Market应用调查
训练三汇报调查情况以及头脑风暴,讨论应用的点子
训练四一个应用的具体实现
培训七OPhone/Android音乐播放与多媒体编程唐文滔
培训八自定义二维图形界面与Open GL与三维动画编程唐文滔
训练五简单游戏设计
培训九位置服务与硬件服务待定
训练六游戏优化,使用传感器

OPhone俱乐部招新啦

简介:

华中科技大学OPhone俱乐部(筹)由华中科技大学Dian团队、中国移动研究院与播思通讯技术(北京)有限公司联合发起筹备建立,由Dian团队负责组织,其目标是在大学生中推广中国移动OPhone手机开发平台以及给在校学生提供深入了解OPhone/Android手机平台开发的机会。

俱乐部协调中国移动研究院、播思通讯、Dian团队各方资源,开展多种多样的活动,包括邀请中国移动研究院、播思通讯专家给俱乐部成员进行技术讲座等,让俱乐部成员得到技术上、阅历上等方方面面的提高。

OPhone俱乐部(筹)以培训与项目为导向,俱乐部成员可以在创意设计、产品规划、产品实现、发布推广等一个或多个环节中充分培养其创新意识和实践能力。每一位俱乐部成员都得到充足和充实的发展、锻炼、提高的机会。

OPhone俱乐部(筹)计划在2010年9正式成立,届时中国移动专家会到校进行宣讲并进行大规模招新

特色:

  • 在这里,我们一起探索前沿的移动互联网技术;
  • 在这里,我们一起研究OPhone/Android等先进的移动开发平台;
  • 在这里,我们一起体验项目的设计、规划、实现、发布,共同成长;
  • 在这里,我们一起发挥创意,让人们的生活更美好。

报名要求:

筹备期间预计招募10有一定编程基础的同学在暑假期间做技术积累:

  • 不限年级与专业,暑假期间能在校坚持学习一个月(7到8月中旬 )时间
  • 对移动互联网开发有浓厚的兴趣和强烈的学习决心;
  • 有Java编程 / 移动应用创意设计 / 用户界面设计经验优先;

报名方式:

报名时间:即日起到6月30日晚12截止

网络报名:请点击  OPhone俱乐部报名表 下载报名表填写之后发送至ophoneclub@gmail.com

我们会根据简历筛选合适的人员进行面试,最终确定吸纳成员,欢迎发送邮件至ophoneclub@gmail.com咨询,我们收到邮件后会尽快回复。