1.1.6 替换安装下载后的apk
当新版本的apk下载到手机的Sdcard目录下时,此时应该立即安装这个新版本的apk。通过查看Android的源代码dir\JB\packages\apps\PackageInstaller目录下的AndroidManifest.xml清单文件可以看到这样一个意图过滤器:
<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="content" /> <data android:scheme="file" /> <data android:mimeType="application/vnd.android.package-archive" /> </intent-filter>
所以,根据此意图过滤器,我们可以写出对应的方法来激活这个意图:
/** * 安装一个apk文件 * * @param file 要安装的完整文件名 */ protected void installApk(File file) { //隐式意图
Intent intent = new Intent(); intent.setAction("android.intent.action.VIEW");//设置意图的动作 intent.addCategory("android.intent.category.DEFAULT"); //为意图添加额外的数据 //intent.setType("application/vnd.android.package-archive"); //intent.setData(Uri.fromFile(file)); intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");//设置意图的数据与类型 startActivity(intent);//激活该意图 }
说明:这是一个隐式意图。隐式意图的激活方式:系统首先查询一个系统注册表(位于手机的 data\system\packages.xml 文件中),当查找到与之对应的数据后才将对应的组件激活,这个过程是先查询后激活,效率相对于显式意图要低一些。如果组件在不同的应用程序里面,则不能通过显式意图来激活,这时我们需要借助隐式意图。显式意图的应用场景:在当前应用程序里去激活自己的组件,直接通过指定组件名即可激活,效率较高。
此时,SplashActivity的完整的业务代码见在线资源包中代码文本文件1.1.6.doc。
相关代码解析:
(1)
case DOWNLOAD_SUCCESS: Log.i(TAG, "文件下载成功"); //得到发送过来的消息中的文件对象 File file = (File) msg.obj; //将文件对象传入,执行安装方法来安装该文件 installApk(file); break;
(2)
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");//设置意图的数据与类型
调用intent对象的setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive")方法可以同时设置意图的数据与类型,这里之所以没有使用“intent.setType("application/vnd. android.package-archive")”与“intent.setData(Uri.fromFile(file))”,是因为:当执行intent. setType ("application/vnd.android. package-archive")代码时,会将前面的intent.setData(Uri.fromFile(file))中设置的数据移除,当执行 intent.setData(Uri.fromFile(file))时,会将前面的 intent.setType ("application/vnd.android. package-archive")中设置的类型移除,反之同理。而调用intent对象的setDataAndType(Uri. fromFile(file), "application/vnd.android.package-archive")方法时,则可以同时设置这两者,不会出现移除现象,从而激活系统的安装意图。
当成功下载到 apk 后,程序执行到 installApk(file)方法时,会激活系统的安装意图,立即进入apk的安装界面来安装传入的文件,如图1-11所示。单击“安装”按钮后,显示出正在安装的界面,如图1-12所示。如果apk安装成功,那么当再次进入Splash界面时,版本号应该是2.0,如图1-13所示。Log中重要的日志信息如图1-14所示。
图1-11
图1-12
图1-13
图1-14
(3)AlertDialog.Builder builder = new Builder(this)。我们向构造器中传入的是 this,即SplashActivity.this,传入的是当前 Activity 的上、下文对象。还有一种上、下文对象是getApplicationContext(),如果传入的是 getApplicationContext(),运行程序时,会出现错误“android.view.WindowManager$BadTokenException:Unable to add window -- token null is not for an application”,这句话的意思是:不能够添加窗体,该应用的令牌是null。此时通过一个实验来揭开迷雾。
新建工程“AlertDialogTest”,在Activity中实现对有“弹出对话框”字样的按钮的监听,一旦单击该按钮,将会在当前的Activity中弹出一个对话框。具体业务代码如下:
package com.example.alertdialogtest; import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.os.Bundle; import android.view.View; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /**
* 当Activity失去焦点时调用 */ @Override protected void onPause() { System.out.println("pause"); super.onPause(); } /** * 当单击按钮时执行该方法,因为我们在对应的XML文件中的Button中设置了属性: android:onClick="onClick" * @param view */ public void onClick(View view ){ //获取对话框的构造器 AlertDialog.Builder builder = new Builder(this); builder.setMessage("我是对话框"); builder.setPositiveButton("确定", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //TODO Auto-generated method stub } }); //创建并显示对话框 builder.create().show(); } } 其对应的activity_main布局文件为: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="弹出对话框" /> </LinearLayout>
当运行工程时,会出现一个带有“弹出对话框”按钮的界面,如图1-15所示。当单击该按钮时,会弹出一个对话框,如图1-16所示。根据Activity的生命周期我们知道,当一个Activity失去焦点时,必然会执行Activity生命周期方法中的onPause()方法,而此时,Log日志中并没有打印出所预料的“pause”信息。所以,onPause()方法肯定没有被执行。通过以上分析我们可以得出这样一个结论:Dialog窗体是Activity的一部分。然而,通过getApplicationContext()方法获取的上、下文是属于整个应用程序的,而Activity.this则是获得的当前Activity的上、下文。如果是通过getApplicationContext获取上、下文,那么,系统就不知道当前的这个窗体要挂载在哪个Activity上(因为getApplicationContext是整个应用程序所共有的)。
图1-15
图1-16
下面再介绍一下两者的区别。
① 对于getApplicationContext,我们可以假定它是一个父类(它属于整个应用所共有), Activity.this可以假定为getApplicationContext的一个子类(当前Activity的上、下文),该子类中包含了一些特殊的引用(相对于父类来说,功能更加完善)。所以,一般可以用getApplicationContext的地方,就可以用Activity.this来替代。
② 生命周期上:通过getApplicationContext获取的上、下文对象,只要当前应用程序的进程还存在,那么该对象就一直存在;对于Activity.this上、下文来说,只要当前的Activity执行了onDestroy()方法,这个上、下文对象也就跟着被系统回收。
③ 应用场景上:如果我们要通过一个上、下文来执行某个动作,且希望该动作一直处于“活跃”状态,那么应当考虑使用getApplicationContext获取的上、下文对象。例如,当使用数据库时,需要传递一个上、下文,如果传递的是Activity.this,那么,当Activity执行onDestroy()方法时,数据库就会被关闭,应用程序会出现错误。但如果使用getApplicationContext()方法来获取上、下文对象,然后将其传递进去,那么就可以避免上面的错误。
总结:Dialog窗体是Activity的一部分;一般当关乎到生命周期时,我们才会仔细分析使用哪个上、下文,一般情况下使用的是Activity.this。
④ loadMainUI()方法用于跳转至另外一个界面MainActivity(需要在清单文件中为其配置一下对应的信息),MainActivity对应的业务代码如下:
package com.guoshisp.mobilesafe; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity { @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } } 其对应的main.xml布局文件如下: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="我的手机卫士" android:textColor="#66ff00" android:textSize="28sp" /> </LinearLayout>