Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式

文章目录

  • 1.进程管理——隐藏系统进程
  • 2.进程管理——锁屏清理
  • 3.拓展功能——生成快捷方式
  • 4.高级工具——常用号码查询(布局实现)
  • 5.高级工具——常用号码查询(逻辑实现)
  • 6.拓展功能——窗体小部件
    • 6.1 窗体小部件——布局实现
    • 6.2 窗体小部件——生命周期
    • 6.3 窗体小部件——更新进程总数 & 可用内存数
    • 6.4 窗体小部件——清理进程
    • 6.5 窗体小部件——锁屏优化
  • 7.拓展功能——反编译
  • 8.高级工具——程序锁(布局实现)
  • 9.高级工具——程序锁(逻辑实现)

1.进程管理——隐藏系统进程

在“进程管理”模块中,我们已经完成了大部分功能,现在需要完成将系统进程进行隐藏的功能,即该模块下方的第四个按钮“进程设置”中的第一个功能,如图所示:

Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式_第1张图片

首先修改ProcessManagerActivity,对第四个按钮控件注册点击事件,并且添加setting(),作为跳转到设置界面Activity的方法。为了达到隐藏系统应用的目的,可以直接修改适配器中的getCount()方法,然后在退出Activity时刷新一次数据,代码如下:

package com.example.mobilesafe.activity;

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

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.format.Formatter;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import com.example.mobilesafe.R;
import com.example.mobilesafe.constant.ConstantValue;
import com.example.mobilesafe.dao.ProcessInfoDao;
import com.example.mobilesafe.domain.ProcessInfo;
import com.example.mobilesafe.utils.SharedPreferencesUtil;
import com.example.mobilesafe.utils.ToastUtil;

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

public class ProcessManagerActivity extends AppCompatActivity {

    private TextView tv_process_count;

    private TextView tv_memory_info;

    private TextView tv_title_type;

    private ListView lv_process_list;

    private Button btn_all;

    private Button btn_reverse;

    private Button btn_clear;

    private Button btn_setting;

    private int mProcessCount; // 进程总数

    private List<ProcessInfo> mProcessInfoList;

    private List<ProcessInfo> mSystemList;

    private List<ProcessInfo> mCustomerList;

    private MyAdapter mAdapter;

    private ProcessInfo mProcessInfo;

    private long mAvailSpace;

    private long mTotalSpace;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            mAdapter = new MyAdapter();
            lv_process_list.setAdapter(mAdapter);
            if (tv_title_type != null && mCustomerList != null ){
                tv_title_type.setText("用户进程("+mCustomerList.size()+")");
            }
        }
    };

    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() {
            if (SharedPreferencesUtil.getBoolean(getApplicationContext(), ConstantValue.SHOW_SYSTEM,false)){
                return mCustomerList.size() + mSystemList.size() + 2;
            }else {
                return mCustomerList.size() + 1;
            }
        }

        @Override
        public ProcessInfo 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_process_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_memory = convertView.findViewById(R.id.tv_memory);
                    holder.cb_box = convertView.findViewById(R.id.cb_box);
                    // 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());
                String strMemorySize = Formatter.formatFileSize(getApplicationContext(), getItem(position).getMemSize());
                holder.tv_memory.setText(strMemorySize);
                // 5.本进程无法被选中,所以先将CheckBox隐藏
                if ((getItem(position).getPackageName()).equals(getPackageName())){
                    holder.cb_box.setVisibility(View.GONE);
                }else {
                    holder.cb_box.setVisibility(View.VISIBLE);
                }
                holder.cb_box.setChecked(getItem(position).isCheck());
                // 6.返回现有条目填充上数据的View对象
                return convertView;
            }
        }
    }

    static class ViewHolder{
        ImageView iv_icon_application;
        TextView tv_app_name;
        TextView tv_memory;
        CheckBox cb_box;
    }

    static class ViewTitleHolder{
        TextView tv_title_des;
    }

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

        // 初始化UI
        initUI();

        // 初始化标题上的信息:进程总数,内存使用情况
        initTitleData();

        // 初始化ListView列表中的数据
        initListData();
    }

    /**
     * 初始化UI
     */
    private void initUI() {
        tv_process_count = findViewById(R.id.tv_process_count);
        tv_memory_info = findViewById(R.id.tv_memory_info);
        tv_title_type = findViewById(R.id.tv_title_type);
        lv_process_list = findViewById(R.id.lv_process_list);
        btn_all = findViewById(R.id.btn_all);
        btn_reverse = findViewById(R.id.btn_reverse);
        btn_clear = findViewById(R.id.btn_clear);
        btn_setting = findViewById(R.id.btn_setting);

        // 给ListView注册滚动事件
        lv_process_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()+")");
                    }
                }
            }
        });

        // 给ListView注册点击事件
        lv_process_list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (position == 0 || position == mCustomerList.size() + 1){
                    // 纯文本条目
                    return;
                }else {
                    // 图文条目
                    if (position < mCustomerList.size() + 1){
                        // 用户应用
                        mProcessInfo = mCustomerList.get(position - 1);
                    }else {
                        // 系统应用
                        mProcessInfo = mSystemList.get(position - mCustomerList.size() - 2);
                    }
                    if (mProcessInfo != null){
                        if (!mProcessInfo.getPackageName().equals(getPackageName())){
                            // 选中条目指向的对象和本应用的包名不一致,才需要去取反状态和设置单选框状态
                            // 状态取反
                            mProcessInfo.setCheck(!mProcessInfo.isCheck());
                            // CheckBox显示状态取反
                            // 通过选中条目的view对象,findViewById找到此条目指向的cb_box,然后切换其状态
                            CheckBox cb_box = view.findViewById(R.id.cb_box);
                            cb_box.setChecked(mProcessInfo.isCheck());
                        }
                    }
                }
            }
        });

        btn_all.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 全选
                selectAll();
            }
        });

        btn_reverse.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 反选
                selectReverse();
            }
        });

        btn_clear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 一键清理内存
                clearAll();
            }
        });

        btn_setting.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setting();
            }
        });
    }

    /**
     * "设置"按钮的功能
     */
    private void setting() {
        Intent intent = new Intent(this, ProcessSettingActivity.class);
        startActivityForResult(intent,0);
    }

    /**
     * 从“设置”Activityt退出后的一些操作
     * @param requestCode
     * @param resultCode
     * @param data
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // 通知数据适配器刷新
        if (mAdapter != null){
            mAdapter.notifyDataSetChanged();
        }
    }

    /**
     * "一键清理"按钮的功能
     */
    private void clearAll() {
        // 0.创建一个记录释放了多大空间的变量
        long totalReleaseSpace = 0;
        // 1.创建一个记录需要杀死的进程的集合
        List<ProcessInfo> killProcessList = new ArrayList<>();
        // 2.获取选中进程
        for (ProcessInfo processInfo : mCustomerList){
            if (processInfo.getPackageName().equals(getPackageName())){
                continue;
            }
            if (processInfo.isCheck()){
                // 3.记录被选中的用户进程(即需要杀死)
                killProcessList.add(processInfo);
            }
        }
        for (ProcessInfo processInfo : mSystemList){
            if (processInfo.isCheck()){
                // 4.记录被选中的系统进程(即需要杀死)
                killProcessList.add(processInfo);
            }
        }
        // 5.循环遍历killProcessList,然后移除mCustomerList和mSystemList中的对象
        for (ProcessInfo processInfo : killProcessList) {
            // 6.判断当前进程再哪个集合中,从所在集合中移除
            if (mCustomerList.contains(processInfo)){
                mCustomerList.remove(processInfo);
            }
            if (mSystemList.contains(processInfo)){
                mSystemList.remove(processInfo);
            }
            // 7.杀死记录在killProcessList中的进程
            ProcessInfoDao.killProcess(this,processInfo);
            // 8.记录释放控件的总大小
            totalReleaseSpace += processInfo.getMemSize();
        }
        // 9.在集合改变后,需要通知数据适配器刷新
        if (mAdapter != null){
            mAdapter.notifyDataSetChanged();
        }
        // 10.更新进程总数
        mProcessCount -= killProcessList.size();
        // 11.更新可用剩余空间(释放空间 + 原有剩余空间 = 当前剩余空间)
        mAvailSpace += totalReleaseSpace;
        // 12.根据进程总数和剩余空间大小更新控件
        tv_process_count.setText("进程总数:"+mProcessCount);
        tv_memory_info.setText("剩余/总共" + Formatter.formatFileSize(this,mAvailSpace) + "/" + Formatter.formatFileSize(this,mTotalSpace));
        // 13.通过Toast告知用户,释放了多少空间,杀死多少进程(也可以使用C语言的占位符)
        ToastUtil.show(getApplicationContext(),"杀死了" + killProcessList.size() + "个进程,释放了" + Formatter.formatFileSize(this,totalReleaseSpace));
    }

    /**
     * “全选”按钮的功能
     */
    private void selectAll() {
        // 1.将所有集合中的对象上的isCheck字段设置为true代表全选(除了手机卫士自己)
        for (ProcessInfo processInfo : mCustomerList){
            if (processInfo.getPackageName().equals(getPackageName())){
                continue;
            }
            processInfo.setCheck(true);
        }
        for (ProcessInfo processInfo : mSystemList){
            processInfo.setCheck(true);
        }
        // 2.通知数据适配器刷新
        if (mAdapter != null){
            mAdapter.notifyDataSetChanged();
        }
    }

    /**
     * “反选”按钮的功能
     */
    private void selectReverse() {
        // 1.将所有集合中的对象上的isCheck字段设置为true代表全选(除了手机卫士自己)
        for (ProcessInfo processInfo : mCustomerList){
            if (processInfo.getPackageName().equals(getPackageName())){
                continue;
            }
            processInfo.setCheck(!processInfo.isCheck());
        }
        for (ProcessInfo processInfo : mSystemList){
            processInfo.setCheck(!processInfo.isCheck());
        }
        // 2.通知数据适配器刷新
        if (mAdapter != null){
            mAdapter.notifyDataSetChanged();
        }
    }

    /**
     * 初始化标题上的信息:进程总数,内存使用情况
     */
    private void initTitleData() {
        // 获取进程总数
        mProcessCount = ProcessInfoDao.getProcessCount(this);
        tv_process_count.setText("进程总数:" + mProcessCount);
        // 获取可用内存大小
        mAvailSpace = ProcessInfoDao.getAvailSpace(this);
        String strAvailSpace = Formatter.formatFileSize(this, mAvailSpace); // 格式化
        // 获取总内存大小
        mTotalSpace = ProcessInfoDao.getTotalSpace(this);
        String strTotalSpace = Formatter.formatFileSize(this, mTotalSpace); // 格式化
        tv_memory_info.setText("剩余/总共" + strAvailSpace + "/" + strTotalSpace);
    }

    /**
     * 初始化列表中的数据
     */
    private void initListData() {
        getData();
    }

    /**
     * 获取数据的方法
     */
    private void getData(){
        new Thread(){
            @Override
            public void run() {
                // 2.准备填充ListView中数据适配器的数据
                mProcessInfoList = ProcessInfoDao.getProcessInfoList(getApplicationContext());
                mSystemList = new ArrayList<>();
                mCustomerList = new ArrayList<>();
                // 分割集合
                for (ProcessInfo processInfo : mProcessInfoList) {
                    if (processInfo.isSystem()){
                        // 系统进程
                        mSystemList.add(processInfo);
                    }else {
                        // 用户进程
                        mCustomerList.add(processInfo);
                    }
                }
                // 3.发送空消息进行数据绑定
                mHandler.sendEmptyMessage(0);
            }
        }.start();
    }
}

