<application>节点是AndroidManifest.xml文件中必须持有的一个节点,它包含在<manifest>节点下。通过<application>节点的相关属性,我们可以声明Android应用程序的相关特性。这个节点包含所有应用程序组件的节点,包括Activity,服务,广播接收器和内容提供者,并且包含了一些可能影响所有组件的属性。这些属性中的其中一些又会作为默认值而被设置到应用程序组件的相同属性上,比如icon,label,permission,process,taskAffinity和allowTaskReparenting等,而其他的一些值则作为应用程序的整体被设置,并且不能被应用程序组件的属性覆盖,比如debuggable,enabled,description和allowClearUserData等。

1.<application>节点配置

 

一般来说,在生成Android应用程序的时候,默认的AndroidManifest.xml文件中就已经包含了一些默认的<application>节点,其中包含应用程序的基本属性。现在我们就来看看<application>节点信息的全集,代码如下:

<application android:allowTaskReparenting=["true"|"false"]

android:backupAgent="string"

android:debuggable=["true"|"false"];

android:description="string resource"

android:enabled=["true"|"false"]

android:hasCode=["true"|"false"]

android:hardwareAccelerated=["true"|"false"]

android:icon="drawable reource"

android:killAfterRestore=["true"|"false"]

android:label="string resource"

android:logo="drawable resource"

android:manageSpaceActivity="string"

android:name="string"

android:permission="string"

android:persistent=["true"|"false"]

android:process="string"

android:restoreAnyVersion=["true"|"false"]

android:taskAffinity="string"

android:theme="resource or theme">

</application>

2.如何实现Application类

 

首先要介绍的是android:name属性,它指的是Application类的子类,当应用程序进程被启动的时候,由android:name属性指定的类将会在所有应用程序组件(activity,服务,广播接收器,内容提供者)被实例化之前实例化。

一般情况下,应用程序无需指定这个属性,Android会实例化Android框架下的applicaiton类。

然而,在一些特殊的情况下,比如希望在应用程序组件启动之前就完成一个初始化工作,或者在系统低内存的时候做一些特别的处理,就要考虑实现自己的Application类的一个子类。

在Android系统提供的系统应用中,就有一个实现了自己的Application实例,这个应用程序就是Launcher。我们可以仿照它来实现一个自己的Application类,具体步骤如下。

①创建一个叫做ApplicationTest的项目,并且在默认生成的MainActivity里的onCreate()方法中添加一行代码来输出一条日志。这样就可以看到Application创建时间,具体代码如下:

public class MainActivity extends Activity {
private static final String TAG="MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e(TAG,"MainActivity is created");
}
}

②实现自己的MyApplication类,代码如下:

public class MyApplication extends Application {
private static final String TAG="MyApplication";
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG,"MyApplication is created");
}
}

③将MyApplication添加到清单文件AndroidManifest.xml中<application>内android:name中

从图中可以看出来,Android先创建的MyApplication,最后才创建的MainActivity。

3.Application提供的函数及其用法

android.app.Application类提供了许多类似onCreate()的方法,它们会在不同的场景下被Android框架回调。与此同时,Application类还提供了一些监控的函数,用于监视本应用中组件的生命周期。如下表所示:

方法名称                                                返回值                   注解
onConfigurationChanged(Configuration newConfig) void  如果组件正在运行时设备配置(包括语种,方向,网络等)发生改变,则由系统调用此方法通知应用程序
onCreate() void 当应用程序正在启动时,并且在创建任何其他应用程序对象之前,调用此方法。由于花费在此功能上的时间直接影响了启动一个进程中首个Activity服务或者接收器的速度,所以尽可能快地执行(例如使用缓慢的初始化状态)。如果你重写了这个方法,需要确保调用super.onCreated()
需要注意的是,在实际应用程序中,如果你的应用程序中的某些组件指定了一个process属性(进程),并且此进程并不存在,那么Application的onCreate()回调就会被调用,换句话说,此方法可能会被多次调用
onLowMemory() void 当整个系统正在低内存运行时,并且希望应用程序缩减使用内存的时候,系统调用此方法通知应用程序。但调用此方法的准确点没有定义时,通常它将在所有后台进程已经终止的时间附近发生。
应用程序可执行此方法来释放任何缓冲或其拥有的不必要的资源。系统在从此方法中返回后运行垃圾回收操作。
onTerminate() void 此方法在仿真进程环境中使用,不在生产Android设备上调用,在生产Android设备上,可以通过简单地终止进程来移除进程。进行移除工作时,则不执行任何用户代码(包括此回调)
onTrimMemory() void 回收内存的时候调用。例如,当它进入后台并且没有足够内存保持许多后台进程运行时。
监控回调接口
registerComponentCallbacks
unregisterComponentCallbacks
void

