黑马程序员技术交流社区

标题: 【济南校区】Android课程同步笔记day10:Android应用之安全卫士 [打印本页]

作者: 小鲁哥哥    时间: 2017-2-8 10:48
标题: 【济南校区】Android课程同步笔记day10:Android应用之安全卫士
本帖最后由 小鲁哥哥 于 2017-4-12 14:32 编辑

Android课程同步笔记day10:Android应用之安全卫士

进程管理
在进程管理的业务类中创建一个获取所有当前正在运行的进程的方法;由于一个进程可能对应于多个应用程序;而一个应用程序可能对应于多个进程;所以在获取数据这块的逻辑稍微复杂点;核心的思想点就是先以应用程序为单位获取到当前应用程序下的所有进程;然后再将每个应用程序下的所有list进程加入到一个总的list集合中;代码如下:
[Java] 纯文本查看 复制代码
/**
         * 获取进程管理需要所有的进程
         *
         * @param context
         * @return
         */
        public static List<ProcessBean> getProcesses(Context context) {
                List<ProcessBean> datas = new ArrayList<ProcessBean>();
                // 用来记录描述一个应用对应多个进程
                Map<String, List<ProcessBean>> map = new HashMap<String, List<ProcessBean>>();
                // 获得运行的进程
                ActivityManager am = (ActivityManager) context
                                .getSystemService(Context.ACTIVITY_SERVICE);
                List<RunningAppProcessInfo> processes = am.getRunningAppProcesses();
                // 遍历当前所有进程
                for (RunningAppProcessInfo info : processes) {
                        // 进程名称
                        String processName = info.processName;
                        // 进程占用的内存大小
                        int pid = info.pid;
                        MemoryInfo memoryInfo = am.getProcessMemoryInfo(new int[] { pid })[0];
                        long memory = memoryInfo.getTotalPss() * 1024;
                        // 进程对应的应用,一个进程可以对应多个应用
                        String[] pkgList = info.pkgList;
                        for (String pkgName : pkgList) {
                                ProcessBean bean = new ProcessBean();
                                bean.processName = processName;
                                bean.memory = memory;
                                // bean.pkg = null;
                                bean.pid = pid;

                                List<ProcessBean> list = map.get(pkgName);
                                if (list == null) {
                                        // 没有进程信息
                                        list = new ArrayList<ProcessBean>();
                                        map.put(pkgName, list);
                                }
                                list.add(bean);
                        }
                }
               
                PackageManager pm = context.getPackageManager();
                // 循环数据
                for (Map.Entry<String, List<ProcessBean>> me : map.entrySet()) {
                        String packageName = me.getKey();
                        List<ProcessBean> list = me.getValue();
                        try {
                                PackageInfo info = pm.getPackageInfo(packageName, 0);
                                PkgBean pkg = new PkgBean();
                                pkg.icon = PackageUtils.getAppIcon(context, info);
                                pkg.name = PackageUtils.getAppName(context, info);
                                pkg.packageName = packageName;

                                for (ProcessBean bean : list) {
                                        bean.pkg = pkg;
                                }
                                datas.addAll(list);
                        } catch (Exception e) {
                                e.printStackTrace();
                        }
                }
                return datas;
        }
数据的展示
有了数据,接下来就可以通过数据适配器将数据和listview结合到一起进行展示了;这里贴出核心代码:
首先是数据的获取:
[Java] 纯文本查看 复制代码
                mLoadingView.setVisibility(View.VISIBLE);
                new Thread(new Runnable() {

                        @Override
                        public void run() {
                                try {
                                        Thread.sleep(1500);
                                } catch (InterruptedException e) {
                                        e.printStackTrace();
                                }

                                mDatas = ProcessProvider
                                                .getProcesses(ProcessManagerActivity.this);
               
                                runOnUiThread(new Runnable() {

                                        @Override
                                        public void run() {
                                                mLoadingView.setVisibility(View.GONE);
                                                // 给listview设置adapter
                                                mAdapter = new ProcessAdapter();
                                                mListView.setAdapter(mAdapter);
                                        }
                                });

                        }
                }).start();

        }