新增一个名为ProcessSettingActivity的Activity,作为“进程管理”模块中设置的Activity,首先修改其布局文件activity_process_setting.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.ProcessSettingActivity"
    android:orientation="vertical">

    <TextView
        android:text="进程设置"
        style="@style/TitleStyle"/>

    <CheckBox
        android:id="@+id/cb_show_system"
        android:text="隐藏系统进程"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <CheckBox
        android:id="@+id/cb_lock_clean"
        android:text="锁屏清理已关闭"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

LinearLayout>

修改ConstantValue,添加一个名为SHOW_SYSTEM的静态常量,作为判断是否显示系统进程的标识符,代码如下:

package com.example.mobilesafe.constant;

public class ConstantValue {

    /**
     * 设置中心——记录更新的状态
     */
    public static final String OPEN_UPDATE = "open_update";

    /**
     * 设置中心——记录归属地信息显示的样式
     */
    public static final String TOAST_STYLE = "toast_style";

    /**
     * 设置中心——记录归属地信息位置(左上角)的x坐标
     */
    public static final String LOCATION_X = "location_x";

    /**
     * 设置中心——记录归属地信息位置(左上角)的y坐标
     */
    public static final String LOCATION_Y = "location_y";

    /**
     * 手机防盗——设置密码的状态
     */
    public static final String MOBILE_SAFE_PASSWORD = "mobile_safe_password";

    /**
     * 手机防盗——四个界面是否设置完成的状态
     */
    public static final String SETUP_OVER = "setup_over";

    /**
     * 手机防盗——SIM卡绑定序列号
     */
    public static final String SIM_NUMBER = "sim_number";

    /**
     * 手机防盗——联系人电话号码
     */
    public static final String CONTACT_PHONE = "contact_phone";

    /**
     * 手机防盗——是否开启防盗保护总开关
     */
    public static final String OPEN_SECURITY = "open_security";

    /**
     * 进程管理——是否显示系统进程
     */
    public static final String SHOW_SYSTEM = "show_system";
}

修改ProcessSettingActivity,添加initSystemShow(),作为完善“隐藏系统进程”功能的方法,代码如下:

package com.example.mobilesafe.activity;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.CheckBox;
import android.widget.CompoundButton;

import com.example.mobilesafe.R;
import com.example.mobilesafe.constant.ConstantValue;
import com.example.mobilesafe.utils.SharedPreferencesUtil;

public class ProcessSettingActivity extends AppCompatActivity {

    private CheckBox cb_show_system;

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

        // 初始化“隐藏系统进程”
        initSystemShow();
    }

    /**
     * 隐藏系统进程
     */
    private void initSystemShow() {
        cb_show_system = findViewById(R.id.cb_show_system);
        // 对之前存储过的状态进行回显
        boolean showSystem = SharedPreferencesUtil.getBoolean(this, ConstantValue.SHOW_SYSTEM, false);
        cb_show_system.setChecked(showSystem);
        if (showSystem){
            cb_show_system.setText("显示系统进程");
        }else {
            cb_show_system.setText("隐藏系统进程");
        }
        // 对选中状态进行监听
        cb_show_system.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                // isChecked作为是否选中的状态
                if (isChecked){
                    cb_show_system.setText("显示系统进程");
                }else {
                    cb_show_system.setText("隐藏系统进程");
                }
                SharedPreferencesUtil.putBoolean(getApplicationContext(), ConstantValue.SHOW_SYSTEM,isChecked);
            }
        });
    }
}

