Android开发实战《手机安全卫士》——9.“软件管理”模块实现 & 删除通话记录 & 备份短信 & 计算手机可用空间 & ListView双条目适配

文章目录

  • 1.通信卫士——删除通话记录
  • 2.高级工具——短信备份
  • 3.拓展功能——短信备份中的回调应用场景分析
  • 4.软件管理——磁盘可用空间计算
  • 5.软件管理——获取应用相关信息集合
  • 6.软件管理——指定两种条目类型数据适配器指定 & 集合数据划分
  • 7.软件管理——常驻悬浮框效果

1.通信卫士——删除通话记录

之前我们实现了拦截短信和拦截电话,现在需要完成删除通话记录的功能。通话记录的数据库一般放置在data/data/com.android.providers.contacts/contacts2.db,打开该数据库中的calls表就是记录通话记录的。

该功能需要按照以下步骤进行实现:

  1. 首先找到通话记录所在的数据库对应的表,我们前面分析过了;
  2. 由于该数据库是系统级数据库,一般不允许直接访问,需要使用内容提供者对数据库进行解析,从而获取其中的数据,其uri为:content://call_log/calls
  3. calls表中有一个名为number的字段,可以作为删除的依据。

修改BlackNumberService,修改endCall()方法,添加内容解析器的相关逻辑,并且完善删除功能,代码如下:

    /**
     * 挂断电话的方法
     * @param phoneNumber 要挂断的电话
     */
    private void endCall(String phoneNumber) {
        int mode = mDao.queryModeByPhone(phoneNumber);
        if (mode == 2 || mode == 3){
            // 拦截电话,由于ServiceManager此类Android对开发者隐藏,所以不能直接调用其方法,需要反射调用
            try {
                // 1.获取ServiceManger字节码文件
                Class<?> clazz = Class.forName("android.os.ServiceManager");
                // 2.获取反射方法
                Method method = clazz.getMethod("getService", String.class);
                // 3.反射调用此方法
                IBinder iBinder = (IBinder) method.invoke(null,Context.TELEPHONY_SERVICE);
                // 4.调用获取aidl文件对象方法
                // ITelePhoney iTelePhoney = ITelePhoney.stub.asInterface(iBinder);
                // 5.调用在aidl中隐藏的endcall方法
                // iTelePhoney.endCall();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            // 6.删除此被拦截的电话号码的通信记录(要加读写日志权限)
            getContentResolver().delete(Uri.parse("content://call_log/calls"),"number = ?",new String[]{phoneNumber});
        }
    }

因为这个操作涉及到系统日志的读写,所以要在清单文件中声明权限,代码如下:

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

注意,这次的删除功能,只能删除过去的通话记录,而不能删除刚刚通话时的通话记录。原因是:数据库中本次通话记录的电话号码还没有插入,就进行了删除操作。

为了解决这个问题,需要引入内容观察者的概念:即观察数据库的变化,一旦数据发生改变,就调用相应方法。

通过内容观察者,观察数据库的插入,一旦有插入,则做删除此条插入数据的操作,修改过后的endCall()方法以及自定义的ContentObserver内部类的代码如下:

/**
     * 挂断电话的方法
     * @param phoneNumber 要挂断的电话
     */
    private void endCall(String phoneNumber) {
        int mode = mDao.queryModeByPhone(phoneNumber);
        if (mode == 2 || mode == 3){
            // 拦截电话,由于ServiceManager此类Android对开发者隐藏,所以不能直接调用其方法,需要反射调用
            try {
                // 1.获取ServiceManger字节码文件
                Class<?> clazz = Class.forName("android.os.ServiceManager");
                // 2.获取反射方法
                Method method = clazz.getMethod("getService", String.class);
                // 3.反射调用此方法
                IBinder iBinder = (IBinder) method.invoke(null,Context.TELEPHONY_SERVICE);
                // 4.调用获取aidl文件对象方法
                // ITelePhoney iTelePhoney = ITelePhoney.stub.asInterface(iBinder);
                // 5.调用在aidl中隐藏的endcall方法
                // iTelePhoney.endCall();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            // 6.删除此被拦截的电话号码的通信记录(要加读写日志权限)(方式一)
            // getContentResolver().delete(Uri.parse("content://call_log/calls"),"number = ?",new String[]{phoneNumber});
            // 6.通过内容解析器去注册内容观察者,然后观察数据库的变化(方式二)
            mContentObserver = new MyContentObserver(new Handler(), phoneNumber);
            getContentResolver().registerContentObserver(Uri.parse("content://call_log/calls"),true,mContentObserver);
        }
    }

    /**
     * 自定义的ContentObserver类
     */
    private class MyContentObserver extends ContentObserver {

        private String phoneNumber;

        public MyContentObserver(Handler handler,String phoneNumber) {
            super(handler);
            this.phoneNumber = phoneNumber;
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            // 数据库中指定表在发生改变时会去调用的方法
            getContentResolver().delete(Uri.parse("content://call_log/calls"),"number = ?",new String[]{phoneNumber});
        }
    }

最后,记得在onDestory()方法中注销这个自定义的内容观察者,顺便注销掉电话状态的监听,代码如下:

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 注销广播
        if (mInnerSmsReceiver != null){
            unregisterReceiver(mInnerSmsReceiver);
        }
        // 注销内容管擦着
        if (mContentObserver != null){
            getContentResolver().unregisterContentObserver(mContentObserver);
        }
        // 取消对电话状态的监听
        if (mPhoneStateListener != null){
            mSystemService.listen(mPhoneStateListener,PhoneStateListener.LISTEN_NONE);
        }
    }

2.高级工具——短信备份

前面我们完成了“通信卫士”模块中的黑名单号码管理功能,,现在需要完成另一个模块——“高级工具”中的第二个功能,即短信备份,如图中的红框所示:

Android开发实战《手机安全卫士》——9.“软件管理”模块实现 & 删除通话记录 & 备份短信 & 计算手机可用空间 & ListView双条目适配_第1张图片

点击该按钮后会弹出一个带有进度条的对话框,表明正在将短信进行备份。在系统中存放短信的数据库所对应的表为:data/data/com.android.providers.telephony/databases/mmssms.db/sms,需要获取其中的四个字段:

  • address:电话号码
  • date:接收/发送短信的时间
  • type:接收/发送,短信类型
  • body:短信内容

跟通话记录一样,访问短信同样需要使用内容观察者,URL内容为:content://sms/

首先修改activity_a.tool.xml,添加一个这样的条目,代码如下:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.AToolActivity"
    android:orientation="vertical">

    <TextView
        style="@style/TitleStyle"
        android:text="高级工具"/>

    <TextView
        android:id="@+id/tv_query_phone_address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="归属地查询"
        android:padding="5dp"
        android:background="@drawable/selector_atool_item_bg"
        android:gravity="center"
        android:drawableLeft="@android:drawable/btn_star"/>

    <TextView
        android:id="@+id/tv_sms_backup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="短信备份"
        android:padding="5dp"
        android:background="@drawable/selector_atool_item_bg"
        android:gravity="center"
        android:drawableLeft="@android:drawable/btn_star"/>

LinearLayout>

在dao下新建SmsBackUpDao,作为将短信的查询过程封装起来的工具类,并将信息解析到xml文件中,代码如下:

package com.example.mobilesafe.dao;

import android.app.ProgressDialog;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Xml;

import org.xmlpull.v1.XmlSerializer;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class SmsBackUpDao {

    // 进度条的进度值
    private static int index = 0;

    /**
     * 短信备份方法
     * @param context 上下文环境
     * @param path 备份文件夹路径
     * @param progressDialog 进度条所在的对话框对象
     */
    public static void backUp(Context context, String path, ProgressDialog progressDialog) {

        FileOutputStream fos = null;

        Cursor cursor = null;

        try {
            // 1.获取短信备份写入的文件
            File file = new File(path);
            // 2.获取内容解析器,对数据库进行查询
            cursor = context.getContentResolver().query(Uri.parse("content://sms/"), new String[]{"address", "date", "type", "body"}, null, null, null);
            // 3.文件相应的输出流
            fos = new FileOutputStream(file);
            /*
             * 4.序列化数据库中读取的数据,放置到xml中,xml应维护成如下格式:
             * 
             * 		
             * 			
* * * *
* * * * * * *
*/
XmlSerializer xmlSerializer = Xml.newSerializer(); // 5.给xml做相应的设置 xmlSerializer.setOutput(fos,"utf-8"); xmlSerializer.startDocument("utf-8",true); // DTD(xml规范) xmlSerializer.startTag(null,"smss"); // 命名空间起始 // 6.备份短信总数的指定给进度条,设置进度条的进度值 progressDialog.setMax(cursor.getCount()); // 7.读取数据库中的每一行的数据,写入到xml中 while (cursor.moveToNext()){ xmlSerializer.startTag(null,"sms"); xmlSerializer.startTag(null,"address"); xmlSerializer.text(cursor.getString(0)); xmlSerializer.endTag(null,"address"); xmlSerializer.startTag(null,"date"); xmlSerializer.text(cursor.getString(1)); xmlSerializer.endTag(null,"date"); xmlSerializer.startTag(null,"type"); xmlSerializer.text(cursor.getString(2)); xmlSerializer.endTag(null,"type"); xmlSerializer.startTag(null,"body"); xmlSerializer.text(cursor.getString(3)); xmlSerializer.endTag(null,"body"); xmlSerializer.endTag(null,"sms"); // 8.每进行一次循环,就要让进度条变化 index++; // 9.ProgressDialog可以在子线程中更新相应的进度条的转变 progressDialog.setProgress(index); } xmlSerializer.endTag(null,"smss"); xmlSerializer.endDocument(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (cursor != null && fos != null) { cursor.close(); fos.close(); } } catch (IOException e) { e.printStackTrace(); } } } }

修改AToolActivity,添加initSmsBackUp(),作为初始化短信加载的方法,代码如下:

package com.example.mobilesafe.activity;

import androidx.appcompat.app.AppCompatActivity;

import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.TextView;

import com.example.mobilesafe.R;
import com.example.mobilesafe.dao.SmsBackUpDao;

import java.io.File;

public class AToolActivity extends AppCompatActivity {

    private TextView tv_query_phone_address;

    private TextView tv_sms_backup;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a_tool);

        // 1.电话归属地查询方法
        initPhoneAddress();

        // 2.短信备份方法
        initSmsBackUp();
    }

    /**
     * 1.电话归属地查询方法
     */
    private void initPhoneAddress() {
        tv_query_phone_address = findViewById(R.id.tv_query_phone_address);
        tv_query_phone_address.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(getApplicationContext(),QueryAddressActivity.class));
            }
        });
    }

    /**
     * 2.短信备份方法
     */
    private void initSmsBackUp() {
        tv_sms_backup = findViewById(R.id.tv_sms_backup);
        tv_sms_backup.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               showSmsBackUpDialog();
            }
        });
    }

    /**
     * 展示备份短信的进度条的对话框
     */
    private void showSmsBackUpDialog() {
        // 1.创建一个带进度条的对话框
        final ProgressDialog progressDialog = new ProgressDialog(this);
        progressDialog.setIcon(R.drawable.ic_launcher);
        progressDialog.setTitle("短信备份");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); // 指定进度条的样式为水平
        progressDialog.show(); // 展示进度条
        // 2.获取短信
        new Thread(){
            @Override
            public void run() {
                String path = getApplicationContext().getExternalFilesDir(null) + File.separator + "smsbackup.xml"; // 将短信信息存储到SD卡中
                SmsBackUpDao.backUp(getApplicationContext(),path,progressDialog);
                progressDialog.dismiss();
            }
        }.start();
    }
}