void

在应用程序中注册一个ComponentCallbacks接口。在Activity生命周期发生改变之前,通过此接口的各个方法通知应用程序。使用这个接口,我们可以在Activity生命周期发生改变之前做一些必要的处理
需要大家注意的是,必须确保在未来恰当的时候使用unregisterComponentCallbacks(ComponentCallbacks)移除ComponentCallbacks对象,它是我们之前用registerComponentCallbacks(ComponentCallbacks)注册的。

接下来,我们通过一些实例来说明如何使用这些方法和接口

①使用onConfigurationChanged()方法监听系统配置更新

onConfigurationChanged()方法的函数原型如下:

public void onConfigurationChanged(Configuration newConfig){}其中newConfig参数表示新的设备配置

onConfigurationChanged()方法是一个回调接口,在设备配置发生变化时由Android系统调用。与此同时,Android系统会通过参数(newConfig)传给应用程序,由应用程序处理这个变化。注意,不同于Activity,其他组件在一个配置改变时从不重新启动,它们孙弱自己处理改变的结果。这里所述的“配置”如下表所示:

配置项 注解
fontScale 表示当前的系统的字体缩放比例,它是基于像素密度缩放的。
注意,在使用用户模式编译出来的系统固件中,不包含修改此项配置的界面,只能通过编程的方法去改变。
数据类型:浮点型
hardKeyBoardHidden 指示硬键盘是否被隐藏起来,此配置项有3个取值,具体如下所示。
0.HARDKEYBOARDHIDDEN_UNDEFINED(Android无法识别的键盘状态)
1.HARDKEYBOARDHIDDEN_NO(硬键盘可用)
2.HARDKEYBOARDHIDDEN_YES(硬键盘被隐藏)
数据类型:整型
keyboard 指示添加到设备上的是哪个种类的键盘,此配置项有以下4个取值
0.KEYBOARD_UNDEFINED(Android无法识别的键盘)
1.KEYBOARD_NOKEYS(无按键键盘)
2.KEYBOARD_QWERTY(打字机键盘)
3.KEYBOARD_12KEY(12键键盘)
数据类型:整型
keyboardHidden 指示当前是否有键盘可用。如果在有硬键盘的Android设备中,硬键盘被收起,而仍有软键盘,则认为键盘是可用的。这个字段有如下3个取值。
0.KEYBOARDHIDDEN_UNDEFINED(Android无法识别的键盘状态)
1.KEYBOARDHIDDEN_NO(仍有软键盘可见)
2.KEYBOARDHIDDEN_YES(所有的软键盘都被隐藏)。
数据类型:整型
locale 定义了设备的语言环境。它包含了国家以及语言信息,这些信息被包含在一个java.util.Locale类型的对象中
mcc IMSI的移动国家码,如果是0,表示未定义。
注意:IMSI是指国际移动用户识别码,它存储在我们的SIM卡中,其总长度不超过15位。
数据类型:整型
mnc IMSI的移动网络号,如果是0表示未定义
数据类型:整型
navigation 指示当前设备可用的导航方式,它有如下5个取值。
0.NAVIGATION_UNDEFINED(未定义的导航方式)
1.NAVIGATION_NONAV(无导航)
2.NAVIGATION_DPAD(面板导航方式)
3.NAVIGATION_TRACKBALL(轨迹球导航)
4.NAVIGATION_WHEEL(滚轮方式导航)
数据类型:整型
navigationHidden 用于指示导航是否可用,有如下取值。
0.NAVIGATIONHIDDEN_UNDEFINED
1.NAVIGATIONHIDDEN_NO
2.NAVIGATIONHIDDEN_YES
数据类型:整型
orientation 指示屏幕方向的标志,有如下4个取值。
0.ORIENTATION_UNDEFINED(未定义的方法)
1.ORIENTATION_PORTRAIT(竖屏方向,屏幕宽度小于高度)
2.ORIENTATION_LANDSCAPE(横屏方向,屏幕宽度大于高度)
3.ORIENTATION_SQUARE(正方形屏幕,认为屏幕宽度等于高度)
注意:在窗口管理服务(WindowManagerService)中计算新配置时,orientation的默认配置是ORIENTATION_SQUARE
数据类型:整型
screenHeightDp 屏幕可用部分的高度
screenLayout 指示屏幕的整体属性,它包括两个部分。
⒈SCREENAYOUT_SIZE_MASK:标志屏幕大小的属性(比如大屏幕,小屏幕等),它有以下5个取值。
SCREENAYOUT_SIZE_UNDEFINED:未定义(值:0)
SCREENAYOUT_SIZE_SMALL:小屏幕(值:1,屏幕分辨率至少为320*426)。
SCREENAYOUT_SIZE_NORMAL:普通屏幕(值:2,屏幕分辨率至少为320*470)
SCREENAYOUT_SIZE_LARGE:大屏幕(值:3,屏幕分辨率至少为480*640)
SCREENAYOUT_SIZE_XLARGE:加大屏幕(值:4,屏幕分辨率至少为720*960)
⒉SCREENAYOUT_LONG_MASK:指示屏幕是否比通常情况上更高或者更宽,它有如下3个取值。
SCREENAYOUT_LONG_UNDEFINED:未定义(十六进制值为0)
SCREENAYOUT_LONG_YES:是(十六进制值为20)
SCREENAYOUT_LONG_NO:否(十六进制值为10)
screenWidthDp 屏幕可用部分的宽度
smallestScreenWidthDp 在正常操作中,应用程序将会看到最小的屏幕尺寸。这是在竖屏和横屏中screenWidthDp和ScreenHeightDp的最小值。
touchscreen 设备上触摸屏的种类,它支持如下取值。
0.TOUCHSCREEN_UNDEFINED(未定义模式)
1.TOUCHSCREEN_NOTOUCH(无触屏模式)
2.TOUCHSCREEN_STYLUS(手写笔模式)
3.TOUCHSCREEN_FINGER(手指触屏模式)
uiMode UI模式的位掩码,目前有两个字段。
⒈UI_MODE_TYPE_MASK:定义了设备的整个UI模式,它支持如下取值。
UI_MODE_TYPE_UNDEFINED:未知模式
UI_MODE_TYPE_NORMAL:通常模式
UI_MODE_TYPE_DESK:带底座模式‘
UI_MODE_TYPE_CAR:车载模式
⒉UI_MODE_NIGHT_MASK:定义了屏幕是否在一个特殊模式中。它支持如下取值。
UI_MODE_NIGHT_UNDEFINED:未定义模式
UI_MODE_NIGHT_NO:非夜晚模式
UI_MODE_NIGHT_YES:夜晚模式