数据适配器ProcessAdapter:
[Java] 纯文本查看 复制代码
private class ProcessAdapter extends BaseAdapter implements
                        StickyListHeadersAdapter {

                @Override
                public int getCount() {
                        if (mDatas != null) {
                                return mDatas.size();
                        }
                        return 0;
                }

                @Override
                public Object getItem(int position) {
                        if (mDatas != null) {
                                return mDatas.get(position);
                        }
                        return null;
                }

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

                @Override
                public View getView(int position, View convertView, ViewGroup parent) {
                        ItemViewHolder holder = null;
                        if (convertView == null) {
                                // 没有复用
                                convertView = View.inflate(ProcessManagerActivity.this,
                                                R.layout.item_process, null);
                                holder = new ItemViewHolder();
                                convertView.setTag(holder);
                                holder.tvProcessName = (TextView) convertView
                                                .findViewById(R.id.item_process_tv_name);
                                holder.tvProcessSize = (TextView) convertView
                                                .findViewById(R.id.item_process_tv_size);
                        } else {
                                holder = (ItemViewHolder) convertView.getTag();
                        }

                        // 数据设置
                        ProcessBean bean = mDatas.get(position);
                        holder.tvProcessName.setText(bean.processName);
                        holder.tvProcessSize.setText("占用:"
                                        + Formatter.formatFileSize(ProcessManagerActivity.this,
                                                        bean.memory));

                        return convertView;
                }

                @Override
                public View getHeaderView(int position, View convertView,
                                ViewGroup parent) {
                        HeaderViewHolder holder = null;
                        if (convertView == null) {
                                // 没有复用
                                convertView = View.inflate(ProcessManagerActivity.this,
                                                R.layout.item_process_header, null);
                                holder = new HeaderViewHolder();
                                convertView.setTag(holder);
                                holder.ivIcon = (ImageView) convertView
                                                .findViewById(R.id.item_ph_iv_icon);
                                holder.tvName = (TextView) convertView
                                                .findViewById(R.id.item_ph_tv_name);
                                holder.cbChecked = (CheckBox) convertView
                                                .findViewById(R.id.item_ph_cb_checked);
                        } else {
                                holder = (HeaderViewHolder) convertView.getTag();
                        }

                        // 数据设置
                        ProcessBean processBean = mDatas.get(position);
                        PkgBean pkg = processBean.pkg;

                        if (pkg.icon == null) {
                                holder.ivIcon.setImageResource(R.drawable.ic_default);
                        } else {
                                holder.ivIcon.setImageDrawable(pkg.icon);
                        }
                        holder.tvName.setText(pkg.name);

                        // 设置选择框选中
                        holder.cbChecked.setChecked(pkg.isChecked);

                        // 是否显示checkbox,判断是否是当前的应用
                        if (pkg.packageName.equals(getPackageName())) {
                                // 说明是当前的应用
                                holder.cbChecked.setVisibility(View.GONE);
                        } else {
                                holder.cbChecked.setVisibility(View.VISIBLE);
                        }

                        return convertView;
                }

                @Override
                public long getHeaderId(int position) {
                        ProcessBean bean = mDatas.get(position);
                        PkgBean pkg = bean.pkg;// 表示唯一的应用程序
                        // pkg.packageName --> hashcode
                        return pkg.hashCode();
                }
        }

        private static class HeaderViewHolder {
                ImageView ivIcon;
                TextView tvName;
                CheckBox cbChecked;
        }

        private static class ItemViewHolder {
                TextView tvProcessName;
                TextView tvProcessSize;
        }
数据排序和ui微调
将所有的list数据根据首字母来进行一个排序;由于有些进程的名字可能涉及到中文;所以这里引入一个汉字转化拼音的jar和工具类进来:
在PkgBean中添加一个字段用来记录当前应用的名字首字母:
[Java] 纯文本查看 复制代码
public String firstLetter ;//程序名称的首字母
并且在业务逻辑类ProcessProvider中获取到首字母:
在ProcessManagerActivity中获取数据的时候根据首字母进行排序:
[Java] 纯文本查看 复制代码
                mLoadingView.setVisibility(View.VISIBLE);
                new Thread(new Runnable() {

                        @Override
                        public void run() {
                                try {
                                        Thread.sleep(1500);
                                } catch (InterruptedException e) {
                                        e.printStackTrace();
                                }

                                mDatas = ProcessProvider
                                                .getProcesses(ProcessManagerActivity.this);
                                // 进行排序,根据应用的名称
                                Collections.sort(mDatas, new Comparator<ProcessBean>() {

                                        @Override
                                        public int compare(ProcessBean lhs, ProcessBean rhs) {
                                                String lFirstLetter = lhs.pkg.firstLetter;
                                                String rFirstLetter = rhs.pkg.firstLetter;
                                                return lFirstLetter.compareToIgnoreCase(rFirstLetter);
                                        }
                                });

                                // 初始化记录所有应用程序
                                mPkgs = new HashSet<PkgBean>();
                                for (ProcessBean bean : mDatas) {
                                        PkgBean pkg = bean.pkg;
                                        mPkgs.add(pkg);
                                }

                                runOnUiThread(new Runnable() {

                                        @Override
                                        public void run() {
                                                mLoadingView.setVisibility(View.GONE);
                                                // 给listview设置adapter
                                                mAdapter = new ProcessAdapter();
                                                mListView.setAdapter(mAdapter);
                                        }
                                });

                        }
                }).start();

        }