注意,因为涉及到读取短信的操作,需要到清单文件中声明相应权限,代码如下:

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

3.拓展功能——短信备份中的回调应用场景分析

回调方法,是Android中常见的基于观察者模式的一个编程思想,体现了代码的解耦多态性,回调需要实现以下步骤:

  1. 定义一个接口;
  2. 定义接口中未实现的业务逻辑方法(短信总数设置,备份过程中短信百分比更新),即多个继承了同一个父类的子类中的特有方法
  3. 在方法参数中传递一个实现了此接口的对象(至备份短信的工具类中),该对象实现了接口中未实现的业务逻辑方法;
  4. 获取传递进来的对象,在合适的地方(短信总数设置,备份过程中短信百分比更新)做方法的调用;

假设现在不需要使用ProgreessDialog,而是要使用ProgressBar,就需要修改相应代码,这样会比较麻烦。如果采用回调的思想的话,就利于代码更好地进行维护。

现在就来重构一下代码,新建一个callback包,在该包下新建名为SmsBackUpCallBack的回调接口,在其中定义两个方法,代码如下:

package com.example.mobilesafe.callback;

public interface SmsBackUpCallBack {

    /**
     * 设置短信总数(使用对话框和进度条调用均可)
     * @param max 短信总数
     */
    public void setMax(int max);