下面我们通过一个实例来说明当设备配置发生变化的时候,系统如何通过onConfigurationChanged回调接口来通知应用程序的。

㈠为前面的应用程序添加一个名叫ConfigApplication的Application的子类,并实现onCreate()方法及onConfigurationChanged()方法。在onCreate()方法中,我们会获取应用程序在创建之初所拥有的配置信息。而在onConfigurationChanged()方法中,则可以添加一些代码以便用日志的方式来实时体现配置更新。相关代码如下:

public class ConfigApplication extends Application {
private static final String TAG="ConfigApplication";
private Configuration mConfiguration;
@Override
public void onCreate() {
super.onCreate();
this.mConfiguration=getResources().getConfiguration();//获取配置信息
Log.e(TAG,"onCreate::infomation:orientation="+this.mConfiguration.orientation);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//打印更新后的配置信息
Log.e(TAG,"onConfigurationChanged:infomation:orientation="+newConfig.orientation);
}
}

㈡按前文所述,将ConfigApplication配置到AndroidManifest.xml文件中。

㈢设备运行应用程序,就可以看到如下的日志信息了。

对于日志,说明如下:

日志信息的第一行是初始状态下的方向配置,通过上图我们知道最初的方向值是1。而根据前面的表,可知当前是竖屏方向。