最后运行的效果如下:
UI的微调:
由于listview条目上有CheckBox这样的抢焦点控件;会导致item不可点击;所以这里需要将该控件进行如下处理:
[XML] 纯文本查看 复制代码
<CheckBox
            android:clickable="false"
            android:focusable="false"
            android:id="@+id/item_ph_cb_checked"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true" />
以及为了让ListView更好看一点;每一个条目之间有分割线进行展示;可以让item的header布局添加一个view:
自定义Toast实现字母显示
为了当我们滑动的过程中可以明确知道当前滑动到哪个字母开头的应用程序了;可以弹出一个自定义的toast来进行提示用户;效果如下:
自定吐司在前面我们已经实现过一次;所以这次可以快速的复制拷贝出来一个上图中样式的吐司;创建一个类LetterToast;代码如下
[Java] 纯文本查看 复制代码
public class LetterToast {
        private WindowManager mWM;
        private View mView;
        private TextView mTvLetter;
        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();

        private String mLastLetter = "";
        private Handler mHandler = new Handler();

        private Runnable mHiddenTask = new Runnable() {
                @Override
                public void run() {
                        if (mView.getParent() != null) {
                                mWM.removeView(mView);
                        }
                }
        };

        public LetterToast(Context context) {
                // 初始化WindowManager
                mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

                // 加载自定义Toast的样式
                mView = View.inflate(context, R.layout.toast_letter, null);
                mTvLetter = (TextView) mView.findViewById(R.id.toast_tv_letter);

                // param初始化
                final WindowManager.LayoutParams params = mParams;
                params.height = WindowManager.LayoutParams.WRAP_CONTENT;
                params.width = WindowManager.LayoutParams.WRAP_CONTENT;
                params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
                params.format = PixelFormat.TRANSLUCENT;
                // params.windowAnimations =
                // com.android.internal.R.style.Animation_Toast;
                params.type = WindowManager.LayoutParams.TYPE_TOAST;// 不可以被触摸的
                // params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;//
                // 可以被触摸的,可以显示在打电话界面上方的
                params.setTitle("Toast");
        }

        public void show(String letter) {

                if (mLastLetter.equals(letter)) {
                        return;
                }

                // 显示 正在显示时,有可能出现移除显示,需要取消掉上一次自动关闭操作
                mTvLetter.setText(letter);
                if (mView.getParent() == null) {
                        mWM.addView(mView, mParams);
                }

                // 取消操作
                mHandler.removeCallbacks(mHiddenTask);

                // 显示后得自动延时关闭
                mHandler.postDelayed(mHiddenTask, 1500);
        }
}
在显示吐司的过程中显示1.5S后记得关闭当前吐司;以及重复的字母不需要再显示。
在ProcessManagerActivity中初始化该Toast,并设置一个scroll滚动的监听:
条目全选反选的实现前面我们已经手机中的所有进程显示出来,下面我们要开始进行进程的清理操作;界面下方有两个按钮:全选和反选,右上角有一个杀死进程的按钮;以及listview每一个item条目的点击这些事件需要全部进行处理:
[Java] 纯文本查看 复制代码
private void initEvent() {
                mListView.setOnScrollListener(this);
                // 设置 item条目的点击
                mListView.setOnHeaderClickListener(this);
                // button
                mBtnAll.setOnClickListener(this);
                mBtnReverse.setOnClickListener(this);
                // 清理
                mIvClean.setOnClickListener(this);
        }

        @Override
        public void onClick(View v) {
                if (v == mBtnAll) {
                        clickAll();
                } else if (v == mBtnReverse) {
                        clickReverse();
                } else if (v == mIvClean) {
                        clickClean();
                }
        }