    /**
     * 备份过程中更新进度条的百分比(使用对话框和进度条调用均可)
     * @param index
     */
    public void setProgress(int index);
}

修改SmsBackUpDao,修改backup()方法,将接口作为参数传递,并且完善相应逻辑,代码如下:

package com.example.mobilesafe.dao;

import android.app.ProgressDialog;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Xml;

import com.example.mobilesafe.callback.SmsBackUpCallBack;

import org.xmlpull.v1.XmlSerializer;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class SmsBackUpDao {

    // 进度条的进度值
    private static int index = 0;

    /**
     * 短信备份方法
     * @param context 上下文环境
     * @param path 备份文件夹路径
     * @param smsBackUpCallBack 回调接口
     */
    public static void backUp(Context context, String path, SmsBackUpCallBack smsBackUpCallBack) {

        FileOutputStream fos = null;

        Cursor cursor = null;

        try {
            // 1.获取短信备份写入的文件
            File file = new File(path);
            // 2.获取内容解析器,对数据库进行查询
            cursor = context.getContentResolver().query(Uri.parse("content://sms/"), new String[]{"address", "date", "type", "body"}, null, null, null);
            // 3.文件相应的输出流
            fos = new FileOutputStream(file);
            /*
             * 4.序列化数据库中读取的数据,放置到xml中,xml应维护成如下格式:
             * 
             * 		
             * 			
* * * *
* * * * * * *
*/
XmlSerializer xmlSerializer = Xml.newSerializer(); // 5.给xml做相应的设置 xmlSerializer.setOutput(fos,"utf-8"); xmlSerializer.startDocument("utf-8",true); // DTD(xml规范) xmlSerializer.startTag(null,"smss"); // 命名空间起始 // 6.备份短信总数的指定给进度条,设置进度条的进度值(进度条/对话框进度条) if (smsBackUpCallBack != null){ smsBackUpCallBack.setMax(cursor.getCount()); } // 7.读取数据库中的每一行的数据,写入到xml中 while (cursor.moveToNext()){ xmlSerializer.startTag(null,"sms"); xmlSerializer.startTag(null,"address"); xmlSerializer.text(cursor.getString(0)); xmlSerializer.endTag(null,"address"); xmlSerializer.startTag(null,"date"); xmlSerializer.text(cursor.getString(1)); xmlSerializer.endTag(null,"date"); xmlSerializer.startTag(null,"type"); xmlSerializer.text(cursor.getString(2)); xmlSerializer.endTag(null,"type"); xmlSerializer.startTag(null,"body"); xmlSerializer.text(cursor.getString(3)); xmlSerializer.endTag(null,"body"); xmlSerializer.endTag(null,"sms"); // 8.每进行一次循环,就要让进度条变化 index++; // 9.ProgressDialog可以在子线程中更新相应的进度条的转变(进度条/对话框进度条) if (smsBackUpCallBack != null){ smsBackUpCallBack.setProgress(index); } } xmlSerializer.endTag(null,"smss"); xmlSerializer.endDocument(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (cursor != null && fos != null) { cursor.close(); fos.close(); } } catch (IOException e) { e.printStackTrace(); } } } }

接着,修改ATooaActivity,主要是修改showSmsBackUpDialog()方法,需要传入一个实现了刚刚编写的回调接口的对象,代码如下:

package com.example.mobilesafe.activity;

import androidx.appcompat.app.AppCompatActivity;

import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.widget.TextView;

import com.example.mobilesafe.R;
import com.example.mobilesafe.callback.SmsBackUpCallBack;
import com.example.mobilesafe.dao.SmsBackUpDao;

import java.io.File;

public class AToolActivity extends AppCompatActivity {

    private TextView tv_query_phone_address;

    private TextView tv_sms_backup;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a_tool);

        // 1.电话归属地查询方法
        initPhoneAddress();

        // 2.短信备份方法
        initSmsBackUp();
    }

    /**
     * 1.电话归属地查询方法
     */
    private void initPhoneAddress() {
        tv_query_phone_address = findViewById(R.id.tv_query_phone_address);
        tv_query_phone_address.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(getApplicationContext(),QueryAddressActivity.class));
            }
        });
    }

    /**
     * 2.短信备份方法
     */
    private void initSmsBackUp() {
        tv_sms_backup = findViewById(R.id.tv_sms_backup);
        tv_sms_backup.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               showSmsBackUpDialog();
            }
        });
    }

    /**
     * 展示备份短信的进度条的对话框
     */
    private void showSmsBackUpDialog() {
        // 1.创建一个带进度条的对话框
        final ProgressDialog progressDialog = new ProgressDialog(this);
        progressDialog.setIcon(R.drawable.ic_launcher);
        progressDialog.setTitle("短信备份");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); // 指定进度条的样式为水平
        progressDialog.show(); // 展示进度条
        // 2.获取短信
        new Thread(){
            @Override
            public void run() {
                String path = getApplicationContext().getExternalFilesDir(null) + File.separator + "smsbackup.xml"; // 将短信信息存储到SD卡中
                SmsBackUpDao.backUp(getApplicationContext(), path, new SmsBackUpCallBack() {
                    @Override
                    public void setMax(int max) {
                        progressDialog.setMax(max);
                        // 进度条.setMax(max);
                    }

                    @Override
                    public void setProgress(int index) {
                        progressDialog.setProgress(index);
                        // 进度条.setProgress(index);
                    }
                });
                progressDialog.dismiss();
            }
        }.start();
    }
}