日志信息的第五行是切换横屏后,Android系统回调了我们实现的onConfigurationChanged()方法,这时系统配置已经发生了改变,因此这里的日志打印了当前的屏幕方向是2,也是就横屏。

建议:由于基类onConfigurationChanged()方法中实现了对一些回调接口的调用,所以如果我们重写了这个方法,那么为了维持原Application类的行为,建议在重写的方法入口调用super.onConfigurationChanged(newConfig)。

②使用onCreate()完成应用程序初始化

onCreate()方法的原型为:

public void onCreate(){}

如前面的表所示,onCreate()方法是一个回调接口。Android系统会在应用程序启动的时候,在任何应用程序组件(Activity,服务,广播接收器,内容提供者)被创建之前调用这个接口。

需要注意的是,这个方法的执行效率会直接影响到启动Activity,服务,广播接收器,或者内容提供者的性能,因此该方法应尽可能快地完成。

最后,如果实现了这个回调接口,请前晚不要忘记调用super.onCreate(),否则应用程序会报错。

前面我们实现了Appplication类的子类------Configuration,并且也已经实现了自身的onCreate()方法。这里来做个小实验,让大家更清楚这些知识。

现在,在源代码的onCreate()方法中加入一个大约20秒的等待,以此来模拟在onCreate()方法中做了过于繁重的工作而导致该方法长时间无法完成的情况,修改后的代码如下:

public class ConfigApplication extends Application {
private static final String TAG="ConfigApplication";
private Configuration mConfiguration;
@Override
public void onCreate() {
super.onCreate();
this.mConfiguration=getResources().getConfiguration();//获取配置信息
Log.e(TAG,"onCreate::infomation:orientation="+this.mConfiguration.orientation);
SystemClock.sleep(20000);//沉睡20秒
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//打印更新后的配置信息
Log.e(TAG,"onConfigurationChanged:infomation:orientation="+newConfig.orientation);
}
}

此时运行程序,程序就会崩溃,当然,在真实的设备上,是可以等待的,有的并不会造成崩溃,比如经在小米上测试50秒,程序并没有崩溃,而是等待下去,直到程序正常。当这样会造成不好的用户体验。所以在以后开发过程中,要充分考虑到这些容易出错的情况。

③使用onLowMemory()回调方法监视低内存

该方法的原型为:

public void onLowMemory(){}

当整个系统在低内存运行时,将调用该方法。

好的应用程序会实现该方法来释放任何缓存或者其他不需要的资源。系统从该方法返回之后,将执行一个垃圾回收操作。

④使用registerActivityLifecycleCallbacks()注册可以监视Activity生命周期的接口

registerActivityLifecycleCallbacks()方法的原型为:

public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback){}

在该方法中,参数callbacks表示Activity生命周期的接口。

从Android4.0以后,Android SDK为应用程序提供了一套完整的接口以便监视与本Application相关的Activity的生命周期(创建,启动以及暂停等),它的名字叫做ActivityLifecycleCallbacks。只要在Application中通过registerActivityLifecycleCallbacks()方法将接口注册上,它就会通过ActivityLifecycleCallbacks提供应用程序中相关的Activity生命周期信息。下表列出了这些接口以及用途。