2.进程管理——锁屏清理

在“进程管理”模块中,我们已经完成了大部分功能,现在需要完成锁屏后自动清理进程的功能,即该模块下方的第四个按钮“进程设置”中的第二个功能,如图所示:

Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式_第2张图片

在锁屏的时候,需要将手机正在运行的进程杀死,即使应用退出,此功能也能起效果(服务),分为以下步骤实现:

  • 点击单选框,可以开启/关闭服务(LookScreenService);
  • 判断服务是开启还是关闭的状态,去决定单选框是否选中;
  • 监听手机发出锁屏的广播,手机锁屏后就清理掉手机进程。

修改ProcessSettingActivity,添加initLockScreenClear(),作为锁屏清理的方法,代码如下:

package com.example.mobilesafe.activity;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.widget.CheckBox;
import android.widget.CompoundButton;

import com.example.mobilesafe.R;
import com.example.mobilesafe.constant.ConstantValue;
import com.example.mobilesafe.service.LockScreenService;
import com.example.mobilesafe.utils.ServiceUtil;
import com.example.mobilesafe.utils.SharedPreferencesUtil;

public class ProcessSettingActivity extends AppCompatActivity {

    private CheckBox cb_show_system;

    private CheckBox cb_lock_clean;

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

        // 初始化“隐藏系统进程”
        initSystemShow();

        // 初始化“锁屏时清理系统进程”
        initLockScreenClear();
    }

    /**
     * 隐藏系统进程
     */
    private void initSystemShow() {
        cb_show_system = findViewById(R.id.cb_show_system);
        // 对之前存储过的状态进行回显
        boolean showSystem = SharedPreferencesUtil.getBoolean(this, ConstantValue.SHOW_SYSTEM, false);
        cb_show_system.setChecked(showSystem);
        if (showSystem){
            cb_show_system.setText("显示系统进程");
        }else {
            cb_show_system.setText("隐藏系统进程");
        }
        // 对选中状态进行监听
        cb_show_system.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                // isChecked作为是否选中的状态
                if (isChecked){
                    cb_show_system.setText("显示系统进程");
                }else {
                    cb_show_system.setText("隐藏系统进程");
                }
                SharedPreferencesUtil.putBoolean(getApplicationContext(), ConstantValue.SHOW_SYSTEM,isChecked);
            }
        });
    }

    /**
     * 锁屏时清理进程
     */
    private void initLockScreenClear() {
        cb_lock_clean = findViewById(R.id.cb_lock_clean);
        // 根据锁屏清理的服务是否开启,决定单选框是否选中
        boolean isRunning = ServiceUtil.isRunning(this, "com.example.mobilesafe.service.LockScreenService");
        if (isRunning){
            cb_lock_clean.setText("锁屏清理已开启");
        }else {
            cb_lock_clean.setText("锁屏清理已关闭");
        }
        // 维护CheckBox的选中状态
        cb_lock_clean.setChecked(isRunning);
        // 对选中状态进行监听
        cb_lock_clean.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                // isChecked作为是否选中的状态
                if (isChecked){
                    cb_lock_clean.setText("锁屏清理已开启");
                    // 开启服务
                    startService(new Intent(getApplicationContext(),LockScreenService.class));
                }else {
                    cb_lock_clean.setText("锁屏清理已关闭");
                    // 关闭服务
                    stopService(new Intent(getApplicationContext(), LockScreenService.class));
                }
            }
        });
    }
}

在service包下新增名为LockScreenService的服务,作为锁屏时清理进程的服务,代码如下:

package com.example.mobilesafe.service;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;

import com.example.mobilesafe.dao.ProcessInfoDao;

public class LockScreenService extends Service {

    private IntentFilter intentFilter;

    private InnerReceiver innerReceiver;

    @Override
    public void onCreate() {
        super.onCreate();
        // 锁屏Action
        intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
        innerReceiver = new InnerReceiver();
        registerReceiver(innerReceiver,intentFilter);
    }

    class InnerReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            // 清理手机正在运行的进程
            ProcessInfoDao.killAllProcess(context);
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (innerReceiver != null){
            unregisterReceiver(innerReceiver);
        }
    }
}

为了方便操作,将杀死全部进程的业务逻辑封装到ProcessInfoDao中,新增killAllProcess(),作为杀死全部运行中进程的方法,代码如下:

    /**
     * 杀死全部进程的方法
     * @param context 上下文环境
     */
    public static void killAllProcess(Context context) {
        // 1.获取ActivityManager对象
        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        // 2.获取正在运行的进程的集合
        List<ActivityManager.RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
        // 3.循环遍历所有的进程,并且杀死
        for (ActivityManager.RunningAppProcessInfo runningAppProcess : runningAppProcesses) {
            // 4.除了手机卫士以外的应用,其他的进程都需要杀死
            if (runningAppProcess.processName.equals(context.getPackageName())){
                // 5.如果匹配上了手机卫士,则跳出本次循环,进行下一次循环
                continue;
            }
            activityManager.killBackgroundProcesses(runningAppProcess.processName);
        }
    }

3.拓展功能——生成快捷方式

跟电脑应用一样,手机应用同样具有快捷方式。想要在桌面上生成快捷方式,需要以下内容:

  • 图标
  • 要素
  • 点击方式

查看源码,要想为手机应用生成快捷方式,需要发送一段广播让系统中的广播接收器接收,该广播接收器在清单文件中的声明代码如下:

		
        <receiver
            android:name="com.android.launcher2.InstallShortcutReceiver"
            android:permission="com.android.launcher.permission.INSTALL_SHORTCUT">
            <intent-filter>
		
                <action android:name="com.android.launcher.action.INSTALL_SHORTCUT" />
            intent-filter>
        receiver>

修改SplashActivity,添加initShortCut()方法,在闪屏页面时就发送广播,以此来生成快捷方式,代码如下:

	// 生成快捷方式(在onCreate()方法中进行调用)
        if (!SharedPreferencesUtil.getBoolean(this,ConstantValue.HAS_SHORTCUT,false)){
            initShortCut();
        }
------------------------------------------------------------------------------------------------------------
	/**
     * 14.生成快捷方式
     */
    private void initShortCut() {
        // 1.给intent维护图标,名称
        Intent intent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
        intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher));
        intent.putExtra(Intent.EXTRA_SHORTCUT_NAME,"手机卫士");
        // 2.点击快捷方式后跳转到的Activity,维护开启的意图对象
        Intent shortCutIntent = new Intent("android.intent.action.HOME");
        shortCutIntent.addCategory("android.intent.category.DEFAULT");
        intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT,shortCutIntent);
        // 3.发送广播
        sendBroadcast(intent);
        // 4.告知Sp已经生成了快捷方式
        SharedPreferencesUtil.putBoolean(this,ConstantValue.HAS_SHORTCUT,true);
    }

修改HomeActivity在声明文件的声明,添加隐式Intent的启动意图,让快捷方式启动生效,除此之外还需要添加相应权限,代码如下:

    <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>

	<activity android:name=".activity.HomeActivity" >
            <intent-filter>
                <action android:name="android.intent.action.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
            intent-filter>
        activity>

最后,修改ConstantValue,添加静态常量HAS_SHORTCUT,作为是否创建快捷方式的标识符,代码如下:

