Android项目实战:手机安全卫士开发案例解析
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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>