4.软件管理——磁盘可用空间计算

我们实现了前面提到的功能,现在需要实现另一个模块——“软件管理”,如图所示:

Android开发实战《手机安全卫士》——9.“软件管理”模块实现 & 删除通话记录 & 备份短信 & 计算手机可用空间 & ListView双条目适配_第2张图片

该模块应该实现以下功能:

  1. 磁盘和sd卡的可用空间大小获取;
  2. 展示用户应用和系统应用的列表;
  3. 应用安装的位置:SD卡、手机内存中均可;
  4. 列表展示应用的logo,应用名称;
  5. 常驻悬浮框的描述内容修改。

首先修改HomeActivity,修改initData()方法,添加进入“软件管理”模块的功能,代码如下:

/**
     * 2.初始化数据
     */
    private void initData() {
        // 1.初始化每个图标的标题
        mTitleStrs = new String[]{"手机防盗","通信卫士","软件管理","进程管理","流量统计","手机杀毒","缓存清理","高级工具","设置中心"};
        // 2.初始化每个图标的图像
        mDrawableIds = new int[]{R.drawable.home_safe,R.drawable.home_callmsgsafe,R.drawable.home_apps,R.drawable.home_taskmanager,R.drawable.home_netmanager,R.drawable.home_trojan,R.drawable.home_sysoptimize,R.drawable.home_tools,R.drawable.home_settings};
        // 3.为GridView设置数据适配器
        gv_home.setAdapter(new MyAdapter());
        // 4.注册GridView中单个条目的点击事件
        gv_home.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                switch (position){
                    case 0:
                        // 手机防盗
                        showDialog();
                        break;
                    case 1:
                        // 通信卫士
                        startActivity(new Intent(getApplicationContext(),BlackNumberActivity.class));
                        break;
                    case 2:
                        // 软件管理
                        startActivity(new Intent(getApplicationContext(),AppManagerActivity.class));
                        break;
                    case 7:
                        // 高级工具
                        startActivity(new Intent(getApplicationContext(),AToolActivity.class));
                        break;
                    case 8:
                        // 设置中心
                        Intent intent = new Intent(getApplicationContext(), SettingActivity.class);
                        startActivity(intent);
                        break;
                    default:
                        break;
                }
            }
        });
    }

在activity包下新增名为AppManagerActivity的Activity,作为“软件管理”模块下的Activity,首先修改其布局activity_app_manager.xml,代码如下:


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.AppManagerActivity"
    android:orientation="vertical">

    <TextView
        style="@style/TitleStyle"
        android:text="软件管理"/>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tv_memory"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="磁盘可用"/>

        <TextView
            android:id="@+id/tv_sd_memory"
            android:layout_alignParentRight="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="sd卡可用"/>

    RelativeLayout>

    <ListView
        android:id="@+id/lv_app_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

LinearLayout>

修改AppManagerActivity,添加initTitle()方法,完成计算磁盘和SD卡的可用空间的业务逻辑,代码如下:

package com.example.mobilesafe.activity;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Environment;
import android.os.StatFs;
import android.text.format.Formatter;
import android.widget.TextView;

import com.example.mobilesafe.R;

public class AppManagerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_app_manager);

        // 初始化磁盘 & SD卡可用大小
        initTitle();

        
    }

    /**
     * 初始化磁盘 & SD卡可用大小
     */
    private void initTitle() {
        // 0.初始化控件
        TextView tv_memory = findViewById(R.id.tv_memory);
        TextView tv_sd_memory = findViewById(R.id.tv_sd_memory);
        // 1.获取磁盘(内存,区分于手机运行内存)可用大小,磁盘路径
        String path = Environment.getDataDirectory().getAbsolutePath();
        // 2.获取SD卡可用大小,SD卡路径
        String sdpath = Environment.getExternalStorageDirectory().getAbsolutePath();
        // 3.获取以上两个路径下文件夹的可用大小
        long space = getAvailSpace(path);
        long sdspace = getAvailSpace(sdpath);
        // 4.对bytes为单位的数值格式化
        String strSpace = Formatter.formatFileSize(this, space);
        String strSdSpace = Formatter.formatFileSize(this, sdspace);
        // 5.给控件赋值
        tv_memory.setText("磁盘可用:" + strSpace);
        tv_sd_memory.setText("sd卡可用:" + strSdSpace);
    }

    /**
     * 计算文件夹的可用大小
     * @param path 路径名
     * @return 可用空间大小,单位为byte=8bit
     */
    private long getAvailSpace(String path) {
        // 获取可用磁盘大小的对象
        StatFs statFs = new StatFs(path);
        // 获取可用区块的个数
        long availableBlocks = statFs.getAvailableBlocks();
        // 获取区块的大小
        long blockSize = statFs.getBlockSize();
        // 可用空间大小 = 区块大小 * 可用区块个数
        return availableBlocks * blockSize;
    }
}

5.软件管理——获取应用相关信息集合

上一节中我们完成了“软件管理”模块中获取内存大小和SD卡大小的功能,这一节我们要实现获取应用相关信息集合的功能。

ListView控件中,每个条目都应该拥有以下信息:

  • 应用图标
  • 应用名称
  • 应用存储路径(内存,sd卡)
  • 应用类型(用户应用,系统应用)
  • 应用包名

为了将这些信息进行一个封装,在domain包下新建AppInfo,作为封装条目中这些信息的一个Java Bean,代码如下:

package com.example.mobilesafe.domain;

import android.graphics.drawable.Drawable;

public class AppInfo {

    private Drawable icon;

    private String name;

    private String packagename;

    private boolean isSdCard;

    private boolean isSystem;

    public AppInfo() {
    }

    public Drawable getIcon() {
        return icon;
    }

    public void setIcon(Drawable icon) {
        this.icon = icon;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPackagename() {
        return packagename;
    }

    public void setPackagename(String packagename) {
        this.packagename = packagename;
    }

    public boolean isSdCard() {
        return isSdCard;
    }

    public void setSdCard(boolean sdCard) {
        isSdCard = sdCard;
    }

    public boolean isSystem() {
        return isSystem;
    }

    public void setSystem(boolean system) {
        isSystem = system;
    }
}

为了将获取应用相关信息集合的一系列操作进行复用,在dao下新增AppInfoDao,添加静态方法getAppInfoList(),这里使用到了状态机制,该机制一般应用在手机游戏里的RPG元素当中,例如(某样属性(0001) & 某样道具(1111) = 0001,说明该道具具有该属性)代码如下:

 package com.example.mobilesafe.dao;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

import com.example.mobilesafe.domain.AppInfo;

import java.util.ArrayList;
import java.util.List;

public class AppInfoDao {

    /**
     * 获取安装在手机上应用相关信息集合的方法
     * @param context 上下文环境
     */
    public static List<AppInfo> getAppInfoList(Context context){
        // 0.声明集合
        List<AppInfo> appInfoList = new ArrayList<>();
        // 1.获取包管理对象
        PackageManager packageManager = context.getPackageManager();
        // 2.通过包管理对象来获取手机上所有应用信息,直接传递0即可
        List<PackageInfo> installedPackages = packageManager.getInstalledPackages(0);
        // 3.循环遍历上述集合,获取每一个安装在手机上的应用相关信息(包名,名称,路径,系统,图标)
        for (PackageInfo packageInfo : installedPackages) {
            AppInfo appInfo = new AppInfo();
            // 4.包名
            appInfo.setPackagename(packageInfo.packageName);
            // 5.获取应用名称、图标,其信息存储在application节点中
            appInfo.setName(packageInfo.applicationInfo.loadLabel(packageManager).toString());
            appInfo.setIcon(packageInfo.applicationInfo.loadIcon(packageManager));
            // 6.判断应用是否为系统应用(状态机)
            if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == ApplicationInfo.FLAG_SYSTEM){
                // 系统应用
                appInfo.setSystem(true);
            }else {
                // 非系统应用
                appInfo.setSystem(false);
            }
            // 7.判断应用是否为sd卡应用
            if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == ApplicationInfo.FLAG_EXTERNAL_STORAGE){
                // sd卡应用
                appInfo.setSdCard(true);
            }else {
                // 非sd卡应用
                appInfo.setSdCard(false);
            }
            // 8.加入集合中
            appInfoList.add(appInfo);
        }
        // 9.返回集合
        return appInfoList;
    }
}

修改AppManagerActivity,添加intiListView()方法,对布局中的列表进行初始化和数据适配,代码如下:

package com.example.mobilesafe.activity;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.StatFs;
import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import com.example.mobilesafe.R;
import com.example.mobilesafe.dao.AppInfoDao;
import com.example.mobilesafe.domain.AppInfo;

import java.util.ArrayList;
import java.util.List;

public class AppManagerActivity extends AppCompatActivity {

    private ListView lv_app_list;

    private List<AppInfo> mAppInfoList;

    private MyAdapter mAdapter;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            // 4.使用数据适配器
            mAdapter = new MyAdapter();
            lv_app_list.setAdapter(mAdapter);
        }
    };

    class MyAdapter extends BaseAdapter{

        @Override
        public int getCount() {
            return mAppInfoList.size();
        }

        @Override
        public AppInfo getItem(int position) {
            return mAppInfoList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            // 1.判断ConverView是否为空
            if (convertView == null){
                convertView = View.inflate(getApplicationContext(),R.layout.list_app_item,null);
                // 2.获取控件实例赋值给ViewHolder
                holder = new ViewHolder();
                holder.iv_icon_application = convertView.findViewById(R.id.iv_icon_application);
                holder.tv_app_name = convertView.findViewById(R.id.tv_app_name);
                holder.tv_app_location = convertView.findViewById(R.id.tv_app_location);
                // 3.将Holder放到ConverView上
                convertView.setTag(holder);
            }else {
                holder = (ViewHolder) convertView.getTag();
            }
            // 4.获取Holder中的控件实例,赋值
            holder.iv_icon_application.setBackground(getItem(position).getIcon());
            holder.tv_app_name.setText(getItem(position).getName());
            // 5.显示应用安装的位置
            if (getItem(position).isSdCard()){
                holder.tv_app_location.setText("sd卡应用");
            }else {
                holder.tv_app_location.setText("内存应用");
            }
            // 6.返回现有条目填充上数据的View对象
            return convertView;
        }
    }

    static class ViewHolder{
        ImageView iv_icon_application;
        TextView tv_app_name;
        TextView tv_app_location;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_app_manager);

        // 初始化磁盘 & SD卡可用大小
        initTitle();
        
        // 初始化ListView
        intiListView();

    }

    /**
     * 初始化磁盘 & SD卡可用大小
     */
    private void initTitle() {
        // 0.初始化控件
        TextView tv_memory = findViewById(R.id.tv_memory);
        TextView tv_sd_memory = findViewById(R.id.tv_sd_memory);
        // 1.获取磁盘(内存,区分于手机运行内存)可用大小,磁盘路径
        String path = Environment.getDataDirectory().getAbsolutePath();
        // 2.获取SD卡可用大小,SD卡路径
        String sdpath = Environment.getExternalStorageDirectory().getAbsolutePath();
        // 3.获取以上两个路径下文件夹的可用大小
        long space = getAvailSpace(path);
        long sdspace = getAvailSpace(sdpath);
        // 4.对bytes为单位的数值格式化
        String strSpace = Formatter.formatFileSize(this, space);
        String strSdSpace = Formatter.formatFileSize(this, sdspace);
        // 5.给控件赋值
        tv_memory.setText("磁盘可用:" + strSpace);
        tv_sd_memory.setText("sd卡可用:" + strSdSpace);
    }

    /**
     * 计算文件夹的可用大小
     * @param path 路径名
     * @return 可用空间大小,单位为byte=8bit
     */
    private long getAvailSpace(String path) {
        // 获取可用磁盘大小的对象
        StatFs statFs = new StatFs(path);
        // 获取可用区块的个数
        long availableBlocks = statFs.getAvailableBlocks();
        // 获取区块的大小
        long blockSize = statFs.getBlockSize();
        // 可用空间大小 = 区块大小 * 可用区块个数
        return availableBlocks * blockSize;
    }

    /**
     * 初始化ListView
     */
    private void intiListView() {
        // 1.初始化控件
        lv_app_list = findViewById(R.id.lv_app_list);
        new Thread(){
            @Override
            public void run() {
                // 2.准备填充ListView中数据适配器的数据
                mAppInfoList = AppInfoDao.getAppInfoList(getApplicationContext());
                for (AppInfo appInfo : mAppInfoList) {
                    Log.i("应用信息为:",appInfo.toString());
                }
                // 3.发送空消息进行数据绑定
                mHandler.sendEmptyMessage(0);
            }
        }.start();
    }
}