package com.example.mobilesafe.constant;

public class ConstantValue {

    /**
     * 设置中心——记录更新的状态
     */
    public static final String OPEN_UPDATE = "open_update";

    /**
     * 设置中心——记录归属地信息显示的样式
     */
    public static final String TOAST_STYLE = "toast_style";

    /**
     * 设置中心——记录归属地信息位置(左上角)的x坐标
     */
    public static final String LOCATION_X = "location_x";

    /**
     * 设置中心——记录归属地信息位置(左上角)的y坐标
     */
    public static final String LOCATION_Y = "location_y";

    /**
     * 手机防盗——设置密码的状态
     */
    public static final String MOBILE_SAFE_PASSWORD = "mobile_safe_password";

    /**
     * 手机防盗——四个界面是否设置完成的状态
     */
    public static final String SETUP_OVER = "setup_over";

    /**
     * 手机防盗——SIM卡绑定序列号
     */
    public static final String SIM_NUMBER = "sim_number";

    /**
     * 手机防盗——联系人电话号码
     */
    public static final String CONTACT_PHONE = "contact_phone";

    /**
     * 手机防盗——是否开启防盗保护总开关
     */
    public static final String OPEN_SECURITY = "open_security";

    /**
     * 进程管理——是否显示系统进程
     */
    public static final String SHOW_SYSTEM = "show_system";

    /**
     * 拓展功能——生成快捷方式
     */
    public static final String HAS_SHORTCUT = "has_shortcut";
}

4.高级工具——常用号码查询(布局实现)

前面我们完成了“进程管理”模块的实现,现在需要完成“高级工具”模块中的第三个功能——即常用号码查询,如图中的红框所示:

Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式_第3张图片
在该功能中,会记录一些常用号码,界面如下所示:

Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式_第4张图片

点开任意区域的常用号码,会展开显示一些具体的号码信息,如图所示:

Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式_第5张图片

修改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"/>

    <TextView
        android:id="@+id/tv_commonnumber_query"
        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>

修改AToolActivity,新增initCommonNumberQuery(),作为常用号码查询的方法,代码如下:

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;

    private TextView tv_commonnumber_query;

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

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

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

        // 3.常用号码查询
        initCommonNumberQuery();
    }

    /**
     * 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();
    }

    /**
     * 3.常用号码查询
     */
    private void initCommonNumberQuery() {
        tv_commonnumber_query = findViewById(R.id.tv_commonnumber_query);
        tv_commonnumber_query.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(getApplicationContext(),CommonNumberQueryActivity.class));
            }
        });
    }
}

5.高级工具——常用号码查询(逻辑实现)

该功能需要从数据库获取常用号码,为了保证能在应用打开时就能解析数据库,修改SplashActivity,修改initDB(),添加数据库的解析方法,代码如下:

 /**
     * 12.初始化数据库
     */
    private void initDB(){
        // 归属地数据拷贝过程
        initAddressDB("address.db");
        // 常用号码数据库的拷贝过程
        initAddressDB("commonnum.db");
    }

为了将组信息和孩子节点信息更好地进行封装,在domain下新建GroupInfo和ChildInfo实体类,代码分别如下:

package com.example.mobilesafe.domain;

import java.util.List;

public class GroupInfo {

    private String name;

    private String index;

    private List<ChildInfo> childList;

    public String getName() {
        return name;
    }

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

    public String getIndex() {
        return index;
    }

    public void setIndex(String index) {
        this.index = index;
    }

    public List<ChildInfo> getChildList() {
        return childList;
    }

    public void setChildList(List<ChildInfo> childList) {
        this.childList = childList;
    }
}
package com.example.mobilesafe.domain;

public class ChildInfo {
    
    private String _id;

    private String number;

    private String name;

    public String get_id() {
        return _id;
    }

    public void set_id(String _id) {
        this._id = _id;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getName() {
        return name;
    }

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

在dao下新建一个名为CommonnumberDao的工具类,用于封装对数据库中数据表的操作,代码如下:

package com.example.mobilesafe.dao;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.example.mobilesafe.domain.ChildInfo;
import com.example.mobilesafe.domain.GroupInfo;

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

public class CommonnumberDao {

    // 1.指定访问数据库的路径
    public static String path = "data/data/com.example.mobilesafe/files/commonnum.db";

    /**
     * 2.开启数据库(组)
     * @return 数据组的集合
     */
    public List<GroupInfo> getGroup(){
        SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
        Cursor cursor = db.query("classlist", new String[]{"name", "idx"}, null, null, null, null, null, null);
        List<GroupInfo> groupInfoList = new ArrayList<>();
        while (cursor.moveToNext()){
            GroupInfo groupInfo = new GroupInfo();
            groupInfo.setName(cursor.getString(0));
            groupInfo.setIndex(cursor.getString(1));
            groupInfo.setChildList(getChild(groupInfo.getIndex()));
            groupInfoList.add(groupInfo);
        }
        cursor.close();
        db.close();
        return groupInfoList;
    }

    /**
     * 3.获取每一个组中孩子节点的数据
     * @param idx 数据组返回的子节点表的id
     */
    public List<ChildInfo> getChild(String idx){
        SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
        Cursor cursor = db.rawQuery("select * from table" + idx + ";", null);
        List<ChildInfo> childInfoList = new ArrayList<>();
        while (cursor.moveToNext()){
            ChildInfo childInfo = new ChildInfo();
            childInfo.set_id(cursor.getString(0));
            childInfo.setNumber(cursor.getString(1));
            childInfo.setName(cursor.getString(2));
            childInfoList.add(childInfo);
        }
        cursor.close();
        db.close();
        return childInfoList;
    }
}

在activity下新增名为CommonNumberQueryActivity的Activity,首先修改其布局文件activity_common_number_query.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.CommonNumberQueryActivity"
    android:orientation="vertical">

    <TextView
        style="@style/TitleStyle"
        android:text="常用号码查询"/>

    
    <ExpandableListView
        android:id="@+id/elv_common_number"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    ExpandableListView>

LinearLayout>

随后,在res/layout下新建一个名为elv_child_item.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_name"
        android:text="电话名称"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/tv_number"
        android:text="电话号码"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

LinearLayout>

修改CommonNumberQueryActivity,这里需要从SQLite数据库中获取数据,完善相应逻辑,代码如下:

package com.example.mobilesafe.activity;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.BaseExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.TextView;

import com.example.mobilesafe.R;
import com.example.mobilesafe.dao.CommonnumberDao;
import com.example.mobilesafe.domain.ChildInfo;
import com.example.mobilesafe.domain.GroupInfo;

import java.util.List;

public class CommonNumberQueryActivity extends AppCompatActivity {

    private ExpandableListView elv_common_number;

    private List<GroupInfo> mGroup;

    private MyAdapter mAdapter;

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

        // 初始化UI
        initUI();

        // 初始化数据
        initData();
    }

    /**
     * 初始化UI
     */
    private void initUI() {
        elv_common_number = findViewById(R.id.elv_common_number);
    }

    /**
     * 给可扩展的ListView准备数据,进行填充
     */
    private void initData() {
        CommonnumberDao commonnumberDao = new CommonnumberDao();
        mGroup = commonnumberDao.getGroup();
        mAdapter = new MyAdapter();
        elv_common_number.setAdapter(mAdapter);
        // 给可扩展的ExpandableListView注册点击事件(打电话)
        elv_common_number.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
            @Override
            public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
                startCall(mAdapter.getChild(groupPosition,childPosition).getNumber());
                return false;
            }
        });
    }

    /**
     * 打电话功能封装
     * @param number 列表中对应的孩子节点的条目中的电话
     */
    private void startCall(String number) {
        // 开启系统的打电话界面
        Intent intent = new Intent(Intent.ACTION_CALL);
        intent.setData(Uri.parse("tel:"+ number));
        startActivity(intent);
    }

    class MyAdapter extends BaseExpandableListAdapter {

        @Override
        public int getGroupCount() {
            return mGroup.size();
        }

        @Override
        public int getChildrenCount(int groupPosition) {
            return mGroup.get(groupPosition).getChildList().size();
        }

        @Override
        public GroupInfo getGroup(int groupPosition) {
            return mGroup.get(groupPosition);
        }

        @Override
        public ChildInfo getChild(int groupPosition, int childPosition) {
            return mGroup.get(groupPosition).getChildList().get(childPosition);
        }

        @Override
        public long getGroupId(int groupPosition) {
            return groupPosition;
        }

        @Override
        public long getChildId(int groupPosition, int childPosition) {
            return childPosition;
        }

        @Override
        public boolean hasStableIds() {
            return false;
        }

        @Override
        public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
            TextView textView = new TextView(getApplicationContext());
            textView.setText(getGroup(groupPosition).getName());
            textView.setTextColor(Color.RED);
            // textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP,20); 字体大小
            return textView;
        }

        @Override
        public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
            View view = View.inflate(getApplicationContext(), R.layout.elv_child_item, null);
            TextView tv_name = view.findViewById(R.id.tv_name);
            TextView tv_number = view.findViewById(R.id.tv_number);
            tv_name.setText(getChild(groupPosition,childPosition).getName());
            tv_number.setText(getChild(groupPosition,childPosition).getNumber());
            return view;
        }

        @Override
        public boolean isChildSelectable(int groupPosition, int childPosition) {
            // 孩子节点中的条目是否相应事件
            return true;
        }
    }
}