首先为了实现应用程序的勾选操作我们需要用一个集合将当前所有的应用程序装载起来,在loadListview()中数据排序后可以实现:

接下来实现布局中全选按钮的点击事件,同时需要注意,避免将当前的黑马手机卫士进程选中,我们需要进行判断,具体的代码如下所示:
[Java] 纯文本查看 复制代码
/**全选*/
        private void clickAll() {
                if (mDatas == null || mDatas.size() == 0) {
                        return;
                }
                // 将所有的checkbox选中
                // 找到所有的pkgbean,设置数据ischecked--》true
                for (PkgBean bean : mPkgs) {
                        // 如果是当前的应用,就不让选中
                        if (bean.packageName.equals(getPackageName())) {
                                continue;
                        }
                        bean.isChecked = true;
                }
                // UI更新
                mAdapter.notifyDataSetChanged();
        }

接着,实现反选按钮的点击事件,具体的代码如下所示:
[Java] 纯文本查看 复制代码
/**反选*/
        private void clickReverse() {
                if (mDatas == null || mDatas.size() == 0) {
                        return;
                }
                // 如果是已经选中,就不选中,否则相反
                for (PkgBean bean : mPkgs) {
                        if (bean.packageName.equals(getPackageName())) {
                                continue;
                        }
                        bean.isChecked = !bean.isChecked;
                }
                // UI更新
                mAdapter.notifyDataSetChanged();
        }

全选和反选操作完毕后都调用了mAdapter.notifyDataSetChanged()来刷新界面;所以在adapter的getViewHeader中需要判断当前pkgbean的checked状态来是否勾选以及是否是当前应用程序来进行显示和隐藏:

清理按钮的实现
接下来,我们需要实现清理按钮的点击事件,想要杀死一个应用程序肯定需要使用ActivityManager系统服务来操作;这里先在ProcessProvider中添加好一个静态的方法:
[Java] 纯文本查看 复制代码
/**
         * 杀死应用的进程
         *
         * @param context
         * @param packageName
         */
        public static void killProcess(Context context, String packageName) {
                ActivityManager am = (ActivityManager) context
                                .getSystemService(Context.ACTIVITY_SERVICE);

                am.killBackgroundProcesses(packageName);
        }

那么清理按钮的点击事件中的操作如下:
[AppleScript] 纯文本查看 复制代码
/**杀死选中的应用进程*/
        private void clickClean() {
                if (mDatas == null || mDatas.size() == 0) {
                        return;
                }
                int killCount = 0;
                long releaseMemory = 0;
                Map<Integer, Long> map = new HashMap<Integer, Long>();
                Iterator<PkgBean> iterator = mPkgs.iterator();
                while (iterator.hasNext()) {
                        PkgBean pkg = iterator.next();
                        // 找到选中的应用
                        if (pkg.isChecked) {
                                // 杀死应用
                                ProcessProvider.killProcess(this, pkg.packageName);
                                // 内存移除
                                iterator.remove();
                                ListIterator<ProcessBean> listIterator = mDatas.listIterator();
                                while (listIterator.hasNext()) {
                                        ProcessBean bean = listIterator.next();
                                        if (bean.pkg.packageName.equals(pkg.packageName)) {
                                                // 移除
                                                listIterator.remove();
                                                // 获得进程的内存
                                                long memory = bean.memory;
                                                map.put(bean.pid, memory);
                                        }
                                }
                        }
                }
                // 计算杀死的个数和释放的内存
                for (Map.Entry<Integer, Long> me : map.entrySet()) {
                        releaseMemory += me.getValue();
                }
                killCount = map.size();
                // UI更新
                mAdapter.notifyDataSetChanged();

                if (killCount == 0) {
                        Toast.makeText(this, "没有可杀死的进程", Toast.LENGTH_SHORT).show();
                } else {
                        String text = "杀死了" + killCount + "个进程,释放了"
                                        + Formatter.formatFileSize(this, releaseMemory) + "内存";
                        Toast.makeText(this, text, Toast.LENGTH_SHORT).show();

                        // 顶部ui改变
                        mRunningProcessCount -= killCount;
                        loadProcess();

                        mUsedMemory -= releaseMemory;
                        loadMemory();
                }
        }