方法原型 参数说明 用途
abstract void onActivityCreated(Activity activity,Bundle savedInstanceState) activity:创建的Activity实例
savedInstanceState:创建该Activity时所带的信息(一个Bundle实例)
在应用程序创建Activity之前调用,用于通知接口实现者Activity将要被创建。
abstract void onActivityDestroyed(Activity activity) activity:销毁的Activity实例 在应用程序销毁Activity之前调用,用于通知接口实现者Activity将要被销毁。
abstract void onActivityPaused(Activity activity) activity:暂停的Activity实例 在应用程序暂停Activity之前调用,用于通知接口实现者Activity将要被暂停。
abstract void onActivityResumed(Activity activity) activity:恢复的Activity实例 在应用程序正在恢复Activity之前调用,用于通知接口实现者Activity将要被恢复。
abstract void onActivitySaveInstanceState(Activity activity,Bundle outState) activity:正在执行状态保存的的Activity实例
outState:需要保存的Activity状态
指示当前Activity正在保存自己的状态,这些状态包含在outState中。
abstract void onActivityStarted(Activity activity) activity:启动的Activity实例 在应用程序正在启动Activity之前调用,用于通知接口实现者Activity将要被启动。
abstract void onActivityStopped(Activity activity) activity:停止的Activity实例 在应用程序正在停止Activity之前调用,用于通知接口实现者Activity将要被停止。

特别提醒:从接口定义中,我们可以知道如下信息。

Ⅰ这些接口都是抽象的,因此当我们实现ActivityLifecycleCallbacks接口时,就必须实现这些方法,哪怕只是空实现。

Ⅱ这些接口的返回值都是void,这说明它们只用于通知,别无它用。

另外我们在必要时要调用unregisterActivityLifecycleCallbacks()方法来注销掉原先注册的接口以免造成不必要的资源浪费。

下面我们通过一个实例来说明配置发生变化的时候,系统如何通过onConfigurationChanged()回调接口来通知应用程序,具体的步骤如下所示。

㈠实现自己的Application子类(名叫ALCApplication)。我们将在应用程序创建(onCreate()方法中)时注册自己的Activity生命周期接口,在程序终止(onTerMinate()方法中)时注销这个接口。当完成这些工作以后,将得到如下所示的代码:

public class ALCApplication extends Application {
private final static String TAG="ALCApplication";
private ActivityLifecycleCallbacks mActivityLifecycleCallbacks=new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
Log.e(TAG,"onActivityCreated");
}
@Override
public void onActivityStarted(Activity activity) {
Log.e(TAG,"onActivityStarted");
}
@Override
public void onActivityResumed(Activity activity) {
Log.e(TAG,"onActivityResumed");
}
@Override
public void onActivityPaused(Activity activity) {
Log.e(TAG,"onActivityPaused");
}
@Override
public void onActivityStopped(Activity activity) {
Log.e(TAG,"onActivityStopped");
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
Log.e(TAG,"onActivitySaveInstanceState");
}
@Override
public void onActivityDestroyed(Activity activity) {
Log.e(TAG,"onActivityDestroyed");
}
};
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(this.mActivityLifecycleCallbacks);
}
@Override
public void onTerminate() {
super.onTerminate();
unregisterActivityLifecycleCallbacks(this.mActivityLifecycleCallbacks);
}
}

㈡将ALCApplication配置到AndroidManifest.xml中,当配置完成时,最后的结果看起来与下图类似:

这里我们通过接口监视Activity从启动到推出的生命周期。

在这个实例中,我们在onTerminate()方法中做了注销接口的工作。但值得注意的是,onTerminate()方法只会在虚拟机进程中被调用,永远不会在真实的Android设备中被调用。

⑤使用registerComponentCallbacks()注册一个可以用来舰艇Activity生命周期的接口

该方法原型为:

public void registerComponentCallbacks(ComponentCallbacks callback){}

其中参数callback是ComponentCallbacks 接口的一个实现。当Activity的生命周期发生变化时,会通过这个接口通知应用程序。对于所有应用程序来,它是通用的回调API集合的接口。ComponentCallbacks中只包括两个方法,它们分别是public abstract void onConfigurationChanged(Configuration newConfig)和public abstract void onLowMemory()。 这两个方法的调用与Application中的同名回调方法的调用条件一样的。

ComponentCallbacks()和registerComponentCallbacks()方法的用法与ActivityLifecycleCallbacks()和registerActivityLifecycleCallbacks()的用法是一样的,这里就不单举例说明了。