6.拓展功能——窗体小部件

当下的手机安全应用中,都会在手机主界面上显示一个窗体小部件,如图中红框所示:

Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式_第6张图片

窗体小部件功能需要使用Widget组件来实现,参考Google的官方文档,窗体小部件功能需要声明一个广播接收者,代码如下:

	<receiver android:name="ExampleAppWidgetProvider" >
	    <intent-filter>
			<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
	    intent-filter>
	    <meta-data android:name="android.appwidget.provider"
		       android:resource="@xml/example_appwidget_info" />
	receiver>

新建一个provider包,在包下新建MyAppWidgetProvider,作为官方要求的内容提供器,需要去继承AppWidgetProvider,代码留空即可。

在res下新建xml文件夹,其中新建example_appwidget_info.xml,作为窗体小部件的配置文件,代码如下:


<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="40dp"
    android:minHeight="40dp"
    android:initialLayout="@layout/example_appwidget">
appwidget-provider>

与此同时,在res/layout下新建example_appwidget.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:text="窗体小部件示例"
        android:background="#f00"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

LinearLayout>

显示效果如图所示:

Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式_第7张图片

以上是演示窗体小部件的使用实例,接下来将分小节继续讨论如何编写一个类似于金山卫士的窗体小部件。

6.1 窗体小部件——布局实现

首先,在xml下新建process_widget_provider.xml,作为新的窗体小部件的配置文件,代码如下:


<appwidget-provider
    android:minWidth="294.0dip"
    android:minHeight="72.0dip"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/process_widget"
  xmlns:android="http://schemas.android.com/apk/res/android" />

接下来,在res/layout下新建process_widget.xml,作为窗体小部件的布局文件,代码如下:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_root"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/widget_bg_portrait"
    android:gravity="center_vertical">

    <LinearLayout
        android:layout_width="0.0dip"
        android:layout_height="fill_parent"
        android:layout_marginLeft="5.0dip"
        android:layout_weight="1.0"
        android:background="@drawable/widget_bg_portrait_child"
        android:gravity="center_vertical"
        android:orientation="vertical"
        android:paddingTop="3.0dip"
        android:paddingBottom="3.0dip">

        <TextView
            android:id="@+id/tv_process_count"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10.0dip" />

        <ImageView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="1.0dip"
            android:layout_marginBottom="1.0dip"
            android:background="@drawable/widget_bg_portrait_child_divider" />

        <TextView
            android:id="@+id/tv_process_memory"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10.0dip" />
    LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_vertical">

            <ImageView
                android:layout_width="20.0dip"
                android:layout_height="20.0dip"
                android:src="@drawable/ic_launcher" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/app_name"
                android:textColor="#fff" />
        LinearLayout>

        <Button
            android:id="@+id/btn_clear"
            android:layout_width="90.0dip"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginTop="5.0dip"
            android:background="@drawable/selector_next_btn_bg"
            android:text="一键清理"
            android:textColor="#fff" />
    LinearLayout>
LinearLayout>

最后,修改声明文件中声明窗体小部件部分的代码,代码如下:

		<receiver android:name=".provider.MyAppWidgetProvider" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            intent-filter>
            <meta-data android:name="android.appwidget.provider"
                android:resource="@xml/process_widget_provider" />
        receiver>

最终的窗体小部件的布局显示效果如图中的红框所示:

Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式_第8张图片

6.2 窗体小部件——生命周期

上一节中,我们完成了窗体小部件的布局编写,这一节中我们来探讨一下该部件应该要实现的功能:

  1. 显示进程总数和可用内存大小
  2. 需要实时更新数据,所以将更新过程放置在服务中;
  3. 一旦拖出窗体小部件,开启服务;所有窗体小部件销毁的时候,关闭服务;

为了让窗体小部件更好地和服务进行绑定,需要简单了解一下其生命周期。由于窗体小部件本质上是一个广播接收者,所以它们的生命周期类似。而在窗体小部件的功能实现里,主要需要使用到以下生命周期中的回调方法:

  • onEnabled():创建第一个窗体小部件的方法;
  • onUpdate():创建多个窗体小部件的方法,就算只创建一个窗体小部件也会调用;
  • onAppWidgetOptionsChanged():当窗体小部件的宽高发生改变的时候会调用,创建小部件的时候也会调用,需要在api >= 16以上的环境中使用;
  • onDisabled():删除任意一个窗体小部件调用方法;
  • onDeleted():删除最后一个窗体小部件调用方法。

根据以上信息,修改MyAppWidgetProvider,在合适的时候开启/关闭服务,代码如下:

package com.example.mobilesafe.provider;

import android.annotation.TargetApi;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import com.example.mobilesafe.service.UpdateWidgetService;

public class MyAppWidgetProvider extends AppWidgetProvider {

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
    }

    @Override
    public void onEnabled(Context context) {
        super.onEnabled(context);
        // 创建第一个窗体小部件的方法
        // 开启服务
        context.startService(new Intent(context,UpdateWidgetService.class));
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        // 创建多个窗体小部件的方法
        // 开启服务
        context.startService(new Intent(context,UpdateWidgetService.class));
    }

    @TargetApi(16)
    @Override
    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
        // 当窗体小部件的宽高发生改变的时候会调用,创建小部件的时候也会调用,需要在api > 16的环境下使用
        // 开启服务
        context.startService(new Intent(context, UpdateWidgetService.class));
    }

    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        super.onDeleted(context, appWidgetIds);
        // 删除任意一个窗体小部件调用方法
    }


    @Override
    public void onDisabled(Context context) {
        super.onDisabled(context);
        // 删除最后一个窗体小部件调用方法
        // 关闭服务
        context.stopService(new Intent(context,UpdateWidgetService.class));
    }



}

