zoukankan      html  css  js  c++  java
  • 学习手机安全卫士项目源码记录(一)

      从图书馆借了一本《Android项目实战——手机安全卫士开发案例解析》,想通过学习源代码来加深对Android重点知识的理解,以及进一步复习领悟JAVA SE。接下来的两个月,一边学习一遍记录重要的知识点,希望自己能有所收获、有所提高。

      Splash界面的作用:

      1.展现产品的LOGO,提升产品的知名度。

      2.初始化的操作(初始化数据库、文件的复制、配置的读取)。

      3.根据系统的时间或日期做出相应的判断来加载不同的Splash界面(例如,QQ的登陆界面),提升用户体验。

      4.连接服务器,检查获取更新信息,提示用户升级。在此项目中用于连接服务器,检查版本是否需要更新下载,以及初始化数据库。

    问题1:PackageManager pm=this.getPackageManager();Android源代码里他是怎么通过getPackageManager()这个方法实例化PackageManager的?

      大概明白点了,在源码里找到了ContextImpl.java,里面就有getPackageManager()方法,此方法源码如下:

     1 @Override
     2 public PackageManager getPackageManager(){
     3     if(mPackageManager !=null){
     4     return mPackageManager;
     5     }
     6     IPackageManager pm=ActivityThread.getPackageManager();
     7     if(pm!=null){
     8     //Doesn't matter if we make more than one instance.
     9     return (mPackageManager=new ApplicationPackageManager(this, pm);
    10     }
    11     return null;
    12 }

      

    去掉标题栏以及设置全屏模式
    //设置为无标题栏
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    //设置为全屏模式
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

     
      Splash界面加载时的具体流程:

      1.为Splash界面做一个淡进(由暗变明)的动画效果,动画播放时间为2秒。

      2.在Splash界面加载时,连接服务器检查软件是否需要更新(后面还需要实现对数据库的复制),其流程如下图:

        

     context和getApplicationContext()的区别

       getApplicationContext()返回应用的上下文,生命周期是整个应用,应用摧毁它才摧毁。

      context返回当前Activity的上下文,Activity执行了onDestory()方法后摧毁它就摧毁,如果组件是属于当前Activity的,就应该使用context。

      getBaseContext()返回由构造函数指定或setBaseContext()设置的上下文。

      如果我们要通过一个上下文来执行某个动作,且希望该动作一直处于“活跃”状态,那么应当考虑使用getApplicationContext获取上下文。例如:当使用数据库时,需要传递一个上下文,如果传递的是Activity.this,那么,当Aactivity执行onDestory()方法时,数据库就会被关闭,应用程序就会出现错误。

      Dialog窗体是Activity的一部分,一般当关乎到生命周期时,我们才会仔细分析使用哪个上下文,一般情况下使用Activity.this。

     创建并显示对话框

     1 private void showUpdateDialog(){
     2 // 创建对话框的构造器
     3         AlertDialog.Builder builder=new Builder(this);
     4         //设置对话框提示标题左边的提示标语
     5         builder.setIcon(getResources().getDrawable(R.drawable.xxx));
     6         //设置对话框的标题
     7         builder.setTitle("升级提示");
     8         //设置对话框的提示内容
     9         builder.setMessage("");
    10         //设置升级按钮
    11         builder.setPositiveButton("升级", new OnClickListener(){
    12 
    13             @Override
    14             public void onClick(DialogInterface dialog, int which) {
    15                 
    16             }
    17         });
    18         builder.setNegativeButton("取消", new OnClickListener(){
    19             @Override
    20             public void onClick(DialogInterface dialog, int which) {
    21             }});
    22         builder.create().show();
    23     }

      private Handler handler=new Handler(){

      public void handleMessage(Message msg){

       switch(msg.what){ 

          case  XXX:

          .......

          break; 

          ......  

       }}}

     该对象是为了接收子类线程发送过来的消息(子线程与主线程进行通信),将子线程发送过来的消息在handleMessage(Message msg)方法中进行处理。

    为什么要将URL放在res/values目录下的config.xml中?

      String serverurl=getResources().getString(R.string.serverurl):得到访问服务端配置信息(即服务端的info.xml文件)的URL地址。

      该URL地址存在values文件夹下的config.xml文件中,目的在于,当这个URL需要变更时,不需要在代码中进行修改,只需要修改这个配置文件即可实现,降低了开发成本。配置信息如下:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="serverurl">http://192.168.0.4:8080/info.xml<string>
    <resources>

      问题2:在手机上运行调试时出现了如下图错误

        很明显是空指针异常。而且是在SplashActivity.java源代码中的第95行。去代码中仔细查看,原来是缺少一句

    r1_splash=findViewById(R.id.rl_splash);即使前面已经声明了全局变量private View r1_splash;但并没有初始化r1_splash。

    解析服务端的info.xml文件时,需要创建一个解析XML的业务方法。代码如下:

      

     1 /**
     2  * 解析XML数据
     3  *
     4  */
     5 public class UpdateInfoParser {
     6     
     7     public static UpdateInfo getUpdateInfo(InputStream is) throws XmlPullParserException, IOException{
     8         //获得一个XmlPullParser的解析实例
     9         XmlPullParser parser=Xml.newPullParser();
    10         //将要解析的文件流传入
    11         parser.setInput(is,"utf-8");
    12         //创建UpdateInfo实例,用于存放解析得到的XML中的数据,最终将该对象返回
    13         UpdateInfo info=new UpdateInfo();
    14         //获取当前触发的事件类型
    15         int type=parser.getEventType();
    16         //使用while循环,如果获得的事件码是文档结束,那么就结束解析
    17         while(type!=XmlPullParser.END_DOCUMENT){
    18             if(type==XmlPullParser.START_TAG){//开始元素
    19                 if("version".equals(parser.getName())){
    20                 //判断当前元素是否是读者需要检索的元素,下同
    21                 //因为内容页相当于一个节点,所以获取内容时需要调用parser对象的nextText()
    22                 //方法才可以得到内容
    23                     String version=parser.nextText();
    24                     info.setVersion(version);
    25                 }else if("description".equals(parser.getName())){
    26                     String description=parser.nextText();
    27                     info.setDescription(description);
    28                 }else if("apkurl".equals(parser.getName())){
    29                     String apkurl=parser.nextText();
    30                     info.setApkurl(apkurl);
    31                 }
    32             }
    33             type=parser.next();//触发下一个事件,并返回事件的类型
    34         }
    35         return info;
    36     }
    37 }

      为Splash界面播放一个动画

      AlphaAnimation aa=new AlphaAnimation(0.3f,1.0f)设置了一个透明度由0.3f~1.0f的淡入的动画效果。0.0f表示完全透明,1.0f表示正常显示效果。

      aa.setDuration(2000)为设置动画的执行时间,单位为毫秒。

      r1_splash.startAnimation(aa)为启动动画。

      此外,查阅API还发现Animation类还有这几个子类AnimationSet,RotateAnimation,ScaleAnimation,TranslateAnimation。

    new Thread(new CheckVerisonTask()){}.start()

      由于联网的过程是一般是一个耗时的操作,为了避免出现ANR异常,我们在主线程中开启一个子线程用于联网核对版本号信息。此时我们还应该在清单文件中配置网络权限的信息: 

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

    Handler

      handler.sendMessage(msg):通过handler对象向主线程中发送消息,然后在Handler的handleMessage(Message msg)方法中可以处理该消息。

      msg.what=GET_INFO_SUCCESS:为msg做一个标记,这样在Handler的handleMessage(Message msg)的Switch中可以获取该标记,以便识别是哪个消息。

    下载APK的工具类:

     1 /**
     2  * 下载的工具类:下载文件的路径;下载文件后保存的路径;关心进度条;上、下文
     3  *
     4  */
     5 public class DownLoadUtil {
     6     /**
     7 
     8      * 下载一个文件
     9      * @param urlpath
    10      * 路径
    11      * @param filepath
    12      * 保存到本地的文件路径
    13      * @param pd
    14      * 进度条对话框
    15      * @return
    16      * 下载后的apk
    17      */
    18     public static File getFile(String urlpath,String filepath,ProgressDialog pd){
    19     try {
    20         URL url=new URL(urlpath);
    21         File file=new File(filepath);
    22             FileOutputStream fos=new FileOutputStream(file);
    23             HttpURLConnection conn=(HttpURLConnection) url.openConnection();
    24             //下载的请求是GET方式,conn的默认方式也是GET请求
    25             conn.setRequestMethod("GET");
    26             //服务端的响应时间
    27             conn.setConnectTimeout(5000);
    28             //获取服务端的文件总长度
    29             int max=conn.getContentLength();
    30             //将进度条的最大值设置为要下载的文件的总长度
    31             pd.setMax(max);
    32             //获取要下载的apk的文件输入流
    33             InputStream is=conn.getInputStream();
    34             //设置一个缓存区
    35             byte[] buf=new byte[1024];
    36             int len=0;
    37             int process=0;
    38             while((len=is.read(buf))!=-1){
    39                 fos.write(buf, 0, len);
    40                 //没读取一次输入流,就刷新一次下载进度
    41                 process+=len;
    42                 pd.setProgress(process);
    43                 //设置睡眠时间,便于观察下载进度
    44                 Thread.sleep(30);
    45             }
    46             //刷新缓存数据到文件中
    47             fos.flush();
    48             //关流
    49             fos.close();
    50             is.close();
    51             return file;
    52         } catch (Exception e) {
    53             // TODO Auto-generated catch block
    54             e.printStackTrace();
    55             return null;
    56         }
    57     }

    获取一个路径中的文件名的代码:

     1         /**
     2          * 获取一个路径中的文件名。例如:mymobilesafe.apk
     3          * @param urlpath
     4          * @return
     5          */
     6         public static String getFileName(String urlpath){
     7             return urlpath.substring(urlpath.lastIndexOf("/")+1, urlpath.length());
     8             
     9         
    10     }

    涉及对Sdcard的操作,需要在清单文件中配置相应的权限:

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

    安装APK的方法:

     1 /**
     2      * 安装一个apk文件
     3      * @param file 要安装的完整的文件名
     4      */
     5     protected void installApk(File file){
     6         //隐式意图
     7         Intent intent=new Intent();
     8         intent.setAction("android.intent.action.VIEW");//设置意图的动作
     9         intent.addCategory("android.intent.category.DEFAULT");
    10         //为意图添加额外的数据
    11 intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");//设置意图的类型和数据
    12         startActivity(intent);//激活该意图

    LayoutInflater:

      布局填充器,通过getSystemService(Context.LAYOUT_INFLATER_SERVICE)方法可以实例化一个LayoutInflater,也可以通过LayoutInflater inflater=getLayoutInflater();来获取。然后使用inflate()方法在填充xml布局文件。

      还可以使用LayoutInflater.from(this)来实例化。

      最简单的方式是 View view=View.inflate(this, R.layout.xxx, null);

      LayoutInflater的作用类似于findViewById(),不同的是LayoutInflater填充的是layout文件夹下的xml布局文件进行实例化,而findViewById()是某个xml布局文件中的某个widget控件。setContentView()一旦调用, layout就会立刻显示UI;而inflate只会把Layout形成一个以View类实现成的对象。

       一般在activity中通过setContentView()将界面显示出来,然后才能使用findViewById()是找xml布局文件下的具体widget控件(如Button、TextView等)

      但是如果还需要对其他布局进行操作,这就需要LayoutInflater动态加载, LayoutInflater是用来找res/layout/下的xml布局文件,并且实例化。

    gravity和layout_gravity的区别

      LinearLayout有两个非常相似的属性:gravity和layout_gravity。

    区别在于:android:gravity 是对该view中内容的限定。比如一个button里面的text,可以通过gravity设置text相对于button靠左,靠右,居中。

         android:layout_gravity是用来设置该view相对于父view的位置。比如一个button在LinearLayout里,通过layout_gravity就可以设置button相对于LinearLayout是靠左,靠右还是居中。

      此项目中,如果在main_item.xml中缺少android:gravity="center_horizontal",图标底下的文字就无法相对于图标居中显示。

    baseAdapter

      baseAdapter主要作用是给Spiner、ListView、GirdView来填充数据的。这个适配器取得了Adapter的最大控制权:程序要创建多少个列表项,每个列表项的组件都由开发者来决定。继承了baseAdapter接口后需要覆写如下四个方法。

      getCount():该方法返回值控制该Adapter将会包含多少个列表项。

      getItem(int position):返回第position处的列表项的对象,如果不对这个返回的对象做相应的操作,可以返回一个null。

      getItemId(int position):返回第position处的列表项的ID。

      getView(int position,View convertView,ViewGroup parent):该方法的返回值决定第position出的列表的组件。

     GridView在绘制的时候,系统首先调用getCount()函数,根据它的返回值得到GridView的长度,然后根据这个长度,调用getView逐一绘制每一个(行)。如果getCount()返回值是0,列表将不显示,若return 1 ,则只显示一行。系统显示列表时,首先实例化一个适配器,在适配器中getView方法中完成数据的映射。系统在绘制GridView中的每一个view时,都会调用getView()方法,getView()有三个参数,position表示显示的是第几个,covertView是从布局文件中通过inflate方法填充的布局,即将item.xml文件变为View实例用来显示,然后将xml文件中各个组件有findViewById()实例化。

    适配器对象MainAdapter的代码如下:

     1 public class MainAdapter extends BaseAdapter {
     2     //将tv_name和iv_icon定义为静态的,有利于提高程序运行的效率,
     3     //因为getView()方法会被调用好多次,定义为静态后(在内存中只有一份)就避免了
     4     //多次在栈内存中创建变量tv_name和iv_icon的引用
     5     private static  TextView tv_name;
     6     private static ImageView iv_icon;
     7     //布局填充器
     8     private LayoutInflater inflater;
     9     //接收MainActivity传递过来的上下文对象
    10     private Context context;
    11     //将9个item的每一个图片对应的id都存入该数组中
    12     private  int[] icons={
    13         R.drawable.widget01,R.drawable.widget02,R.drawable.widget03,
    14         R.drawable.widget04,R.drawable.widget05,R.drawable.widget06,
    15         R.drawable.widget07,R.drawable.widget08,R.drawable.widget09
    16     };
    17     //将9个item的每一个标题都存入该数组中
    18     private String[] names={
    19             "手机防盗","通信卫士","软件管理","进程管理","流量统计","手机杀毒","系统优化","高级工具","设置中心"
    20     };
    21     public MainAdapter(Context context){
    22         this.context=context;
    23         //获取系统中的布局填充器
    24         inflater=(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    25     }
    26     /**
    27      * 返回gridview有多少个item
    28      */
    29     @Override
    30     public int getCount() {
    31         // TODO Auto-generated method stub
    32         return names.length;
    33     }
    34     /**
    35      * 获取每个item对象,如果不对这个返回的item对象做相应的操作
    36      * 可以返回一个null,这里我们简单处理一下,返回position
    37      */
    38     @Override
    39     public Object getItem(int position) {
    40         // TODO Auto-generated method stub
    41         return position;
    42     }
    43     /**
    44      * 返回当前item的id
    45      */
    46     @Override
    47     public long getItemId(int position) {
    48         // TODO Auto-generated method stub
    49         return position;
    50     }
    51     /**
    52      * 返回每一个gridview条目中的view对象
    53      */
    54     @Override
    55     public View getView(int position, View convertView, ViewGroup parent) {
    56         // TODO Auto-generated method stub
    57         View view=inflater.inflate(R.layout.main_item, null);
    58          tv_name=(TextView) view.findViewById(R.id.tv_main_item_name);
    59          iv_icon=(ImageView) view.findViewById(R.id.iv_main_item_icon);
    60         tv_name.setText(names[position]);
    61         iv_icon.setImageResource(icons[position]);
    62         return view;
    63     }
    64 }

      

    SharedPreferences

      提供了一种轻量级的数据存储方式,主要应用在数据量比较少的情况下。它以“key-value”方式将数据保存在一个xml文件中。

      使用SharedPreferences存储数据比较简单,步骤如下:

      1)使用getSharedPreferences()生成SharedPreferences对象。调用getSharedPreferences()方法时,需要指定如下两个参数:

        一是存储数据的xml文件名,这个xml文件存储在"/data/data/包名/shared_prefs/"目录下,其文件名由该参数指定,注意,文件名不需要指定后缀(.xml),系统会在该文件名之后自动添加xml后缀并创建之。

        二是操作模式,其取值有三种:MODE_WORLD_READABLE(可读),MODE_WORLD_WRITEABLE(可写)和MODE_PRIVATE(私有)。

      2)使用SharedPreferences.Editor的putXXX()方法保存数据。

      3)使用SharedPreferences.Editor的commit()方法将上一步保存的数据写到xml文件中。

      4)使用SharedPreference的getXXX()方法获取相应的数据。

    源代码中:

    1  sp=getSharedPreferences("config",MODE_PRIVATE);
    2  boolean autoupdate=sp.getBoolean("autoupdate",true);

      第一行,获取config.xml文件,如果该文件不存在,将会自动创建该文件,文件的操作类型为私有

      第二行,从sp对应的config.xml文件中获取autoupdate所对应的boolean值,如果没有查找到该键,

          将返回默认的boolean值true(即第二个参数)。

    Checkbox的勾选状态发生改变时,用setOnCheckedChangeListener(new OnCheckedChangeListener(){});进行监听。代码如下:

     1 /**
     2          * 当Checkbox的状态发生改变时onCheckedChanged()方法被回调
     3          */
     4         cb_setting_autoupdate.setOnCheckedChangeListener(new OnCheckedChangeListener() {
     5             //第一个参数:当前的Checkbox;第二个参数:当前的Checkbox是否处于勾选状态
     6             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
     7                 // TODO Auto-generated method stub
     8                 //获取编辑器
     9                 Editor editor=sp.edit();
    10                 //持久化存储当前Checkbox的状态,当下次进入时,已然可以保存当前设置的状态
    11                 editor.putBoolean("autoupdate", isChecked);
    12                 //将数据真正提交到sp里面
    13                 editor.commit();
    14                 if(isChecked){//Checkbox处于选中效果
    15                 //当Checkbox处于勾选状态时,表示自动更新已经开启,同时修改字体颜色
    16                     tv_setting_autoupdate_status.setTextColor(Color.GREEN);
    17                     tv_setting_autoupdate_status.setText("自动更新已经开启");
    18                 }else{//Checkbox处于未勾选状态
    19                     tv_setting_autoupdate_status.setTextColor(Color.RED);
    20                     tv_setting_autoupdate_status.setText("自动更新已经关闭");
    21                     
    22                 }
    23             }
    24         });

    为GridView对象中的item设置单击时的事件要使用setOnClickListener(new OnItemClickListener(){});

    代码如下:

     1 //为gv_main对象设置一个适配器,该适配器的作用是为每个item填充对应的数据
     2         gv_main.setAdapter(new MainAdapter(this));
     3         //为GridView对象中的item设置单击时的监听事件
     4         gv_main.setOnItemClickListener(new OnItemClickListener(){
     5             //参数一:item的父控件,也就是GridView
     6             //参数二:当前单击的item
     7             //参数三:当前单击的item在GridView中的位置
     8             //参数四:id的值为单击GridView的哪一项对应的数值,单击GridView第九页,那么id就等于8
     9             @Override
    10             public void onItemClick(AdapterView<?> parent, View view,
    11                     int position, long id) {
    12                 // TODO Auto-generated method stub
    13                 switch(position){
    14                 case 8://设置中心
    15                     //跳转到”设置中心“对应的Activity界面
    16                     Intent settingIntent=new Intent(MainActivity.this,SettingCenterActivity.class);
    17                     startActivity(settingIntent);
    18                     break;
    19                 }
    20             }
    21         });

    android.view.View.OnClickListener与content.DialogInterface.OnClickListener()冲突

      View.OnClickListener:  Interface definition for a callback to be invoked when a view is clicked.

      DialogInterface.OnClickListener:  Interface used to allow the creator of a dialog to run some code when an item on the     dialog is clicked..

      在同一个activity中需要用到这个两个监听事件,若同时导入会有冲突。 解决办法是调用时都带上全路径名。例如

    new android.content.DialogInterface.OnClickListener();

    又一次遇到空指针异常

      View view=View.inflate(this, R.layout.first_entry_dialog, null);
            //查找view对象中的各个控件
            et_first_dialog_pwd=(EditText)view.findViewById(R.id.et_first_dialog_pwd);在这行代码中没有写view,而Activity类也有findViewById方法,如果不加view,则是默认调用this.findViewById,而Activity窗体布局中并没有et_first_dialog_pwd,所以导致了空指针异常。

    MD5加密工具类代码(Md5Encoder.java)

     1 public class Md5Encoder {
     2     public static String encode(String password){
     3         try{
     4             //获取到数字消息的摘要器
     5             MessageDigest digest=MessageDigest.getInstance("MD5");
     6             //执行加密操作
     7             byte[] result=digest.digest(password.getBytes());
     8             StringBuilder sb=new StringBuilder();
     9             //将每个byte字节的数据转换成十六进制的数据
    10             for(int i=0;i<result.length;i++){
    11                 int number=result[i]&0xff;////向int[]赋值,&0xff的作用是消除对int前24位的影响
    12 //(计算机中使用补码存储数据,如果直接将一个第一位为“1”的byte值赋给int,则前24为将为“1”
    13                 String str=Integer.toHexString(number);//将十进制的number转换成十六进制的数据
    14                 if(str.length()==1){//判断加密后的字符长度,如果长度为1,则在该字符前面补0
    15                     sb.append("0");
    16                     sb.append(str);
    17                 }else{
    18                     sb.append(str);
    19                 }
    20             }
    21             return sb.toString();//将加密后的字符转成字符串返回
    22         }catch(NoSuchAlgorithmException e){//加密器没有被找到,该异常不可能发生,因为填入的“MD5”是正确的
    23             e.printStackTrace();
    24             return "";
    25         }
    26     }
    27 }

     我发现:书中52页说“然后在EditText文本框中输入”mp3“后,单击”确定“按钮,当再次进入程序主界面时,就可以看到修改后的标题生效了,”

       再次进入程序主界面,需要退出应用后再次打开应用才能看到修改的标题,说明修改的动作发生在onCreate()方法里。但从LostProtectedActivity窗体返回到MainActivity窗体时,并不能看到更新。需要改进一下:还记得Activity从暂停态到运行态所触发的事件是onResume()方法,即当MainActivity重新获得焦点,开始与用户交互时回调此方法。所以我可以在MainActivity中覆写onResume()方法,即可完成更新UI,代码如下:

     1     /**
     2      * 当MainActivity重新获得焦点(即Activity开始与用户交互)时回调此方法
     3      */
     4 @Override
     5     protected void onResume() {
     6             super.onResume();
     7             sp=this.getSharedPreferences("config",MODE_PRIVATE);
     8     if((sp.getString("newname", "")!=null)){
     9            //重新加载GridView的适配器
    10         gv_main.setAdapter(new MainAdapter(MainActivity.this));    

      本来考虑到使用Handler更新UI,费了好大劲还是没有成功。。才发现自己对Handler机制还是糊涂。先在这里重新温习一下Handler的基本知识吧。

      Handler:

      它的作用有两个——发送消息和处理消息,程序使用Handler发送消息,被Handler发送的消息必须被送到指定的MessageQueue。也就是说,如果Handler正常工作,必须在当前线程中有一个MessageQueue,否则消息就没有MessageQueue进行保存了。不过MessageQueue是由Looper负责管理的,也就是说,如果希望Handler正常工作,就必须在当前线程中有一个Looper对象。为了保证当前线程中有Looper对象,可以分如下两种情况处理。

      1.主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可以通过Handler来发送消息、处理消息。

      2.子线程中,必须由我们创建一个Looper对象,并启动它。创建Looper对象调用它的prepare()方法即可。

     prepare()方法保证每个线程最多只有一个Looper对象。然后调用Looper的静态loop()方法来启动它。loop()方法使用一个死循环不断取出MessageQueue中的消息,并将取出的消息分给该消息对应的Handler进行处理。

      在子线程中使用Handler的步骤如下:

      1.调用Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建与之配套的MessageQueue。

      2.有了Looper之后,创建Handler子类的实例,覆写handlerMessage()方法,该方法负责处理来自于其他线程的消息。

      3.调用Looper的loop()方法启动Looper。

  • 相关阅读:
    方便操作的命名范围scope
    使用Emmet加速Web前端开发
    Beanstalkd一个高性能分布式内存队列系统
    2000年互联网泡沫
    简单有效的kmp算法
    文本比较算法:计算文本的相似度
    字符串的四则运算
    文本比较算法:Needleman/Wunsch算法
    两则面试题(动态规划)
    文本比较算法:编辑距离
  • 原文地址:https://www.cnblogs.com/dazuihou/p/3588780.html
Copyright © 2011-2022 走看看