在上面代码清理应用进程的过程中也需要统计当前杀死了多少个进程以及多少内存,界面上方的数据需要更新,以及通过吐司提示用户。
另外,需要注意的是,清理手机进程需要添加一个权限,如下所示。
[XML] 纯文本查看 复制代码
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
锁屏自动清理
当设置界面中的锁屏清理进程按钮开启时,就会打开进程清理的服务,在该服务中注册了监听屏幕锁屏的广播接收者,当屏幕锁屏时该广播接收到屏幕锁屏的消息后会自动清理进程。
首先在设置界面添加一个锁屏自动清理的条目:

布局以及控件的初始化操作都跟归属地显示设置一样,这里就不再贴代码了;主要是开启按钮的时候需要开启的服务AutoCleanService,在服务中动态注册广播监听锁屏的操作:
[Java] 纯文本查看 复制代码
public class AutoCleanService extends Service {
        private static final String TAG = "AutoCleanService";

        private ScreenOffReceiver mReceiver;

        @Override
        public IBinder onBind(Intent intent) {
                return null;
        }

        @Override
        public void onCreate() {
                super.onCreate();
                Log.d(TAG, "自动清理服务开启");

                // 锁屏时自动清理服务
                // 监听锁屏

                // 注册广播
                mReceiver = new ScreenOffReceiver();
                IntentFilter filter = new IntentFilter();
                filter.addAction(Intent.ACTION_SCREEN_OFF);// 锁屏的行为
                filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
                registerReceiver(mReceiver, filter);
        }

        @Override
        public void onDestroy() {
                super.onDestroy();
                Log.d(TAG, "自动清理服务关闭");

                // 注销
                unregisterReceiver(mReceiver);
        }

        private class ScreenOffReceiver extends BroadcastReceiver {

                @Override
                public void onReceive(Context context, Intent intent) {
                        // 接受到锁屏
                        // 杀死进程
                        List<ProcessBean> processes = ProcessProvider.getProcesses(context);
                        for (ProcessBean bean : processes) {
                                if (bean.pkg.packageName.equals(context.getPackageName())) {
                                        // 不杀死自己
                                        continue;
                                }
                                ProcessProvider.killProcess(context, bean.pkg.packageName);
                        }
                }
        }
}

不被杀死的前台服务
为了让我们的应用做的更加完善更加健壮,以至于不被其他一些杀毒软件在清理进程的时候给杀死;可以让我们的应用做成一个前台进程;即开启一个前台的服务在后台一直运行。
创建一个服务ProtectingService
[Java] 纯文本查看 复制代码
public class ProtectingService extends Service {
        private int id = 100;

        @Override
        public IBinder onBind(Intent intent) {
                // TODO Auto-generated method stub
                return null;
        }

        @Override
        public void onCreate() {
                super.onCreate();

                // 不被杀死的服务
                // 1.新建Notification
                NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
                                this).setSmallIcon(R.drawable.ic_launcher)
                                .setContentTitle("My notification")
                                .setContentText("Hello World!");
                // Creates an explicit intent for an Activity in your app
                Intent resultIntent = new Intent(this, SplashActivity.class);

                // The stack builder object will contain an artificial back stack for
                // the
                // started Activity.
                // This ensures that navigating backward from the Activity leads out of
                // your application to the Home screen.
                TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
                // Adds the back stack for the Intent (but not the Intent itself)
                stackBuilder.addParentStack(SplashActivity.class);
                // Adds the Intent that starts the Activity to the top of the stack
                stackBuilder.addNextIntent(resultIntent);
                PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
                                PendingIntent.FLAG_UPDATE_CURRENT);
                mBuilder.setContentIntent(resultPendingIntent);
                NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
                // mId allows you to update the notification later on.

                Notification notification = mBuilder.build();
                mNotificationManager.notify(id, notification);
                // 2.注册成前台进程
                startForeground(id, notification);
        }

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

}

在splashActivity的oncreate中开启:
[Java] 纯文本查看 复制代码
if(!ServiceStateUtils.isServiceRunning(this,ProtectingService.class)){
        startService(new Intent(this,ProtectingService.class));
}

运行程序后发现手机上方的title栏有一个我们自己应用程序的notification通知在显示即表示我们的进程已被做成一个前台进程了




作者: 雪飞舞舞    时间: 2017-3-11 22:47
学习了

作者: baby14    时间: 2019-7-22 08:10
多谢分享




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2