6.3 窗体小部件——更新进程总数 & 可用内存数

在service包下新建UpdateWidgetService,作为窗体小部件绑定的service,在该service中用定时器来定时更新响应信息,代码如下:

package com.example.mobilesafe.service;

import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.text.format.Formatter;
import android.widget.RemoteViews;

import com.example.mobilesafe.R;
import com.example.mobilesafe.dao.ProcessInfoDao;
import com.example.mobilesafe.provider.MyAppWidgetProvider;

import java.util.Timer;
import java.util.TimerTask;

public class UpdateWidgetService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        // 管理进程总数和可用内存数的更新(定时器)
        startTimer();
    }

    /**
     * 开启一个定时器
     */
    private void startTimer() {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                // UI定时刷新
                updateAppWidget();
            }
        }, 0, 5000);
    }

    /**
     * 更新窗口小部件中的信息
     */
    private void updateAppWidget() {
        // 1.获取AppWidget对象
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
        // 2.获取窗体小部件布局转换成的view对象
        RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.process_widget);
        // 3.给窗体小部件设置view对象,内部控件赋值
        remoteViews.setTextViewText(R.id.tv_process_count,"进程总数:" + ProcessInfoDao.getProcessCount(this));
        // 4.显示可用内存大小
        String strAcailSpace = Formatter.formatFileSize(this, ProcessInfoDao.getAvailSpace(this));
        remoteViews.setTextViewText(R.id.tv_process_memory,"可用内存:" + strAcailSpace);
        // 5.通知AppWidget进行更新
        ComponentName componentName = new ComponentName(this, MyAppWidgetProvider.class);
        appWidgetManager.updateAppWidget(componentName,remoteViews);
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

6.4 窗体小部件——清理进程

在窗体小部件中还有一个功能——就是点击按钮之后会一键清理掉进程,我们现在来实现一下这个功能。

修改UpdateWidgetService,为窗体小部件注册点击事件,完善清理进程的功能,代码如下:

package com.example.mobilesafe.service;

import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.text.format.Formatter;
import android.widget.RemoteViews;

import com.example.mobilesafe.R;
import com.example.mobilesafe.dao.ProcessInfoDao;
import com.example.mobilesafe.provider.MyAppWidgetProvider;

import java.util.Timer;
import java.util.TimerTask;

public class UpdateWidgetService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        // 管理进程总数和可用内存数的更新(定时器)
        startTimer();
    }

    /**
     * 开启一个定时器
     */
    private void startTimer() {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                // UI定时刷新
                updateAppWidget();
            }
        }, 0, 5000);
    }

    /**
     * 更新窗口小部件中的信息
     */
    private void updateAppWidget() {
        // 1.获取AppWidget对象
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
        // 2.获取窗体小部件布局转换成的view对象
        RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.process_widget);
        // 3.给窗体小部件设置view对象,内部控件赋值
        remoteViews.setTextViewText(R.id.tv_process_count,"进程总数:" + ProcessInfoDao.getProcessCount(this));
        // 4.显示可用内存大小
        String strAcailSpace = Formatter.formatFileSize(this, ProcessInfoDao.getAvailSpace(this));
        remoteViews.setTextViewText(R.id.tv_process_memory,"可用内存:" + strAcailSpace);

        // 5.点击窗体小部件,进入应用
        Intent intent = new Intent("android.intent.action.HOME");
        intent.addCategory("android.intent.category.DEFAULT");
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.ll_root,pendingIntent);
        // 6.通过延期意图发送广播,在广播接收者中杀死进程
        Intent broadCastIntent = new Intent("android.intent.action.KILL_BACKGROUND_PROCESS");
        PendingIntent broadcast = PendingIntent.getBroadcast(this, 0, broadCastIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.btn_clear,broadcast);

        // 7.通知AppWidget进行更新
        ComponentName componentName = new ComponentName(this, MyAppWidgetProvider.class);
        appWidgetManager.updateAppWidget(componentName,remoteViews);

    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

在清单文件中声明一个新的广播接收者,接收由UpdateWidgetService发出的自定义广播,代码如下:

<receiver
            android:name=".receiver.KillProcessReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.KILL_BACKGROUND_PROCESS" />
            intent-filter>
        receiver>

在receiver包下新建KillProcessReceiver,作为接收到广播后杀死进程的广播接收者,代码如下:

package com.example.mobilesafe.receiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

import com.example.mobilesafe.dao.ProcessInfoDao;

public class KillProcessReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // 杀死进程
        ProcessInfoDao.killAllProcess(context);
    }
}

6.5 窗体小部件——锁屏优化

前面我们基本上完成了窗体小部件的功能实现,现在想要对它进行优化——即在锁屏的时候停止定时器的更新,在解锁之后重新开启定时器的更新,这样是为了保证性能的高效。

修改UpdateWidgetService,按照上述说明提出的思路进行相应优化,代码如下:

package com.example.mobilesafe.service;

import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import android.text.format.Formatter;
import android.widget.RemoteViews;

import com.example.mobilesafe.R;
import com.example.mobilesafe.dao.ProcessInfoDao;
import com.example.mobilesafe.provider.MyAppWidgetProvider;

import java.util.Timer;
import java.util.TimerTask;

public class UpdateWidgetService extends Service {

    private Timer mTimer;
    private InnerReceiver mInnerReceiver;

    @Override
    public void onCreate() {
        super.onCreate();
        // 管理进程总数和可用内存数的更新(定时器)
        startTimer();

        // 注册监听锁屏/解锁状态的广播接收者
        IntentFilter intentFilter = new IntentFilter();
        // 开锁Action
        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
        // 解锁Action
        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);

        // 绑定广播接收器的接收条件
        mInnerReceiver = new InnerReceiver();
        registerReceiver(mInnerReceiver,intentFilter);
    }

    /**
     * 监听到锁屏/解锁状态的广播接收者内部类
     */
    private class InnerReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)){
                // 开启定时更新任务
                startTimer();
            }else {
                // 关闭定时更新任务
                cancelTimerTask();
            }
        }
    }

    /**
     * 关闭定时器的任务
     */
    private void cancelTimerTask() {
        // Timer中的cancel()就是取消定时任务的方法
        if(mTimer != null){
            mTimer.cancel();
            mTimer = null; // 置空以此回收
        }
    }

    /**
     * 开启一个定时器
     */
    private void startTimer() {
        mTimer = new Timer();
        mTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                // UI定时刷新
                updateAppWidget();
            }
        }, 0, 5000);
    }

    /**
     * 更新窗口小部件中的信息
     */
    private void updateAppWidget() {
        // 1.获取AppWidget对象
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
        // 2.获取窗体小部件布局转换成的view对象
        RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.process_widget);
        // 3.给窗体小部件设置view对象,内部控件赋值
        remoteViews.setTextViewText(R.id.tv_process_count,"进程总数:" + ProcessInfoDao.getProcessCount(this));
        // 4.显示可用内存大小
        String strAcailSpace = Formatter.formatFileSize(this, ProcessInfoDao.getAvailSpace(this));
        remoteViews.setTextViewText(R.id.tv_process_memory,"可用内存:" + strAcailSpace);

        // 5.点击窗体小部件,进入应用
        Intent intent = new Intent("android.intent.action.HOME");
        intent.addCategory("android.intent.category.DEFAULT");
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.ll_root,pendingIntent);
        // 6.通过延期意图发送广播,在广播接收者中杀死进程
        Intent broadCastIntent = new Intent("android.intent.action.KILL_BACKGROUND_PROCESS");
        PendingIntent broadcast = PendingIntent.getBroadcast(this, 0, broadCastIntent, PendingIntent.FLAG_CANCEL_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.btn_clear,broadcast);

        // 7.通知AppWidget进行更新
        ComponentName componentName = new ComponentName(this, MyAppWidgetProvider.class);
        appWidgetManager.updateAppWidget(componentName,remoteViews);

    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mInnerReceiver != null){
            unregisterReceiver(mInnerReceiver);
        }
        // 停止定时器任务
        cancelTimerTask();
    }
}