在res/layout下新建list_app_item,作为列表中每个条目的布局,代码如下:


<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv_icon"
        android:background="@drawable/ic_launcher"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/tv_app_name"
        android:layout_toRightOf="@id/iv_icon"
        android:text="应用名称"
        android:textColor="#000"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/tv_app_location"
        android:layout_toRightOf="@id/iv_icon"
        android:layout_below="@id/tv_app_name"
        android:text="安装位置"
        android:textColor="#000"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

RelativeLayout>

6.软件管理——指定两种条目类型数据适配器指定 & 集合数据划分

前面我们已经完成了ListView展示数据,现在需要实现另一种条目类型的数据适配器指定。

目前列表中需要用到两种条目:

  • 图片条目:图片 + 文字
  • 纯文本条目:文字

效果应该如下图所示:

Android开发实战《手机安全卫士》——9.“软件管理”模块实现 & 删除通话记录 & 备份短信 & 计算手机可用空间 & ListView双条目适配_第3张图片

默认的ListView指定了一种类型的条目,现在想让ListView的数据适配器多一种类型的条目,需要重写方法getViewTypeCount(),并且区分索引指定条目的展示类型(根据索引值来决定展示哪种类型的条目),需要重写getViewItemType()。与此同时,还需要在重写方法中修改一些关于集合的属性。

自然,为了达到区分的目的,需要将集合分成两部分:

修改AppManagerActivity,修改适配器的业务,完善相应逻辑,代码如下:

package com.example.mobilesafe.activity;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.StatFs;
import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import com.example.mobilesafe.R;
import com.example.mobilesafe.dao.AppInfoDao;
import com.example.mobilesafe.domain.AppInfo;

import java.util.ArrayList;
import java.util.List;

public class AppManagerActivity extends AppCompatActivity {

    private ListView lv_app_list;

    private List<AppInfo> mAppInfoList;

    private MyAdapter mAdapter;

    // 系统应用所在的集合
    private List<AppInfo> mSystemList;