Android清单文件详解(三)----应用程序的根节点<application>的更多相关文章

  1. Android清单文件具体解释(三)----应用程序的根节点&lt;application&gt;

    <application>节点是AndroidManifest.xml文件里必须持有的一个节点,它包括在<manifest>节点下.通过<application>节 ...

  2. Android.mk文件详解(转)

    源:Android.mk文件详解 从对Makefile一无所知开始,折腾了一个多星期,终于对Android.mk有了一个全面些的了解.了解了标准的Makefile后,发现Android.mk其实是把真 ...

  3. Android Studio 插件开发详解三:翻译插件实战

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78113868 本文出自[赵彦军的博客] 一:概述 如果不了解插件开发基础的同学可以 ...

  4. Android WebView 开发详解(三)

    转载请注明出处   http://blog.csdn.net/typename/article/details/40302351 powered by miechal zhao 概览 Android ...

  5. Android编译系统(Android.mk文件详解)

    [Android-NDK(Native Development Kit)  docs文档] NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成a ...

  6. Android清单文件具体解释(二) ---- 应用程序权限声明

    我们知道,Android系统的各个模块提供了很强大的功能(比方电话,电源和设置等),通过使用这些功能.应用程序能够表现的更强大.更灵活.只是,使用这些功能并非无条件的.而是须要拥有一些权限.接下来,我 ...

  7. Android事件分发详解(三)——ViewGroup的dispatchTouchEvent()源码学习

    package cc.aa; import android.os.Environment; import android.view.MotionEvent; import android.view.V ...

  8. MyBatis之Mapper XML 文件详解(三)-Result Maps

    resultMap 元素是 MyBatis 中最重要最强大的元素.它就是让你远离 90%的需要从结果 集中取出数据的 JDBC 代码的那个东西, 而且在一些情形下允许你做一些 JDBC 不支持的事 情 ...

  9. Android Studio 插件开发详解四:填坑

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78265540 本文出自[赵彦军的博客] 在前面我介绍了插件开发的基本流程 [And ...

随机推荐

  1. SVN Can't open file 'xxx':Premission denied

    换了一台电脑,重新搭建本地svn服务器的时候,服务器搭起来了,但是用Cornerstone往服务器上传工程的时候报错 报错有以上两种,都是因为文件权限的限制 解决方法 第一种报错 1.在Finder里 ...

  2. 21分钟 MySQL 入门教程

    目录 一.MySQL的相关概念介绍 二.Windows下MySQL的配置 配置步骤 MySQL服务的启动.停止与卸载 三.MySQL脚本的基本组成 四.MySQL中的数据类型 五.使用MySQL数据库 ...

  3. 解决springmvc中文件下载功能中使用javax.servlet.ServletOutputStream out = response.getOutputStream();后运行出异常但结果正确的问题

    问题描述: 在springmvc中实现文件下载功能一般都会使用javax.servlet.ServletOutputStream out = response.getOutputStream();封装 ...

  4. Effective Java 14 In public classes, use accessor methods, not public fields

    Principle To offer the benefits of encapsulation you should always expose private field with public ...

  5. MaxMin搜索

  6. 以前写的关于Linux C/C++的博客

    以前在CU写的关于Linux C/C++的博客 http://blog.chinaunix.net/uid/25909722/cid-24318-list-1.html

  7. nginx添加模块 (非覆盖安装)

    nginx添加模块(非覆盖安装) 原已经安装好的nginx,现在需要添加一个未被编译安装的模块: 查看原来编译时都带了哪些参数# /usr/local/nginx/sbin/nginx -V ngin ...

  8. 手机号码js正则验证

    手机号码js正则验证 var myreg = /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\d{8})$/; if (!myreg.test($(" ...

  9. linux中C语言获取高精度时钟gettimeofday函数

    前言:    在开发中,很多时候需要知道各个函数或者是某些设备对命令的操作用时,因此需要用到 gettimeofday 来获取当前时钟. 一,函数说明 #include  int gettimeofd ...

  10. Centos7 搭建hadoop2.6 HA

    用户配置: User :root Password:toor 2.创建新用户 student Pwd: student 3.安装virtualbox的增强工具软件 4.系统默认安装的是openjdk ...