7.拓展功能——反编译

反编译是开发中可能需要用到的功能,即将某个打包成apk的文件进行反编译,读取其源码设计。这里使用比较常用的apktool来进行apk的反编译。

反编译的步骤:

  1. 将要反编译的apk文件放到apktool文件夹中;
  2. 用cmd命令移动到apktool文件夹里;
  3. 输入apktool d (你要反编译的apk文件的全路径) (反编译后源码的位置) 即可;
  4. 当出现完如图所示的反编译过程记录后,反编译结束:

Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式_第9张图片

8.高级工具——程序锁(布局实现)

前面我们完成了“高级工具”模块中常用号码查询的功能,现在需要实现该模块的最后一个功能,即——程序锁,如图中红框所示:

Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式_第10张图片

选中该功能后,可以对应用进行加锁/解锁,会进入下图所示的界面:

Android开发实战《手机安全卫士》——11.“进程管理”模块拓展 & 窗体小部件 & 生成快捷方式_第11张图片

该功能需要以下步骤:

  1. 创建已加锁应用的数据库(字段:_id,packagename),如果应用已经加锁,将加锁应用的包名维护到数据库中;
  2. 已加锁的应用 + 未加锁的应用 == 手机中所有应用(AppInfoDao);

在db包下新建AppLockOpenHelper,首先先创建一个记录加锁应用信息的数据库,代码如下:

package com.example.mobilesafe.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import androidx.annotation.Nullable;

public class AppLockOpenHelper extends SQLiteOpenHelper {

    public AppLockOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 创建数据库中表的方法
        db.execSQL("create table applock " +
                "(_id integer primary key autoincrement ," +
                " packagename varchar(50));");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

在dao下新建AppLockDao,作为对数据库中数据操作的工具类,代码如下:

package com.example.mobilesafe.dao;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.example.mobilesafe.db.AppLockOpenHelper;

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

public class AppLockDao {

    // 0.声明SQLite工具类
    private final AppLockOpenHelper mApplockOpenHelper;

    /**
     * 让AppLockDao实现单例模式(懒汉)
     * 1.私有化构造方法
     * 2.声明一个当前类的对象
     * 3.提供获取单例方法,如果当前类的对象为空,创建一个新的
      */

    /**
     * 1.私有化构造方法
     * @param context 上下文环境
     */
    private AppLockDao(Context context) {
        // 创建数据库及其表结构
        mApplockOpenHelper = new AppLockOpenHelper(context, "applock.db", null, 1);
    }

    // 2.声明一个当前类对象
    private static AppLockDao applockDao;

    /**
     * 3.提供获取单例方法
     * @param context 上下文环境
     * @return
     */
    public static AppLockDao getInstance(Context context){
        if (applockDao == null){
            applockDao = new AppLockDao(context);
        }
        return applockDao;
    }

    /**
     * 4.插入数据
     * @param packagename 待插入的应用包名
     */
    public void insert(String packagename){
        SQLiteDatabase db = mApplockOpenHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("packagename",packagename);
        db.insert("applock",null,values);
        db.close();
    }

    /**
     * 5.删除数据
     * @param packagename 待插入的应用包名
     */
    public void delete(String packagename){
        SQLiteDatabase db = mApplockOpenHelper.getWritableDatabase();
        db.delete("applock","packagename = ?",new String[]{packagename});
        db.close();
    }

    /**
     * 6.查询所有数据
     */
    public List<String> queryAll(){
        SQLiteDatabase db = mApplockOpenHelper.getWritableDatabase();
        Cursor cursor = db.query("applock", new String[]{"packagename"}, null, null, null, null, null);
        List<String> lockPackageList = new ArrayList<>();
        while (cursor.moveToNext()){
            lockPackageList.add(cursor.getString(0));
        }
        cursor.close();
        db.close();
        return lockPackageList;
    }
}

修改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"/>

    <TextView
        android:id="@+id/tv_commonnumber_query"
        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_app_lock"
        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>

修改AToolActivity,添加initAppLock(),作为跳转到程序锁界面的方法,代码如下:

    /**
     * 4.程序锁
     */
    private void initAppLock() {
        tv_app_lock = findViewById(R.id.tv_app_lock);
        tv_app_lock.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(getApplicationContext(),AppLockActivity.class));
            }
        });
    }

在activity包下新建AppLockActivity,首先修改其布局文件activity_app_lock.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.AppLockActivity"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center">

        <Button
            android:id="@+id/btn_unlock"
            android:text="未加锁"
            android:background="@drawable/tab_left_pressed"
            android:textColor="#fff"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

        <Button
            android:id="@+id/btn_lock"
            android:text="已加锁"
            android:background="@drawable/tab_right_default"
            android:textColor="#fff"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    LinearLayout>

    <LinearLayout
        android:id="@+id/ll_unlock"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tv_unlock"
            android:text="未加锁应用"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <ListView
            android:id="@+id/lv_unlock"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    LinearLayout>

    <LinearLayout
        android:id="@+id/ll_lock"
        android:orientation="vertical"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tv_lock"
            android:text="已加锁应用"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <ListView
            android:id="@+id/lv_lock"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    LinearLayout>


LinearLayout>

修改AppLockActivity,完善对应逻辑,区分已加锁和未加锁的数据,代码如下:

package com.example.mobilesafe.activity;

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

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

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

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

public class AppLockActivity extends AppCompatActivity {

    private Button btn_unlock;

    private Button btn_lock;

    private LinearLayout ll_unlock;

    private LinearLayout ll_lock;

    private TextView tv_unlock;

    private TextView tv_lock;

    private ListView lv_unlock;

    private ListView lv_lock;

    private List<AppInfo> mAppInfoList;

    private List<AppInfo> mLockList;

    private List<AppInfo> mUnLockList;

