用Dedexer除了可以反编译其内附的dex,其余的都未成功过。smali可以顺利反编译,而且可以把你修改过的代码重新编译成classes.dex比较邪恶
反编译命令 baksmali-1.2.jar 【dex文件】 -o classes
重新编译 smali-1.2.jar 【需编译文件夹】
- 11:16
- 浏览 (124)
- 论坛浏览 (802)
- 评论 (3)
开源项目android4me 其AXMLPrinter2可以方便的把apk中已经序列化的xml还原为文本格式
把AXMLPrinter2.jar拷贝到C盘,在控制台cd到要解压出来的apk文件夹,执行下面的命令就可以把所有的xml还原文本格式了
for /r . %a in (*.xml) do @java -jar c:/AXMLPrinter2.jar "%a">>"%a".txt
- 11:02
- 浏览 (103)
- 论坛浏览 (432)
- 评论 (0)
Notification通知界面可以用自己定义的界面来显示。下面是我实现的带进度条的通知效果
下面是主要实现部分代码,比较简单直接看代码可以了。
- nf =new Notification(R.drawable.icon,"带进度条的提醒",System.currentTimeMillis()) ;
- nf.icon = R.drawable.icon;
- nf.contentView= new RemoteViews(this.getPackageName(),R.layout.notification);
- nf.contentView.setProgressBar(R.id.ProgressBar01, 100, 0, false);
- nf.contentIntent=PendingIntent.getActivity( this, 0, new Intent(this,remoteview.class) ,0);
nf =new Notification(R.drawable.icon,"带进度条的提醒",System.currentTimeMillis()) ;
nf.icon = R.drawable.icon;
nf.contentView= new RemoteViews(this.getPackageName(),R.layout.notification);
nf.contentView.setProgressBar(R.id.ProgressBar01, 100, 0, false);
nf.contentIntent=PendingIntent.getActivity( this, 0, new Intent(this,remoteview.class) ,0);
- 10:51
- 浏览 (65)
- 论坛浏览 (426)
- 评论 (0)
BroadcastReceiver在android中是一特色功能.android系统的很多消息(如系统启动,新短信,来电话等)都通过BroadcastReceiver来分发.BroadcastReceiver的生命周期是短暂的,而且是消息一到达则创建执行完毕就立刻销毁的.为了说明这一点,我通过注册一个Alarm Service每7秒钟触发产生一次BroadcastReceive事件.
以下是BroadcastReceive的实现
- protected static final class AlarmReceiver extends BroadcastReceiver{
- public AlarmReceiver()
- {
- //查看类创建的进程id和线程id
- Log.i("AlarmReceiver.AlarmReceiver()", Calendar.getInstance().getTime().toLocaleString());
- Log.i("AlarmReceiver.AlarmReceiver() -> pid", String.valueOf(android.os.Process.myPid()));
- Log.i("AlarmReceiver.AlarmReceiver() -> tid", String.valueOf(android.os.Process.myTid()));
- }
- @Override
- public void onReceive(Context context, Intent intent) {
- // TODO Auto-generated method stub
- Log.i("AlarmReceiver.onReceive()", Calendar.getInstance().getTime().toLocaleString());
- }
- }
protected static final class AlarmReceiver extends BroadcastReceiver{
public AlarmReceiver()
{
//查看类创建的进程id和线程id
Log.i("AlarmReceiver.AlarmReceiver()", Calendar.getInstance().getTime().toLocaleString());
Log.i("AlarmReceiver.AlarmReceiver() -> pid", String.valueOf(android.os.Process.myPid()));
Log.i("AlarmReceiver.AlarmReceiver() -> tid", String.valueOf(android.os.Process.myTid()));
}
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
Log.i("AlarmReceiver.onReceive()", Calendar.getInstance().getTime().toLocaleString());
}
}
通过一个"Start"按钮和一个"Stop"按钮来控制消息.
- public void onClick(View arg0) {
- // TODO Auto-generated method stub
- switch(arg0.getId()){
- case R.id.btnStart:
- Log.i("current pid", String.valueOf(android.os.Process.myPid()));
- Log.i("current tid", String.valueOf(android.os.Process.myTid()));
- ((AlarmManager)this.getSystemService(Context.ALARM_SERVICE))
- .setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 7*1000,
- PendingIntent.getBroadcast(this, 0, new Intent(this,AlarmReceiver.class), 0)
- );
- break;
- case R.id.btnStop:
- ((AlarmManager)this.getSystemService(Context.ALARM_SERVICE))
- .cancel(PendingIntent.getBroadcast(this, 0, new Intent(this,AlarmReceiver.class), 0));
- break;
- }
- }
public void onClick(View arg0) {
// TODO Auto-generated method stub
switch(arg0.getId()){
case R.id.btnStart:
Log.i("current pid", String.valueOf(android.os.Process.myPid()));
Log.i("current tid", String.valueOf(android.os.Process.myTid()));
((AlarmManager)this.getSystemService(Context.ALARM_SERVICE))
.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 7*1000,
PendingIntent.getBroadcast(this, 0, new Intent(this,AlarmReceiver.class), 0)
);
break;
case R.id.btnStop:
((AlarmManager)this.getSystemService(Context.ALARM_SERVICE))
.cancel(PendingIntent.getBroadcast(this, 0, new Intent(this,AlarmReceiver.class), 0));
break;
}
}
打开logCat选择I,运行清理掉所有日志点击"Start"按钮
从上图可以看到AlarmReceiver类的实例是每触发一次就新建一次的,而且和程序是在同一个进程同一线程内.
- 23:27
- 浏览 (218)
- 评论 (0)
IPC在android是一个非常独特的实现方式,它是通过binder 驱动来实现不同进程的通信的.
深入的了解清看李先静的<<Android IPC机制详解>>
不同进程之间交换数据通过Parcelable包装交换数据.可以通过Intent在不同的进程之间传送数据.
1,Parcelable包装交换数据实现
a,新建一个实现Parcelable接口的Wrap类.示例
- public class Wrap implements Parcelable {
- public T Data;
- public int describeContents() {
- return 0;
- }
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeXX(Data);
- }
- public Wrap(T value) {
- Data =value;
- }
- private Wrap(Parcel in){
- Data=in.readXX();
- }
- public static final Parcelable.Creator<Wrap> CREATOR = new Parcelable.Creator<Wrap>(){
- public Wrap createFromParcel(Parcel source) {
- return new Wrap(source);
- }
- public Wrap[] newArray(int size) {
- return new Wrap[size];
- }
- };
- }//end class
public class Wrap implements Parcelable {
public T Data;
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeXX(Data);
}
public Wrap(T value) {
Data =value;
}
private Wrap(Parcel in){
Data=in.readXX();
}
public static final Parcelable.Creator<Wrap> CREATOR = new Parcelable.Creator<Wrap>(){
public Wrap createFromParcel(Parcel source) {
return new Wrap(source);
}
public Wrap[] newArray(int size) {
return new Wrap[size];
}
};
}//end class
b,数据交互
Bundle.putParcelable(String key,Parcel value) 传入数据
Bundle.getParcelable(String key) 返回传入的Parcelable实例,再通过该实例获取数据
- 21:58
- 浏览 (159)
- 评论 (0)
android数据的存储方式可以用标准的j2se的api文件存储和网络存储方式.以下是android的其他两种存储方式
1,Shared Preferences是一个简单键值对的xml格式的存储方式
a,读取
getPreferences (int mode) 返回SharedPreferences实例
mode Activity.MODE_PRIVATE, Activity.MODE_WORLD_READABLE, Activity.MODE_WORLD_WRITEABLE
getString (String key, String defValue) 获取数据
b,写数据
通过SharedPreferences(必须为MODE_PRIVATE或MODE_WORLD_WRITEABLE)实例的edit()返回Editor对象
Editor.putString (String key, String value) 设置值
Editor.commit() 提交保存
Editor.clear() 清除所有数据
Editor.remove (String key) 移除某一值
2,SQLite
a,SQLiteDatabase类
1),打开/关闭数据库
openOrCreateDatabase(String name, int mode, CursorFactory factory)返回SQLiteDatabase实例 //数据库不存在则新建一个
mode Context.MODE_PRIVATE, Context.MODE_WORLD_READABLE, Context.MODE_WORLD_WRITEABLE, Context.MODE_PRIVATE
SQLiteDatabase类.close()关闭数据库
2),执行数据库操作
SQLiteDatabase.execSQL (String sql) //执行Sql语句
long insert (String table, String nullColumnHack, ContentValues values)
int update (String table, ContentValues values, String whereClause, String[] whereArgs)
int delete (String table, String whereClause, String[] whereArgs)
其中ContentValues是一个键值对,通过ContentValues.put方法设置列名和列值
Cursor rawQuery (String sql, String[] selectionArgs) //执行的是语句
Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
Cursor query (boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having,
String orderBy, String limit)
其中:
columns 显示列
selection 条件where部分
selectionArgs ?替换符号.如selection为name =? and age=?那么selectionArgs可以为 new String[]{"abc","20"}
Cursor类
int getCount() 返回记录数
boolean isClosed () 判断关闭
boolean isFirst ()
boolean isLast ()
boolean moveToFirst ()
boolean moveToLast ()
boolean moveToNext ()
boolean moveToPosition (int position)
boolean moveToPrevious ()
boolean isNull (int columnIndex) 判断是否为空
XXX getXXX(int columnIndex) 读取某一列的数据
3),事务
SQLiteDatabase.beginTransaction() //打开
SQLiteDatabase.setTransactionSuccessful() //提交
SQLiteDatabase.endTransaction() //结束
b,SQLiteOpenHelper
通过该继承该类可以获取到数据库创建/打开/升级等信息
须实现的方法
onCreate(SQLiteDatabase db) //数据库被创建时触发
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) //升级数据库
其他重写
onOpen(SQLiteDatabase db) 书库打开是触发
方法
SQLiteDatabase getReadableDatabase ()
synchronized SQLiteDatabase getWritableDatabase ()
无论是Shared Preferences还是SQLite都是程序私有的其他程序无法直接访问.要想访问其他程序的数据须实现Content Provider.
- 21:47
- 浏览 (201)
- 评论 (0)
Service拥有一个单独进程的模块.
1,继承自Service类,须实现public IBinder onBind(Intent intent)
2,通过startServie触发运行,stopService终止运行
生命周期: onCreate(如果是第一次运行) -> onStart -> onDestroy
3,绑定触发(调用4功能前提)
boolean bindService (Intent service, ServiceConnection conn, int flags) //flags:Context.BIND_AUTO_CREATE
ServiceConnection须实现两个方法
onServiceConnected(ComponentName name, IBinder service)
onServiceDisconnected(ComponentName name)
相对应的方法unbindService (ServiceConnection conn)
生命周期: onCreate(如果是第一次运行)-> onBind(仅一次,不可多次绑定)--> onUnbind-> onDestory
4,怎样提供客户端调用Service方法
a,新建aidl文件定义XX接口
b,实现XX接口的XX.Stub()该类下实现XX接口定义的方法
c,XX.Stub()实例返回给public IBinder onBind(Intent intent)
d,在ServiceConnection.onServiceConnected(ComponentName name, IBinder service) 中通过
XX.Stub.asInterface(service)返回XX接口实例
5,获取系统正在运行的Services
ActivityManager am = (ActivityManager)Activity.getSystemService(ACTIVITY_SERVICE);
am.getRunningServices();
- 21:41
- 浏览 (65)
- 评论 (0)
[传入]
1,设置传入
a,新建一个Bundle实例,Bundle.putXX添加数据,Intent.putExtras传递如Bundle参数
b, Intent.putExtra设置键值对
startActivityForResult启动另外一个Activity
[获取传入]
getIntent().getExtras()返回Bundle存储其他Activity传入的数据
b,传出返回值
1,设置返回
Intent.putExtra设置键值对
Activity.setResult送出返回值
2,获取返回值
重写protected void onActivityResult(int requestCode, int resultCode, Intent data)
data里存储的是返回值
- 22:19
- 浏览 (155)
- 评论 (0)
用于接收外部事件(注意BroadcastReceiver是用时创建用完后即摧毁的)
1,获取
a,注册 Context.registerReceiver (BroadcastReceiver receiver, IntentFilter filter)
receiver 为获取BroadcastReceiver事件的类
filter 事件过滤器为sendBroadcast中的intent
与之对应的Context.unregisterReceiver (BroadcastReceiver receiver)
或在<application>节内添加
<receiver android:name=".接收事件BoardCastReceiver类">
<intent-filter android:name="要接受的事件">
</intent-filter>
</receiver>
b,继承该类须实现 public void onReceive(Context context, Intent intent)方法
2,产生
Context.sendBroadcast
如果传入的参数是Intent(name)那么name应该等于<intent-filter android:name="要接受的事件">
- 22:18
- 浏览 (153)
- 评论 (0)
[Toast]
1,显示提示信息
public static Toast makeText (Context context, CharSequence text, int duration).show()
public static Toast makeText (Context context, int resId, int duration).show()
duration可以是:Toast.LENGTH_LONG,Toast.LENGTH_SHORT
[Notification]
1,(NotificationManager)getSystemService(NOTIFICATION_SERVICE) 获取NotificationManager 通知管理器
2,实例化Notification
设置属性
icon 图标
tickerText 通知栏显示的内容
defaults 震动(Notification.DEFAULT_VIBRATE) /声音(Notification.DEFAULT_SOUND) /光照(Notification.DEFAULT_LIGHTS) /Notification.DEFAULT_ALL
3,通知被查看设置
setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)
contentIntent=PendingIntent.getActivity(this, 0, new Intent(this, 该通知被点击时调用Activity.class), 0)
4,显示
NotificationManager.notify(int id, Notification notification)显示通知
5,取消显示
NotificationManager.cancel(int id)//id=notify中的id且大于0
NotificationManager.cancelAll()取消全部通知
6,其他
振动加权限<uses-permission android:name="android.permission.VIBRATE"></uses-permission>
- 22:15
- 浏览 (140)
- 评论 (0)
1,继承自TabActivity类
2,getTabHost()获取TabHost实例
3, LayoutInflater.from(this).inflate(界面布局Id, tabHost.getTabContentView(), true);
4,添加选项卡
tabHost.addTab(
tabHost.newTabSpec(选项卡Tag) //返回TabSpec实例
.setIndicator(选项卡显示标题) //或.setIndicator(CharSequence label, Drawable icon)
.setContent(选项卡绑定视图Id)); //或.setContent(Intent intent) 设置其他Activity为选项卡
5,监听选项卡更改事件
TabHost.setOnTabChangedListener 重写public void onTabChanged(String arg0)//arg0=选项卡Tag
6,方法
setCurrentTab (int index)//设置第几个选项卡选中
setCurrentTabByTag (String tag)设置选项卡选中,tag=选项卡Tag
相对应的方法
getCurrentTab ()
getCurrentTabByTag ()
7,注意事项
Toast在这种布局中不起作用
- 22:12
- 浏览 (205)
- 评论 (0)
1,
a,创建.重写public boolean onCreateOptionsMenu(Menu menu)
menu.add添加菜单项
b,监听菜单选中事件.重写public boolean onOptionsItemSelected(MenuItem item)//item.getItemId()为菜单项的id
2,带子菜单的菜单
重写public boolean onCreateOptionsMenu(Menu menu)
menu.addSubMenu(CharSequence title)返回父菜单SubMenu,SubMenu.add添加其下级菜单
3,从XML加载菜单
getMenuInflater()返回MenuInflater实例或 new MenuInflater(Context context)
MenuInflater.inflate(int menuRes, Menu menu) 加载XML
4,上下文菜单
1,registerForContextMenu(View v)注册要显示上下文菜单的控件
2,重写public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) 创建上下文菜单
- 22:11
- 浏览 (71)
- 评论 (0)
1,创建
a,new AlertDialog.Builder(Context context)
b,一些设置
c,create()创建
d,show()显示
e,dismiss()退出对话框
2,常用方法
setIcon:设置图标
setTitle:设置标题
setPositiveButton:设置 确定按钮
setNegativeButton:设置 取消按钮
setNeutralButton:设置 忽略按钮
setCancelable(Boolean arg0)//按回退键是否可以取消
3,可选但唯一的方法
setMessage 设置显示消息
setItems 显示单选列表,选择后对话框退出
setSingleChoiceItems 显示单选列表,选择后对话框不退出
setMultiChoiceItems 显示多选列表,选择后对话框不退出
4,从资源文件加载对框视图
通过setView( LayoutInflater.from(Context context).inflate(resource, null) )安装视图
5,通过Activity.showDialog(int id)来显示对话框
重写Activity的以下方法
protected Dialog onCreateDialog(int id) //Dialog第一次创建时调用.这里需要通过AlertDialog.create()返回创建的对话框
protected onPrepareDialog(int id, Dialog dialog) //Dialog显示前调用
6,其他对话框
a,ProgressDialog对话框
setIndeterminate 设置进度条是否自动运转
setProgressStyle 设置显示风格.ProgressDialog.STYLE_HORIZONTAL/ProgressDialog.STYLE_SPINNER
setProgress 设置进度,只有对话框显示后才有用
setCancelable(Boolean arg0)//按回退键是否可以取消,进度条对框框默认不取消
setMessage() 设置显示内容
最简单的显示方法: public static ProgressDialog show (Context context, CharSequence title, CharSequence message)
b,TimePickerDialog/DatePickerDialog
- 22:09
- 浏览 (157)
- 评论 (0)
个人学习android做的笔记,贴出来备忘.
1,EditText
主要函数:setText/getText设置/获取文本内容,setHint设置缺省显示内容;
2,RadioGroup,RadioButton
RadioButton的isChecked()判断是否被选中
获取选中RadioButon的ID:设置RadioGroup.setOnCheckedChangeListener方法
public onCheckedChanged(RadioGroup group,int checkedId)//checkedId是选中RadioButton的ID
3,CheckBox
isChecked()判断是否被选中
setOnCheckedChangeListener方法监视选中状态改变情况
4,Spinner
a,显示数据
1),硬编码加载
通过setAdapter方法设置类型为ArrayAdapter (Context context, int textViewResId, String []objects)
textViewResourceId:显示内容的ViewID默认设置为R.layout.simple_spinner_item
objects:显示的内容
2),从资源文件中加载
ArrayAdapter.createFromResource (Context context, int textArrayResId, int textViewResId) //textArrayResId是资源ID
返回ArrayAdapter<CharSequence>
b,设置下拉列表的风格(可选)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
c,监听选项选择变更事件
setOnItemSelectedListener设置监听去
Spinner.OnItemSelectedListener类须实现以下两个方法
public void onItemSelected (AdapterView<?> parent, View view, int position, long id)//view 选中项实例,position选择项在adapter中的位置
public void onNothingSelected(AdapterView<?> arg0)
d,设置选中项
Spinner.setSelection(position)//索引从0开始
d,获取选中项
getSelectedItemPosition ()
getSelectedItem () //该值toString()则为选中内容的字符串
getSelectedItemId ()
getSelectedView ()
5,AutoCompleteTextView
1,setAdapter设置数据adapter
2,设置输入多少个字符显示提示AutoCompleteTextView.setThreshold(int threshold)
6,MultiAutoCompleteTextView (允许输入多值,多值之间会自动地用指定的分隔符分开)
1,setAdapter设置数据adapter
2,setTokenizer设置提示Tokenizer缺省的为new MultiAutoCompleteTextView.CommaTokenizer()以逗号分隔
7,DatePicker,TimePicker
a,DatePicker
1),设置初始显示日期init(int Year, int month, int day, new DatePicker.OnDateChangedListener(){
public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth){}
})
2),获取设置值 getYeah()/getMonth()/getDayOfMonth()
b,TimePicker
1),设置setCurrentHour (Integer currentHour) /setCurrentMinuter (Integer currentHour)
2,setIs24HourView(Boolean)设置是否为24小时制显示
3,监听设置改变setOnTimeChangedListener
c,获取系统当期时间和日期
Calendar.getInstance()返回Calendar
Calendar.get (int field) ---field 可以为Calendar.YEAR/ Calendar.MONTH/ Calendar.DAY_OF_MONTH/ Calendar.HOUR_OF_DAY/ Calendar.MINUTE
8,ImageView/ImageButton
主要方法
setImageResource 设置显示图片
setAlpha 设置Alpha
invalidate 强制更新
setScaleType( ScaleType st) 设置图片显示方式,st是一枚举
setAdjustViewBounds 设置是否调整控件适应图片大小
setBackgroundResource 设置背景
9,ImageSwitcher(显示一系列的图片,但当前只显示一张图片)
显示数据
setFactory( ViewSwitcher.ViewFactory factory)设置要显示的数据
,ViewFactory接口须实现方法public View makeView(){}负责提供当前显示的视图(ImageView),且View必须为新实例
方法
setImageResource设置当前显示的图片
getCurrentView()返回当前显示的视图
setInAnimation(Animation ani)设置视图装载入时的动画效果,AnimationUtils.loadAnimation(Context context, int id) 获取动画效果android.R.anim.XX
setOutAnimation(Animation ani)设置视图装载入时的动画效果
10,Gallery(显示一系列的图片,提供拖动等特效)
显示数据
setAdapter(SpinnerAdapter adapter)设置数据适配器.
数据适配器可以继承自BaseAdapter,该类 public View getView(int position, View convertView, ViewGroup parent) 返回当前选择的视图(ImageView)
选项选中监听setOnItemSelectedListener
11,GridView(表格显示一系列图片)
显示数据
setAdapter(ListAdapter adapter)设置数据适配器.
数据适配器可以继承自BaseAdapter,该类 public View getView(int position, View convertView, ViewGroup parent) 返回当前选择的视图(ImageView)
选项选中监听setOnItemSelectedListener
getSelectedView()返回当前选中的视图
12,ScrollView
13,ProgressBar
setIndeterminate 设置进度条是否自动运转
setProgressStyle 设置显示风格.ProgressDialog.STYLE_HORIZONTAL/ProgressDialog.STYLE_SPINNER
setProgress 设置进度
setMax 设置最大值
getProgress()获取当前进度
14,SeekBar
方法
setMax 设置最大值
getProgress()获取当前值
setProgress 设置值
setIndeterminate
监听器
setOnSeekBarChangeListener其下有三个方法
public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) //数值变更,arg1新值,是否Touch
public void onStartTrackingTouch(SeekBar seekBar)//开始拖动
public void onStopTrackingTouch(SeekBar seekBar) //结束拖动
15,ListView
a,显示数据setAdapter(ListAdapter adapter)
adapter可为new SimpleCursorAdapter/SimpleAdapter(Context context, int layout, Cursor c, String[] from, int[] to)
layout 用来显示数据的模板.显示一列可用android.R.layout.simple_list_item_1 两列可用android.R.layout.simple_list_item_2
多列则需要自己实现xml视图布局文件
c 数据(可用ArrayList构造数据)
from ':
to 用来显示对应列的空件id
b,动态增删数据
adapter.notifyDataSetChanged()
d,设置背景色
setBackGroudRource
- 22:04
- 浏览 (292)
- 评论 (0)
- 22:01
- 浏览 (138)
- 评论 (0)
Google于2007年底正式发布了Android SDK, 作为 Android系统的重要特性,Dalvik虚拟机也第一次进入了人们的视野。它对内存的高效使用,和在低速CPU上表现出的高性能,确实令人刮目相看。依赖于底层Posix兼容的操作系统,它可以简单的完成进程隔离和线程管理。每一个Android应用在底层都会对应一个独立的Dalvik虚拟机实例,其代码在虚拟机的解释下得以执行。
很多人认为Dalvik虚拟机是一个Java虚拟机,因为Android的编程语言恰恰就是Java语言。但是这种说法并不准确,因为Dalvik虚拟机并不是按照Java虚拟机的规范来实现的,两者并不兼容;同时还要两个明显的不同:
- Java虚拟机运行的是Java字节码,而Dalvik虚拟机运行的则是其专有的文件格式DEX(Dalvik Executable)。
- 在Java SE程序中的Java类会被编译成一个或者多个字节码文件(.class)然后打包到JAR文件,而后Java虚拟机会从相应的CLASS文件和JAR文件中获取相应的字节码;Android应用虽然也是使用Java语言进行编程,但是在编译成CLASS文件后,还会通过一个工具(dx)将应用所有的CLASS文件转换成一个DEX文件,而后Dalvik虚拟机会从其中读取指令和数据。
Dalvik和Android系统
Android作为新一代的基于Linux的开源手机操作系统,其系统架构由下而上可以分为以下几部分:
- Linux内核
- 本地库
- Android运行库
- 应用框架
- 应用
如图所示,Android运行库包括两部分:核心库和Dalvik虚拟机。核心库包括了最基本的类库,如data structure, network, Utilities, File system等的,很多实现代码都是来自Apache Harmony项目,主要目的是保证虚拟机的类库能够和Java SE的类库最大可能的兼容,从而降低应用开发者从Java SE阵营转移到Android开发阵营的难度,增加其可用性。Dalvik虚拟机主要是完成对象生命周期的管理,堆栈的管理,线程管理,安全和异常的管理,以及垃圾回收等等重要功能。
Dalvik虚拟机的主要特征
Dalvik虚拟机非常适合在移动终端上使用,相对于在桌面系统和服务器系统运行的虚拟机而言,它不需要很快的CPU速度和大量的内存空间。根据Google的测算,64M的RAM已经能够令系统正常运转了。其中24M被用于底层系统的初始化和启动,另外20M被用于高层启动高层服务。当然,随着系统服务的增多和应用功能的扩展,其所消耗的内存也势必越来越大。
归纳起来,Dalvik虚拟机有如下几个主要特征:
-
专有的DEX文件格式
DEX是Dalvik虚拟机专用的文件格式,而问什么弃用已有的字节码文件(CLASS文件)而采用新的格式呢?
1.一个应用中会定义很多类,编译完成后即会有很多相应的CLASS文件,CLASS文件间会有不少冗余的信息;而DEX文件格式会把所有的CLASS文件内容整合到一个文件中。这样,除了减少整体的文件尺寸,I/O操作,也提高了类的查找速度。
原来每个类文件中的常量池,在DEX文件中由一个常量池来管理,具体方式如下图:
2.增加了新的操作码的支持
3.文件结构尽量简洁,使用等长的指令,借以提高解析速度
4. 尽量扩大只读结构的大小,借以提高跨进程的数据共享
如何生成DEX文件呢?Android系统和Dalvik虚拟机提供了工具(DX),在把Java源代码编译成CLASS文件后,使用DX工具。
- DEX的优化
DEX文件的结构是紧凑的,然是如果我们还想要求运行时的性能有进一步提高,我们就仍然需要对DEX文件进行进一步优化。优化主要是针对以下几个方面:
- 调整所有字段的字节序(LITTLE_ENDIAN)和对齐结构中的没一个域
- 验证DEX文件中的所有类
- 对一些特定的类进行优化,对方法里的操作码进行优化
优化后的文件大小会有所增加,应该是原DEX文件的1-4倍。
优化发生的时机有两个:对于预置应用,可以在系统编译后,生成优化文件,以ODEX结尾。这样在发布时除APK文件(不包含DEX)以外,还有一个相应的ODEX文件;对于非预置应用,包含在APK文件里的DEX文件会在运行时被优化,优化后的文件将被保存在缓存中。
- 基于寄存器
相对于基于堆栈的虚拟机实现,基于寄存器的虚拟机实现虽然在硬件通用性上要差一些,但是它在代码的执行效率上却更胜一筹。一般来讲,虚拟机中指令的解释执行时间主要花在以下三个方面:
- 分发指令
- 访问运算数
- 执行运算
其中“分发指令”这个环节对性能的影响最大。在基于寄存器的虚拟机里,可以更为有效的减少冗余指令的分发和减少内存的读写访问,如:
虽然Dalvik虚拟机并没有使用目前流行的虚拟机技术,如JIT,但是根据Google的报告,这个功能的缺失并没有另Dalvik虚拟机在性能上有所损失。我们也同时相信,Dalvik虚拟机的性能还有进一步提高的空间。
- 一个应用,一个虚拟机实例,一个进程
每一个Android应用都运行在一个Dalvik虚拟机实例里,而每一个虚拟机实例都是一个独立的进程空间。虚拟机的线程机制,内存分配和管理,Mutex等等都是依赖底层操作系统而实现的。所有Android应用的线程都对应一个Linux线程,虚拟机因而可以更多的依赖操作系统的线程调度和管理机制。
不同的应用在不同的进程空间里运行,加之对不同来源的应用都使用不同的Linux用户来运行,可以最大程度的保护应用的安全和独立运行。
Zygote是一个虚拟机进程,同时也是一个虚拟机实例的孵化器,每当系统要求执行一个Android应用程序,Zygote就会FORK出一个子进程来执行该应用程序。这样做的好处显而易见:Zygote进程是在系统启动时产生的,它会完成虚拟机的初始化,库的加载,预置类库的加载和初始化等等操作,而在系统需要一个新的虚拟机实例时,Zygote通过复制自身,最快速的提供个系统。另外,对于一些只读的系统库,所有虚拟机实例都和Zygote共享一块内存区域,大大节省了内存开销。
应用程序包(APK)被发布到手机上后,运行前会对其中的DEX文件进行优化,优化后的文件被保存到缓存区域(优化后的格式被称为DEY),虚拟机会直接执行该文件。如果应用包文件不发生变化,DEY文件不会被重新生成。
Android应用开发和Dalvik虚拟机
Android应用所使用的编程语言是Java语言,和Java SE一样,编译时使用Sun JDK将Java源程序编程成标准的Java字节码文件(.class文件),而后通过工具软件DX把所有的字节码文件转成DEX文件(classes.dex)。最后使用Android打包工具(aapt)将DEX文件,资源文件以及AndroidManifest.xml文件(二进制格式)组合成一个应用程序包(APK)。应用程序包可以被发布到手机上运行。
- 22:00
- 浏览 (134)
- 评论 (0)