    // 用户应用所在的集合
    private List<AppInfo> mCustomerList;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            // 4.使用数据适配器
            mAdapter = new MyAdapter();
            lv_app_list.setAdapter(mAdapter);
        }
    };

    class MyAdapter extends BaseAdapter{

        // 在ListView中多添加一种类型条目,条目总数有2种
        @Override
        public int getViewTypeCount() {
            return super.getViewTypeCount() + 1;
        }

        // 根据索引值决定展示哪种类型的条目
        @Override
        public int getItemViewType(int position) {
            if (position == 0 || position == mCustomerList.size() + 1){
                // 纯文本条目
                return 0;
            }else {
                // 图文条目
                return 1;
            }
        }

        @Override
        public int getCount() {
            return mCustomerList.size() + mSystemList.size() + 2;
        }

        @Override
        public AppInfo getItem(int position) {
            if (position == 0 || position == mCustomerList.size() + 1){
                // 纯文本条目
                return null;
            }else {
                // 图文条目
                if (position < mCustomerList.size() + 1){
                    // 用户应用
                    return mCustomerList.get(position - 1);
                }else {
                    // 系统应用
                    return mSystemList.get(position - mCustomerList.size() - 2);
                }
            }
            // return mAppInfoList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // 判断当前索引指向的条目类型状态码
            int itemViewType = getItemViewType(position);
            if (itemViewType == 0){
                // 纯文本条目
                ViewTitleHolder holder = null;
                if (convertView == null){
                    convertView = View.inflate(getApplicationContext(),R.layout.list_app_item_text,null);
                    holder = new ViewTitleHolder();
                    holder.tv_title_des = convertView.findViewById(R.id.tv_title_des);
                    convertView.setTag(holder);
                }else {
                    holder = (ViewTitleHolder) convertView.getTag();
                }
                if (position == 0){
                    holder.tv_title_des.setText("用户应用("+mCustomerList.size()+")");
                }else {
                    holder.tv_title_des.setText("系统应用("+mSystemList.size()+")");
                }
                return convertView;
            }else {
                // 图文条目
                ViewHolder holder = null;
                // 1.判断ConverView是否为空
                if (convertView == null){
                    convertView = View.inflate(getApplicationContext(),R.layout.list_app_item,null);
                    // 2.获取控件实例赋值给ViewHolder
                    holder = new ViewHolder();
                    holder.iv_icon_application = convertView.findViewById(R.id.iv_icon_application);
                    holder.tv_app_name = convertView.findViewById(R.id.tv_app_name);
                    holder.tv_app_location = convertView.findViewById(R.id.tv_app_location);
                    // 3.将Holder放到ConverView上
                    convertView.setTag(holder);
                }else {
                    holder = (ViewHolder) convertView.getTag();
                }
                // 4.获取Holder中的控件实例,赋值
                holder.iv_icon_application.setBackground(getItem(position).getIcon());
                holder.tv_app_name.setText(getItem(position).getName());
                // 5.显示应用安装的位置
                if (getItem(position).isSdCard()){
                    holder.tv_app_location.setText("sd卡应用");
                }else {
                    holder.tv_app_location.setText("内存应用");
                }
                // 6.返回现有条目填充上数据的View对象
                return convertView;
            }
        }
    }

    static class ViewHolder{
        ImageView iv_icon_application;
        TextView tv_app_name;
        TextView tv_app_location;
    }

    static class ViewTitleHolder{
        TextView tv_title_des;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_app_manager);

        // 初始化磁盘 & SD卡可用大小
        initTitle();
        
        // 初始化ListView
        intiListView();

    }

    /**
     * 初始化磁盘 & SD卡可用大小
     */
    private void initTitle() {
        // 0.初始化控件
        TextView tv_memory = findViewById(R.id.tv_memory);
        TextView tv_sd_memory = findViewById(R.id.tv_sd_memory);
        // 1.获取磁盘(内存,区分于手机运行内存)可用大小,磁盘路径
        String path = Environment.getDataDirectory().getAbsolutePath();
        // 2.获取SD卡可用大小,SD卡路径
        String sdpath = Environment.getExternalStorageDirectory().getAbsolutePath();
        // 3.获取以上两个路径下文件夹的可用大小
        long space = getAvailSpace(path);
        long sdspace = getAvailSpace(sdpath);
        // 4.对bytes为单位的数值格式化
        String strSpace = Formatter.formatFileSize(this, space);
        String strSdSpace = Formatter.formatFileSize(this, sdspace);
        // 5.给控件赋值
        tv_memory.setText("磁盘可用:" + strSpace);
        tv_sd_memory.setText("sd卡可用:" + strSdSpace);
    }

    /**
     * 计算文件夹的可用大小
     * @param path 路径名
     * @return 可用空间大小,单位为byte=8bit
     */
    private long getAvailSpace(String path) {
        // 获取可用磁盘大小的对象
        StatFs statFs = new StatFs(path);
        // 获取可用区块的个数
        long availableBlocks = statFs.getAvailableBlocks();
        // 获取区块的大小
        long blockSize = statFs.getBlockSize();
        // 可用空间大小 = 区块大小 * 可用区块个数
        return availableBlocks * blockSize;
    }

    /**
     * 初始化ListView
     */
    private void intiListView() {
        // 1.初始化控件
        lv_app_list = findViewById(R.id.lv_app_list);
        new Thread(){
            @Override
            public void run() {
                // 2.准备填充ListView中数据适配器的数据
                mAppInfoList = AppInfoDao.getAppInfoList(getApplicationContext());
                mSystemList = new ArrayList<>();
                mCustomerList = new ArrayList<>();
                // 分割集合
                for (AppInfo appInfo : mAppInfoList) {
                    if (appInfo.isSystem()){
                        mSystemList.add(appInfo);
                    }else {
                        mCustomerList.add(appInfo);
                    }
                }
                // 3.发送空消息进行数据绑定
                mHandler.sendEmptyMessage(0);
            }
        }.start();
    }
}

在res/layout下新建list_app_item_text.xml,作为纯文本条目的布局,代码如下:


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_title_des"
        android:background="#ccc"
        android:text="纯文本条目"
        android:padding="5dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

LinearLayout>

7.软件管理——常驻悬浮框效果

我们已经完成了“软件管理”模块中的大部分功能了,现在需要完成——常驻悬浮框的效果。该效果实现了当列表从“用户应用”拖拽到“系统应用时”,悬浮框中的文字也会发生相应变化。

要想实现该效果,则需要创建一个父布局中的某个控件盖住原来的条目,即ListView控件。

修改activity_app_manager.xml,根据思路来进行修改,代码如下:


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.AppManagerActivity"
    android:orientation="vertical">

    <TextView
        style="@style/TitleStyle"
        android:text="软件管理"/>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tv_memory"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="磁盘可用"/>

        <TextView
            android:id="@+id/tv_sd_memory"
            android:layout_alignParentRight="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="sd卡可用"/>

    RelativeLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ListView
            android:id="@+id/lv_app_list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <TextView
            android:id="@+id/tv_title_type"
            android:background="#ccc"
            android:textColor="#fff"
            android:text="条目类型的说明"
            android:padding="5dp"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    FrameLayout>


LinearLayout>

修改AppManagerActivity,修改intiListView()方法,获取到刚刚创建的文本控件的实例,并设置列表的滚动事件,以此来执行相应逻辑,代码如下:

package com.example.mobilesafe.activity;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.StatFs;
import android.text.format.Formatter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import com.example.mobilesafe.R;
import com.example.mobilesafe.dao.AppInfoDao;
import com.example.mobilesafe.domain.AppInfo;

import java.util.ArrayList;
import java.util.List;

public class AppManagerActivity extends AppCompatActivity {

    private ListView lv_app_list;

    private List<AppInfo> mAppInfoList;

    private MyAdapter mAdapter;

    // 系统应用所在的集合
    private List<AppInfo> mSystemList;

    // 用户应用所在的集合
    private List<AppInfo> mCustomerList;