    private AppLockDao mDao;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            // 6.接收到消息,填充已加锁和未加锁的数据适配器中
        }
    };

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

        // 初始化UI
        initUI();

        // 初始化数据
        initData();
    }

    /**
     * 初始化UI
     */
    private void initUI() {
        btn_unlock = findViewById(R.id.btn_unlock);
        btn_lock = findViewById(R.id.btn_lock);

        ll_unlock = findViewById(R.id.ll_unlock);
        ll_lock = findViewById(R.id.ll_lock);

        tv_unlock = findViewById(R.id.tv_unlock);
        tv_lock = findViewById(R.id.tv_lock);

        lv_unlock = findViewById(R.id.lv_unlock);
        lv_lock = findViewById(R.id.lv_lock);
    }

    /**
     * 初始化数据,区分已加锁和未加锁应用的集合
     */
    private void initData() {
        new Thread(){
            @Override
            public void run() {
                // 1.获取所有手机中的应用
                mAppInfoList = AppInfoDao.getAppInfoList(getApplicationContext());
                // 2.区分已加锁应用和未加锁应用
                mLockList = new ArrayList<>();
                mUnLockList = new ArrayList<>();
                // 3.获取数据库中已加锁应用包名的集合
                mDao = AppLockDao.getInstance(getApplicationContext());
                List<String> lockPackageList = mDao.queryAll();
                for (AppInfo appInfo : mAppInfoList) {
                    // 4.如果循环到的应用包名在数据库中,则说明是已加锁应用
                    if (lockPackageList.contains(appInfo.getPackagename())){
                        mLockList.add(appInfo);
                    }else {
                        mUnLockList.add(appInfo);
                    }
                }
                // 5.告知主线程,可以使用维护的数据

            }
        }.start();
    }
}

9.高级工具——程序锁(逻辑实现)

上一节中我们实现了“高级工具”模块总程序锁的布局实现,这一节中主要来完善逻辑。

在res/layout下新增listview_islock_item.xml,作为填充列表控件的条目布局文件,代码如下:


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

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

    <TextView
        android:id="@+id/tv_name"
        android:text="应用名称"
        android:layout_toRightOf="@id/iv_icon"
        android:layout_centerVertical="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <ImageView
        android:id="@+id/iv_lock"
        android:layout_alignParentRight="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

RelativeLayout>

修改AppLockActivity,完善数据适配器填充等相关逻辑,代码如下:

package com.example.mobilesafe.activity;

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

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

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

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

public class AppLockActivity extends AppCompatActivity {

    private Button btn_unlock;

    private Button btn_lock;

    private LinearLayout ll_unlock;

    private LinearLayout ll_lock;

    private TextView tv_unlock;

    private TextView tv_lock;

    private ListView lv_unlock;

    private ListView lv_lock;

    private List<AppInfo> mAppInfoList;

    private List<AppInfo> mLockList;

    private List<AppInfo> mUnLockList;

    private AppLockDao mDao;

    private MyAdapter mLockAdapter;

    private MyAdapter mUnLockAdapter;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            // 6.接收到消息,填充已加锁和未加锁的数据适配器中
            mLockAdapter = new MyAdapter(true);
            lv_lock.setAdapter(mLockAdapter);

            mUnLockAdapter = new MyAdapter(false);
            lv_unlock.setAdapter(mUnLockAdapter);
        }
    };


    class MyAdapter extends BaseAdapter{

        private boolean isLock;

        /**
         * 用于区分已加锁和未加锁应用的标识
         * @param isLock 用于区分已加锁和未加锁应用的标示,true是已加锁,false是未加锁
         */
        public MyAdapter(boolean isLock) {
            this.isLock = isLock;
        }

        @Override
        public int getCount() {
            if (isLock){
                tv_lock.setText(mLockList.size() + "");
                return mLockList.size();
            }else {
                tv_unlock.setText(mUnLockList.size() + "");
                return mUnLockList.size();
            }
        }

        @Override
        public AppInfo getItem(int position) {
            if (isLock){
                return mLockList.get(position);
            }else {
                return mUnLockList.get(position);
            }
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if (convertView == null){
                convertView = View.inflate(getApplicationContext(), R.layout.listview_islock_item, null);
                holder = new ViewHolder();
                holder.iv_icon = convertView.findViewById(R.id.iv_icon);
                holder.tv_name = convertView.findViewById(R.id.tv_name);
                holder.iv_lock = convertView.findViewById(R.id.iv_lock);
                convertView.setTag(holder);
            }else {
                holder = (ViewHolder) convertView.getTag();
            }
            AppInfo appInfo = getItem(position);
            holder.iv_icon.setBackgroundDrawable(appInfo.getIcon());
            holder.tv_name.setText(appInfo.getName());
            if(isLock){
                holder.iv_lock.setBackgroundResource(R.drawable.lock);
            }else {
                holder.iv_lock.setBackgroundResource(R.drawable.unlock);
            }
            return convertView;
        }
    }

    static class ViewHolder{
        ImageView iv_icon;
        TextView tv_name;
        ImageView iv_lock;
    }

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

        // 初始化UI
        initUI();

        // 初始化数据
        initData();
    }

    /**
     * 初始化UI
     */
    private void initUI() {
        btn_unlock = findViewById(R.id.btn_unlock);
        btn_lock = findViewById(R.id.btn_lock);

        ll_unlock = findViewById(R.id.ll_unlock);
        ll_lock = findViewById(R.id.ll_lock);

        tv_unlock = findViewById(R.id.tv_unlock);
        tv_lock = findViewById(R.id.tv_lock);

        lv_unlock = findViewById(R.id.lv_unlock);
        lv_lock = findViewById(R.id.lv_lock);
    }

    /**
     * 初始化数据,区分已加锁和未加锁应用的集合
     */
    private void initData() {
        new Thread(){
            @Override
            public void run() {
                // 1.获取所有手机中的应用
                mAppInfoList = AppInfoDao.getAppInfoList(getApplicationContext());
                // 2.区分已加锁应用和未加锁应用
                mLockList = new ArrayList<>();
                mUnLockList = new ArrayList<>();
                // 3.获取数据库中已加锁应用包名的集合
                mDao = AppLockDao.getInstance(getApplicationContext());
                List<String> lockPackageList = mDao.queryAll();
                for (AppInfo appInfo : mAppInfoList) {
                    // 4.如果循环到的应用包名在数据库中,则说明是已加锁应用
                    if (lockPackageList.contains(appInfo.getPackagename())){
                        mLockList.add(appInfo);
                    }else {
                        mUnLockList.add(appInfo);
                    }
                }
                // 5.告知主线程,可以使用维护的数据
                mHandler.sendEmptyMessage(0);
            }
        }.start();
    }
}

接着修改AppLockActivity,修改initUI()方法,为按钮注册相应的点击事件,即切换显示未加锁/已加锁列表,代码如下:

    /**
     * 初始化UI
     */
    private void initUI() {
        btn_unlock = findViewById(R.id.btn_unlock);
        btn_lock = findViewById(R.id.btn_lock);

        ll_unlock = findViewById(R.id.ll_unlock);
        ll_lock = findViewById(R.id.ll_lock);

        tv_unlock = findViewById(R.id.tv_unlock);
        tv_lock = findViewById(R.id.tv_lock);

        lv_unlock = findViewById(R.id.lv_unlock);
        lv_lock = findViewById(R.id.lv_lock);

        btn_unlock.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 1.已加锁列表隐藏,未加锁列表显示
                ll_lock.setVisibility(View.GONE);
                ll_unlock.setVisibility(View.VISIBLE);
                // 2.未加锁变成浅色图片,已加锁变成深色图片
                btn_unlock.setBackgroundResource(R.drawable.tab_left_pressed);
                btn_lock.setBackgroundResource(R.drawable.tab_right_default);
            }
        });

        btn_lock.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 1.已加锁列表显示,未加锁列表隐藏
                ll_lock.setVisibility(View.VISIBLE);
                ll_unlock.setVisibility(View.GONE);
                // 2.未加锁变成浅色图片,已加锁变成深色图片
                btn_unlock.setBackgroundResource(R.drawable.tab_left_default);
                btn_lock.setBackgroundResource(R.drawable.tab_right_pressed);
            }
        });
    }

你可能感兴趣的:(Android,android,手机卫士)