    private TextView tv_title_type;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            // 4.使用数据适配器
            mAdapter = new MyAdapter();
            lv_app_list.setAdapter(mAdapter);
        }
    };


    class MyAdapter extends BaseAdapter{

        // 在ListView中多添加一种类型条目,条目总数有2种
        @Override
        public int getViewTypeCount() {
            return super.getViewTypeCount() + 1;
        }

        // 根据索引值决定展示哪种类型的条目
        @Override
        public int getItemViewType(int position) {
            if (position == 0 || position == mCustomerList.size() + 1){
                // 纯文本条目
                return 0;
            }else {
                // 图文条目
                return 1;
            }
        }

        @Override
        public int getCount() {
            return mCustomerList.size() + mSystemList.size() + 2;
        }

        @Override
        public AppInfo getItem(int position) {
            if (position == 0 || position == mCustomerList.size() + 1){
                // 纯文本条目
                return null;
            }else {
                // 图文条目
                if (position < mCustomerList.size() + 1){
                    // 用户应用
                    return mCustomerList.get(position - 1);
                }else {
                    // 系统应用
                    return mSystemList.get(position - mCustomerList.size() - 2);
                }
            }
            // return mAppInfoList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // 判断当前索引指向的条目类型状态码
            int itemViewType = getItemViewType(position);
            if (itemViewType == 0){
                // 纯文本条目
                ViewTitleHolder holder = null;
                if (convertView == null){
                    convertView = View.inflate(getApplicationContext(),R.layout.list_app_item_text,null);
                    holder = new ViewTitleHolder();
                    holder.tv_title_des = convertView.findViewById(R.id.tv_title_des);
                    convertView.setTag(holder);
                }else {
                    holder = (ViewTitleHolder) convertView.getTag();
                }
                if (position == 0){
                    holder.tv_title_des.setText("用户应用("+mCustomerList.size()+")");
                }else {
                    holder.tv_title_des.setText("系统应用("+mSystemList.size()+")");
                }
                return convertView;
            }else {
                // 图文条目
                ViewHolder holder = null;
                // 1.判断ConverView是否为空
                if (convertView == null){
                    convertView = View.inflate(getApplicationContext(),R.layout.list_app_item,null);
                    // 2.获取控件实例赋值给ViewHolder
                    holder = new ViewHolder();
                    holder.iv_icon_application = convertView.findViewById(R.id.iv_icon_application);
                    holder.tv_app_name = convertView.findViewById(R.id.tv_app_name);
                    holder.tv_app_location = convertView.findViewById(R.id.tv_app_location);
                    // 3.将Holder放到ConverView上
                    convertView.setTag(holder);
                }else {
                    holder = (ViewHolder) convertView.getTag();
                }
                // 4.获取Holder中的控件实例,赋值
                holder.iv_icon_application.setBackground(getItem(position).getIcon());
                holder.tv_app_name.setText(getItem(position).getName());
                // 5.显示应用安装的位置
                if (getItem(position).isSdCard()){
                    holder.tv_app_location.setText("sd卡应用");
                }else {
                    holder.tv_app_location.setText("内存应用");
                }
                // 6.返回现有条目填充上数据的View对象
                return convertView;
            }
        }
    }

    static class ViewHolder{
        ImageView iv_icon_application;
        TextView tv_app_name;
        TextView tv_app_location;
    }

    static class ViewTitleHolder{
        TextView tv_title_des;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_app_manager);

        // 初始化磁盘 & SD卡可用大小
        initTitle();
        
        // 初始化ListView
        intiListView();

    }

    /**
     * 初始化磁盘 & SD卡可用大小
     */
    private void initTitle() {
        // 0.初始化控件
        TextView tv_memory = findViewById(R.id.tv_memory);
        TextView tv_sd_memory = findViewById(R.id.tv_sd_memory);
        // 1.获取磁盘(内存,区分于手机运行内存)可用大小,磁盘路径
        String path = Environment.getDataDirectory().getAbsolutePath();
        // 2.获取SD卡可用大小,SD卡路径
        String sdpath = Environment.getExternalStorageDirectory().getAbsolutePath();
        // 3.获取以上两个路径下文件夹的可用大小
        long space = getAvailSpace(path);
        long sdspace = getAvailSpace(sdpath);
        // 4.对bytes为单位的数值格式化
        String strSpace = Formatter.formatFileSize(this, space);
        String strSdSpace = Formatter.formatFileSize(this, sdspace);
        // 5.给控件赋值
        tv_memory.setText("磁盘可用:" + strSpace);
        tv_sd_memory.setText("sd卡可用:" + strSdSpace);
    }

    /**
     * 计算文件夹的可用大小
     * @param path 路径名
     * @return 可用空间大小,单位为byte=8bit
     */
    private long getAvailSpace(String path) {
        // 获取可用磁盘大小的对象
        StatFs statFs = new StatFs(path);
        // 获取可用区块的个数
        long availableBlocks = statFs.getAvailableBlocks();
        // 获取区块的大小
        long blockSize = statFs.getBlockSize();
        // 可用空间大小 = 区块大小 * 可用区块个数
        return availableBlocks * blockSize;
    }

    /**
     * 初始化ListView
     */
    private void intiListView() {
        // 1.初始化控件
        tv_title_type = findViewById(R.id.tv_title_type);
        lv_app_list = findViewById(R.id.lv_app_list);
        new Thread(){
            @Override
            public void run() {
                // 2.准备填充ListView中数据适配器的数据
                mAppInfoList = AppInfoDao.getAppInfoList(getApplicationContext());
                mSystemList = new ArrayList<>();
                mCustomerList = new ArrayList<>();
                // 分割集合
                for (AppInfo appInfo : mAppInfoList) {
                    if (appInfo.isSystem()){
                        mSystemList.add(appInfo);
                    }else {
                        mCustomerList.add(appInfo);
                    }
                }
                // 3.发送空消息进行数据绑定
                mHandler.sendEmptyMessage(0);
            }
        }.start();
        // 4.给ListView注册滚动事件
        lv_app_list.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {

            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                // 在滚动过程中调用方法
                if (mSystemList != null && mCustomerList != null){
                    if (firstVisibleItem >= mCustomerList.size() + 1){
                        // 滚动到了系统应用
                        tv_title_type.setText("系统应用("+mSystemList.size()+")");
                    }else {
                        // 滚动到了用户应用
                        tv_title_type.setText("用户应用("+mCustomerList.size()+")");
                    }
                }
            }
        });
    }
}

你可能感兴趣的:(Android)