A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 小鲁哥哥 于 2017-4-12 14:34 编辑

【济南中心】Android课程同步笔记day13:Android应用之安全卫士

程序真实数据的获取:
获取已安装的应用程序已经在之前的AppProvider中实现过了,可以直接拿来使用,不过这里需要注意的是,我们程序锁加锁的应用必须是可以启动的起来的应用,所以最好是重构一个方法,进行一下是否可以启动的判断;在AppProvider中添加一个getLaunchApps的方法:
[Java] 纯文本查看 复制代码
public class AppProvider {
        private static final String TAG = "AppProvider";
        /**
         * 获取所有已安装的应用程序
         * @param context
         * @return
         */
        public static List<AppInfo> getAllApps(Context context) {
                List<AppInfo> list = new ArrayList<AppInfo>();
                PackageManager pm = context.getPackageManager();
                List<PackageInfo> packages = pm.getInstalledPackages(0);
                // 迭代所有的安装包
                for (PackageInfo info : packages) {
                        // 获取到应用的包名
                        String name = PackageUtils.getAppName(context, info);
                        //获取到应用的icon
                        Drawable icon = PackageUtils.getAppIcon(context, info);

                        boolean isInstallSD = PackageUtils.isInstallSD(info);

                        AppInfo bean = new AppInfo();
                        bean.name = name;
                        bean.icon = icon;
                        bean.space = PackageUtils.getAppSpace(info);
                        bean.isInstallSD = isInstallSD;
                        bean.isSystem = PackageUtils.isSystemApp(info);
                        Log.d(TAG, name + " : " + bean.isSystem);
                        bean.packageName = info.packageName;

                        list.add(bean);
                }
                return list;
        }
        
        /**
         * 获取所有已安装并且可以启动的应用程序
         * @param context
         * @return
         */
        public static List<AppInfo> getAllLauchApps(Context context) {
                List<AppInfo> list = new ArrayList<AppInfo>();
                PackageManager pm = context.getPackageManager();
                List<PackageInfo> packages = pm.getInstalledPackages(0);
                
                // 迭代所有的安装包
                for (PackageInfo info : packages) {
                        //如果不具备启动条件则继续
                        Intent intent = pm.getLaunchIntentForPackage(info.packageName);
                        if(null==intent){
                                continue;
                        }
                        // 获取到应用的包名
                        String name = PackageUtils.getAppName(context, info);
                        //获取到应用的icon
                        Drawable icon = PackageUtils.getAppIcon(context, info);

                        boolean isInstallSD = PackageUtils.isInstallSD(info);

                        AppInfo bean = new AppInfo();
                        bean.name = name;
                        bean.icon = icon;
                        bean.space = PackageUtils.getAppSpace(info);
                        bean.isInstallSD = isInstallSD;
                        bean.isSystem = PackageUtils.isSystemApp(info);
                        Log.d(TAG, name + " : " + bean.isSystem);
                        bean.packageName = info.packageName;

                        list.add(bean);
                }
                return list;
        }
}
AppLockActivity中将之前的假数据代码替换掉为真实的获取数据代码;
这里给出initData的代码:
[Java] 纯文本查看 复制代码
private void initData() {
                // 加载list数据 --假数据展示
                // mLockDatas = new ArrayList<AppInfo>();
                // for (int i = 0; i < 30; i++) {
                //
                // AppInfo bean = new AppInfo();
                // bean.name = "加锁的程序-" + i;
                //
                // mLockDatas.add(bean);
                // }
                //
                // mUnlockDatas = new ArrayList<AppInfo>();
                // for (int i = 0; i < 30; i++) {
                //
                // AppInfo bean = new AppInfo();
                // bean.name = "未加锁的程序-" + i;
                //
                // mUnlockDatas.add(bean);
                // }
                
                // 真实数据加载
                mLockDatas = new ArrayList<AppInfo>();
                mUnlockDatas = new ArrayList<AppInfo>();
                List<AppInfo> allLauchApps = AppProvider.getAllLauchApps(this);
                for (AppInfo bean : allLauchApps) {
                        String packageName = bean.packageName;
                        if (mDao.findLock(packageName)) {
                                // 加锁
                                mLockDatas.add(bean);
                        } else {
                                // 没有加锁
                                mUnlockDatas.add(bean);
                        }
                }
                // 给未加锁的设置adapter
                mUnlockAdapter = new ApplockAdapter(false);
                mUnLockListView.setAdapter(mUnlockAdapter);
                // 给已加锁的设置adapter
                mLockAdapter = new ApplockAdapter(true);
                mLockListView.setAdapter(mLockAdapter);
        }
ListView的item点击事件
接下来对于加锁和未加锁的listview进行点击item中icon的点击事件处理,主要在adapter中的getVIew方法中分别对当前item进行判断如果是加锁则进行加锁的点击事件处理,如果是未加锁则对未加锁的点击事件处理。
注意:
       在条目移除的过程中添加一个平移的动画。动画执行完成后进行数据库的操作以及界面的更新
[Java] 纯文本查看 复制代码
@Override
                public View getView(int position, View convertView, ViewGroup parent) {
                        ViewHolder holder = null;
                        if (convertView == null) {
                                // 没有复用
                                convertView = View.inflate(AppLockActivity.this,
                                                R.layout.item_app_lock, null);
                                holder = new ViewHolder();
                                convertView.setTag(holder);
                                holder.ivIcon = (ImageView) convertView
                                                .findViewById(R.id.item_al_iv_icon);
                                holder.ivLock = (ImageView) convertView
                                                .findViewById(R.id.item_al_iv_lock);
                                holder.tvName = (TextView) convertView
                                                .findViewById(R.id.item_al_tv_name);
                        } else {
                                // 有复用
                                holder = (ViewHolder) convertView.getTag();
                        }

                        // 设置数据
                        AppInfo bean = null;
                        if (mLocked) {
                                bean = mLockDatas.get(position);
                        } else {
                                bean = mUnlockDatas.get(position);
                        }

                        if (bean.icon == null) {
                                holder.ivIcon.setImageResource(R.drawable.ic_default);
                        } else {
                                holder.ivIcon.setImageDrawable(bean.icon);
                        }
                        holder.tvName.setText(bean.name);
                        
                        final AppInfo app = bean;
                        final View contentView = convertView;
                        if (mLocked) {
                                holder.ivLock.setImageResource(R.drawable.btn_unlock_selector);
                                // 设置解锁点击点击事件
                                holder.ivLock.setOnClickListener(new OnClickListener() {
                                        @Override
                                        public void onClick(View v) {
                                                clickRemoveLock(app, contentView);
                                        }
                                });
                        } else {
                                holder.ivLock.setImageResource(R.drawable.btn_lock_selector);

                                // 设置加锁点击点击事件
                                holder.ivLock.setOnClickListener(new OnClickListener() {
                                        @Override
                                        public void onClick(View v) {
                                                clickAddLock(app, contentView);
                                        }
                                });
                        }
                        return convertView;
                }

private void clickAddLock(final AppInfo app, final View contentView) {
                // 当前全局中有动画
                if (isAnimation) {
                        return;
                }
                // 动画的移动整个条目
                TranslateAnimation animation = new TranslateAnimation(
                                Animation.RELATIVE_TO_PARENT, 0, Animation.RELATIVE_TO_PARENT,
                                1, Animation.RELATIVE_TO_PARENT, 0,
                                Animation.RELATIVE_TO_PARENT, 0);
                animation.setDuration(300);
                animation.setAnimationListener(new AnimationListener() {
                        @Override
                        public void onAnimationStart(Animation animation) {
                                isAnimation = true;
                        }
                        @Override
                        public void onAnimationRepeat(Animation animation) {
                        }
                        @Override
                        public void onAnimationEnd(Animation animation) {
                                isAnimation = false;
                                // 将条目添加到数据库
                                String packageName = app.packageName;
                                boolean add = mDao.add(packageName);
                                if (add) {
                                        // 移除数据从 未加锁的数据集合中
                                        mUnlockDatas.remove(app);
                                        // 添加数据到 加锁的集合中
                                        mLockDatas.add(app);

                                        // UI更新
                                        mUnlockAdapter.notifyDataSetChanged();
                                }
                        }
                });
                contentView.startAnimation(animation);
        }

        private void clickRemoveLock(final AppInfo app, final View contentView) {
                // 当前全局中有动画
                if (isAnimation) {
                        return;
                }
                // 动画的移动整个条目
                TranslateAnimation animation = new TranslateAnimation(
                                Animation.RELATIVE_TO_PARENT, 0, Animation.RELATIVE_TO_PARENT,
                                -1, Animation.RELATIVE_TO_PARENT, 0,
                                Animation.RELATIVE_TO_PARENT, 0);
                animation.setDuration(300);
                animation.setAnimationListener(new AnimationListener() {
                        @Override
                        public void onAnimationStart(Animation animation) {
                                isAnimation = true;
                        }
                        @Override
                        public void onAnimationRepeat(Animation animation) {
                        }
                        @Override
                        public void onAnimationEnd(Animation animation) {
                                isAnimation = false;
                                // 将条目从数据库中移除
                                String packageName = app.packageName;
                                boolean delete = mDao.delete(packageName);
                                if (delete) {
                                        // 从 已加锁的集合中移除
                                        mLockDatas.remove(app);
                                        // 添加到未加锁的集合中
                                        mUnlockDatas.add(app);

                                        // UI更新
                                        mLockAdapter.notifyDataSetChanged();
                                }
                        }
                });
                contentView.startAnimation(animation);
        }

AccesibilityService
Accessibility:
许多Android使用者因为各种情况导致他们要以不同的方式与手机交互。
这包括了有些用户由于视力上,身体上,年龄上的问题致使他们不能看完整的屏幕或者使用触屏,也包括了无法很好接收到语音信息和提示的听力能力比较弱的用户。
Android提供了Accessibility功能和服务帮助这些用户更加简单地操作设备,包括文字转语音(这个不支持中文),触觉反馈,手势操作,轨迹球和手柄操作。
开发者可以利用这些服务使得程序更好用。
开发者可以搭建自己的Accessibility服务,这可以加强可用性,例如声音提示,物理反馈,和其他可选的操作模式。
Accessibility服务可以为所有的应用程序,一组应用程序或单个应用程序提供这些增强功能。
AccessibilityService使用很简单:
1. 新建一个类继承AccessibilityService,并在AndroidManifest文件里注册它:
[XML] 纯文本查看 复制代码
<service    android:name="com.itheima.zphuanlove.service.AppLockService"
android:label="@string/accessibility_service_label"      android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" >
      <intent-filter>
           <action android:name="android.accessibilityservice.AccessibilityService" />
      </intent-filter>
</service>
注册权限:
[XML] 纯文本查看 复制代码
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
2.在<service节点中添加meta-data配置:
[XML] 纯文本查看 复制代码
<meta-data
    android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
其中的xml文件accessibility_service_config文件如下:
[XML] 纯文本查看 复制代码
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault"
    android:description="@string/accessibility_service_description" />
3.创建服务AppLockService继承AccessibilityService
[Java] 纯文本查看 复制代码
public class AppLockService extends AccessibilityService {
        private AppLockDao mDao;
        
        @Override
        public void onCreate() {
                super.onCreate();
                mDao = new AppLockDao(this);
        }
        
        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
                String className = event.getClassName().toString();
                String packageName = event.getPackageName().toString();

                // 判断是否切换到上锁的程序
                if (mDao.findLock(packageName)) {
                        // 找到上锁的
                         // 弹出提示
                         Toast.makeText(this, "提示上锁", Toast.LENGTH_SHORT).show();
                }
        }

        @Override
        public void onInterrupt() {

        }
}
几个重要的重载方法:
onServiceConnected() - 可选。系统会在成功连接上你的服务的时候调用这个方法,在这个方法里你可以做一下初始化工作,例如设备的声音震动管理,也可以调用setServiceInfo()进行配置工作。
onAccessibilityEvent() - 必须。通过这个函数可以接收系统发送来的AccessibilityEvent,接收来的AccessibilityEvent是经过过滤的,过滤是在配置工作时设置的。
onInterrupt() - 必须。这个在系统想要中断AccessibilityService返给的响应时会调用。在整个生命周期里会被调用多次。

onUnbind() - 可选。在系统将要关闭这个AccessibilityService会被调用。在这个方法中进行一些释放资源的工作。

拦截界面
当我们判断到当前打开的应用程序是我们上锁的应用程序时,应该弹出拦截的界面LockActivity。
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
    android:orientation="vertical" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="@color/title_bg" >

        <ImageView
            android:id="@+id/lock_iv_icon"
            android:layout_width="36dp"
            android:layout_height="36dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="8dp"
            android:src="@drawable/ic_default" />

        <TextView
            android:id="@+id/lock_tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="8dp"
            android:layout_toRightOf="@id/lock_iv_icon"
            android:text="应用名称"
            android:textColor="#ffffff" />
    </RelativeLayout>

    <com.itheima.zphuanlove.view.LockPatternView
        android:id="@+id/lock_parttern_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
代码实现:
注意这里需要通过AppLockService打开该activity的时候传递包名数据过来。以及在当前界面按返回键的时候应该进行处理,直接跳转到桌面应用。
[Java] 纯文本查看 复制代码
public class LockActivity extends Activity {        

        private ImageView mIvIcon;
        private TextView mTvName;
        private LockPatternView mPatternView;
        private String mPackageName;

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

                initView();
        }
        
        private void initView() {
                mIvIcon = (ImageView) findViewById(R.id.lock_iv_icon);
                mTvName = (TextView) findViewById(R.id.lock_tv_name);
                mPatternView = (LockPatternView) findViewById(R.id.lock_parttern_view);

                mPackageName = getIntent().getStringExtra("pkg");
                PackageManager pm = getPackageManager();
                try {
                        PackageInfo info = pm.getPackageInfo(mPackageName, 0);

                        // 设置图标,应用名称
                        Drawable icon = PackageUtils.getAppIcon(this, info);
                        String name = PackageUtils.getAppName(this, info);

                        mIvIcon.setImageDrawable(icon);
                        mTvName.setText(name);

                } catch (NameNotFoundException e) {
                        e.printStackTrace();
                }
        }
        

        @Override
        public void onBackPressed() {
                // 让当前应用进入桌面
                // <intent-filter>
                // <action android:name="android.intent.action.MAIN" />
                // <category android:name="android.intent.category.HOME" />
                // <category android:name="android.intent.category.DEFAULT" />
                // <category android:name="android.intent.category.MONKEY"/>
                // </intent-filter>
                Intent intent = new Intent();
                intent.setAction("android.intent.action.MAIN");
                intent.addCategory("android.intent.category.HOME");
                intent.addCategory("android.intent.category.DEFAULT");
                intent.addCategory("android.intent.category.MONKEY");
                startActivity(intent);

                super.onBackPressed();
        }
}
服务中开服务中开启该activity记得一定要设置flag为FLAG_ACTIVITY_NEW_TASK
接下来继续完善拦截页面,当用户在拦截页面输入正确的密码后我们应该关闭当前拦截页面,显示被拦截的应用,并且发送广播通知我们的拦截服务不要再对该应用进行拦截。

在LockActivity中注册密码的输入:
[Java] 纯文本查看 复制代码
private void initEvent() {
                mPatternView.setOnPatternListener(new OnPatternListener() {
                        @Override
                        public void onPatternStart() {
                        }

                        @Override
                        public void onPatternDetected(List<Cell> pattern) {
                                // 设置密码校验
                                StringBuilder builder = new StringBuilder();
                                for (int i = 0; i < pattern.size(); i++) {
                                        Cell cell = pattern.get(i);

                                        int p = cell.getRow() * 3 + cell.getColumn();
                                        builder.append(p);
                                }
                                String password = builder.toString();

                                // 比对密码
                                String pwd = PreferenceUtils.getString(LockActivity.this,
                                                Config.KEY_APP_LOCK_PWD);

                                if (!pwd.equals(password)) {
                                        // 不同
                                        mPatternView.setDisplayMode(DisplayMode.Wrong);
                                        return;
                                }
                                // 通知程序锁的服务去停止对当前应用的拦截,发送广播
                                Intent intent = new Intent();
                                intent.setAction("org.itheima.lock");
                                intent.putExtra("pkg", mPackageName);
                                sendBroadcast(intent);
                                // 让当前的activity销毁,显示上锁的程序
                                finish();
                        }

                        @Override
                        public void onPatternCleared() {
                        }

                        @Override
                        public void onPatternCellAdded(List<Cell> pattern) {
                        }
                });
        }
在AppLockService中注册广播接收者,完整代码为:
[Java] 纯文本查看 复制代码
public class AppLockService extends AccessibilityService {
        private AppLockDao mDao;
        private ApplockReceiver mReceiver;

        private List<String> mUnlockApps;

        @Override
        public void onCreate() {
                super.onCreate();
                mDao = new AppLockDao(this);
                mUnlockApps = new ArrayList<String>();

                // 注册广播
                mReceiver = new ApplockReceiver();
                IntentFilter filter = new IntentFilter();
                filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
                filter.addAction("org.itheima.lock");
                filter.addAction(Intent.ACTION_SCREEN_OFF);// 锁屏
                registerReceiver(mReceiver, filter);
        }

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

        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
                String className = event.getClassName().toString();
                String packageName = event.getPackageName().toString();
                // 判断是否不需要拦截
                if (mUnlockApps.contains(packageName)) {
                        return;
                }
                // 判断是否切换到上锁的程序
                if (mDao.findLock(packageName)) {
                        // 找到上锁的
                        // 弹出提示
                        // Toast.makeText(this, "提示上锁", Toast.LENGTH_SHORT).show();
                        // 跳转到上锁界面
                        Intent intent = new Intent(this, LockActivity.class);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        intent.putExtra("pkg", packageName);
                        startActivity(intent);
                }
        }

        @Override
        public void onInterrupt() {

        }

        private class ApplockReceiver extends BroadcastReceiver {
                @Override
                public void onReceive(Context context, Intent intent) {
                        String action = intent.getAction();
                        if ("org.itheima.lock".equals(action)) {
                                // 接收到放行拦截应用的广播
                                String packageName = intent.getStringExtra("pkg");
                                mUnlockApps.add(packageName);
                        } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                                // 清空所有放行的应用
                                mUnlockApps.clear();
                        }
                }
        }
}
注意:

1.这里有一个Bug!,如果当我们应用程序打开后没有关闭,这时去点击加锁的应用会弹出拦截的页面,但是由于默认的任务栈启动模式的原因会导致当输入完密码的时候直接显示我们的安全卫士应用了,所以为了解决该问题我们最好是将LockActivity的界面做成SingleInstance模式。
[XML] 纯文本查看 复制代码
<activity      android:name="com.itheima.zphuanlove.activity.LockActivity"
            android:launchMode="singleInstance" />
</activity>

2.通过监听锁屏的广播,当锁屏时清除不加锁的集合,可以让下次解锁手机的时候可以再次锁住需要监听的应用。

细节处理
首先在重用工具中增加打开程序锁服务的开关设置:

实现控件的布局和初始化,以及点击事件如下:
[Java] 纯文本查看 复制代码
private void clickApplockService() {
                // 如果服务没有开启,就去开启服务
                // if (ServiceStateUtils.isServiceRunning(this, ApplockService.class)) {
                // // 开启的,就关闭
                // // AccesibilityService.不可以同api开启或是关闭,需要通过用户操作
                // }

                // <intent-filter>
                // <action android:name="android.intent.action.MAIN" />
                // <action android:name="android.settings.ACCESSIBILITY_SETTINGS" />
                // <!-- Wtf... this action is bogus! Can we remove it? -->
                // <action android:name="ACCESSIBILITY_FEEDBACK_SETTINGS" />
                // <category android:name="android.intent.category.DEFAULT" />
                // <category android:name="android.intent.category.VOICE_LAUNCH" />
                // </intent-filter>
                Intent intent = new Intent();
                intent.setAction("android.settings.ACCESSIBILITY_SETTINGS");
                startActivity(intent);

        }
开启AccessibilityService需要用户手动去打开和关闭,没有api可以操作。
为了解决一些bug的产生我们需要修改下LockActivity的清单文件配置,增加两个属性:
[XML] 纯文本查看 复制代码
 <activity
            android:taskAffinity=":lock"
            android:excludeFromRecents="true"
            android:name="com.itheima.zphuanlove.activity.LockActivity"
            android:launchMode="singleInstance" />
1. android:excludeFromRecents="true"代表的是设置为true后,当用户按了“最近任务列表”时候,该Task不会出现在最近任务列表中,可达到隐藏应用的目的。
2.android:taskAffinity=":lock"设置当前activity所在的任务属性名。
(每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果 Application也没有指明,那么该taskAffinity的值就等于包名。而Task也有自己的affinity属性,它的值等于它的根 Activity的taskAffinity的值。)
缓存清理
首先确定需要实现的效果,先来看下效果图:
1.     进入的时候正在扫描:
2.扫描完成:
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        style="@style/titleBarStyle"
        android:text="缓存清理" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:background="@color/title_bg" >

        <!-- 扫描中 -->

        <RelativeLayout
            android:id="@+id/cc_rl_scaning_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone" >

            <RelativeLayout
                android:id="@+id/cc_rl_icon_container"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:layout_centerVertical="true"
                android:layout_marginLeft="10dp"
                android:background="@drawable/scan_bg" >

                <!-- 应用图标 -->

                <ImageView
                    android:id="@+id/cc_iv_icon"
                    android:layout_width="60dp"
                    android:layout_height="60dp"
                    android:layout_centerInParent="true"
                    android:src="@drawable/ic_default" />

                <!-- 扫描的线 -->

                <ImageView
                    android:id="@+id/cc_iv_scan_line"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:src="@drawable/scan_line" />
            </RelativeLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="10dp"
                android:layout_toRightOf="@id/cc_rl_icon_container"
                android:orientation="vertical" >

                <ProgressBar
                    android:id="@+id/cc_progress_bar"
                    style="?android:attr/progressBarStyleHorizontal"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:progressDrawable="@drawable/progress_horizontal" />

                <TextView
                    android:id="@+id/cc_tv_name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="5dp"
                    android:singleLine="true"
                    android:text="应用名称"
                    android:textColor="#ffffff"
                    android:textSize="16sp" />

                <TextView
                    android:id="@+id/cc_tv_cachesize"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="5dp"
                    android:singleLine="true"
                    android:text="应用缓存大小"
                    android:textColor="#ffffff"
                    android:textSize="16sp" />
            </LinearLayout>
        </RelativeLayout>

        <!-- 扫描结束后 -->

        <RelativeLayout
            android:id="@+id/cc_rl_scaned_container"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

            <Button
                android:id="@+id/cc_btn_scan"
                style="@style/btnOkNormal"
                android:layout_alignParentRight="true"
                android:layout_centerVertical="true"
                android:layout_marginRight="8dp"
                android:text="快速扫描" />

            <TextView
                android:id="@+id/cc_tv_cache_detail"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:layout_centerVertical="true"
                android:layout_marginLeft="8dp"
                android:layout_toLeftOf="@id/cc_btn_scan"
                android:text="缓存情况"
                android:textColor="#ffffff"
                android:textSize="16sp" />
        </RelativeLayout>
    </RelativeLayout>

    <ListView
        android:id="@+id/cc_listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1000" >
    </ListView>

    <Button
        android:id="@+id/cc_btn_clear"
        style="@style/btnOkNormal"
        android:layout_width="match_parent"
        android:layout_weight="1"
        android:text="一键清理" />

</LinearLayout>
实现ListView的假数据展示接下来在CacheClearActivity中设置布局,并初始化所有的控件。其中ListView的展示可以通过假数据先进行展示,那么需要先确定好item的布局以及数据结构javabean;那么对应的javaBean:CacheBean代码如下:
[Java] 纯文本查看 复制代码
public class CacheBean {
        public Drawable icon;// 图标
        public String name;
        public long cacheSize;// 缓存大小
        public String packageName;
}

Item布局文件item_cache.xml:
[XML] 纯文本查看 复制代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp" >

    <ImageView
        android:id="@+id/item_cache_iv_icon"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_default" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="8dp"
        android:layout_toRightOf="@id/item_cache_iv_icon"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/item_cache_tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="应用名称" />

        <TextView
            android:id="@+id/item_cache_tv_cachesize"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="应用缓存大小"
            android:textColor="#99000000" />
    </LinearLayout>

    <ImageView
        android:id="@+id/item_cache_iv_clear"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:src="@drawable/btn_clear_selector" />

</RelativeLayout>

制造假数据实现adapter:
[Java] 纯文本查看 复制代码
private void initData() {
                // 数据加载,假数据加载
                mDatas = new ArrayList<CacheBean>();
                for (int i = 0; i < 40; i++) {
                        CacheBean bean = new CacheBean();
                        bean.name = "应用-" + i;
                        bean.cacheSize = 10000;

                        mDatas.add(bean);
                }

                mAdapter = new CacheAdapter();
                mListView.setAdapter(mAdapter);
        }

        private class CacheAdapter extends BaseAdapter {

                @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) {
                        ViewHolder holder = null;
                        if (convertView == null) {
                                convertView = View.inflate(CacheClearActivity.this,
                                                R.layout.item_cache, null);
                                holder = new ViewHolder();
                                convertView.setTag(holder);
                                holder.ivClear = (ImageView) convertView
                                                .findViewById(R.id.item_cache_iv_clear);
                                holder.ivIcon = (ImageView) convertView
                                                .findViewById(R.id.item_cache_iv_icon);
                                holder.tvName = (TextView) convertView
                                                .findViewById(R.id.item_cache_tv_name);
                                holder.tvCacheSize = (TextView) convertView
                                                .findViewById(R.id.item_cache_tv_cachesize);
                        } else {
                                holder = (ViewHolder) convertView.getTag();
                        }

                        // 设置数据
                        final CacheBean bean = mDatas.get(position);
                        if (bean.icon == null) {
                                holder.ivIcon.setImageResource(R.drawable.ic_default);
                        } else {
                                holder.ivIcon.setImageDrawable(bean.icon);
                        }

                        holder.tvName.setText(bean.name);
                        holder.tvCacheSize.setText("缓存大小:"
                                        + Formatter.formatFileSize(CacheClearActivity.this,
                                                        bean.cacheSize));

                        if (bean.cacheSize > 0) {
                                holder.ivClear.setVisibility(View.VISIBLE);
                        } else {
                                holder.ivClear.setVisibility(View.GONE);
                        }
                        return convertView;
                }
        }

        private static class ViewHolder {
                ImageView ivIcon;
                ImageView ivClear;
                TextView tvName;
                TextView tvCacheSize;
        }

展现的效果如下:
获取真实数据逐步显示:
通过AsyncTask来实现异步获取数据然后实时更新进度显示当前正在扫描的应用程序,在initData方法中添加一个startScan()方法开始扫描:
CacheScanTask的代码如下:
[Java] 纯文本查看 复制代码
private class CacheScanTask extends AsyncTask<Void, CacheBean, Void> {
                private int progress;
                private int max;
                @Override
                protected void onPreExecute() {
                        // 隐藏扫描后的,显示扫描中
                        mContainerScanned.setVisibility(View.GONE);
                        mContainerScanning.setVisibility(View.VISIBLE);
                }

                @Override
                protected Void doInBackground(Void... params) {
                        // 真实数据(所有的应用 )
                        List<PackageInfo> packages = mPm.getInstalledPackages(0);
                        max = packages.size();
                        mProgressBar.setMax(max);
                        for (PackageInfo info : packages) {
                                CacheBean bean = new CacheBean();
                                bean.icon = PackageUtils.getAppIcon(CacheClearActivity.this,
                                                info);
                                bean.name = PackageUtils.getAppName(CacheClearActivity.this,
                                                info);
                                bean.cacheSize = 0;// TODO:
                                publishProgress(bean);
                                
                                SystemClock.sleep(50);
                        }
                        return null;
                }

                @Override
                protected void onProgressUpdate(CacheBean... values) {
                        CacheBean bean = values[0];
                        // 1.逐个添加
                        if (bean.cacheSize > 0) {
                                // 有缓存
                                mDatas.add(0, bean);
                        } else {
                                mDatas.add(bean);
                        }
                        // 2.ui逐个更新
                        mAdapter.notifyDataSetChanged();
                        // 3. 默认滑动到底部
                        mListView.smoothScrollToPosition(mAdapter.getCount());
                        
                        // 显示扫描的图标
                        mIvIcon.setImageDrawable(bean.icon);
                        mTvName.setText(bean.name);
                        mTvCacheSize.setText("缓存大小:"
                                        + Formatter.formatFileSize(CacheClearActivity.this,
                                                        bean.cacheSize));
                        mProgressBar.setProgress(++progress);
                }

                @Override
                protected void onPostExecute(Void result) {
                        // 隐藏扫描后的,显示扫描中
                        mContainerScanned.setVisibility(View.VISIBLE);
                        mContainerScanning.setVisibility(View.GONE);
                }
        }
扫描过程中的动画以及扫描后的数据展示
在扫描的过程中让扫描的线不停的做上下来回的位移动画即可表现出不停的扫描操作。扫描前开启动画:
不过一定要记得在扫描后关闭清空动画:
扫描完毕后的文本显示需要我们获取到扫描出有多少个有缓存的应用以及缓存的大小,需要在扫描中去统计:
在doInBackGroud中去判断当应用的缓存大于0的时候就进行累加:
在扫描完毕后进行文本展示,并注意将listview滚动到第一行进行展示:
快速扫描的点击
扫描完成后有一个快速扫描的按钮,可以重新再次进行扫描,注册点击事件,直接调用写好的startScan()方法即可。

不过这里有一个bug需要解决,就是在重新扫描后,会有之前重复的数据没有清除,这里需要在每次扫描前进行清空:
获取应用程序缓存
获取应用程序缓存需要借助于系统的服务来帮助我们进行实现,并且需要拿到中间代理人对象来获取到应用的缓存,拷贝aidl文件:
通过mPm.getPackageSizeInfo(packageName, mStatsObserver);即可获取到缓存,不过刚好这个方法又给系统给隐藏起来了,所以需要用到反射,修改后的代码如下:
[Java] 纯文本查看 复制代码
final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
                public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
                        String packageName = stats.packageName;
                        PackageInfo info;
                        try {
                                info = mPm.getPackageInfo(packageName, 0);

                                CacheBean bean = new CacheBean();
                                bean.icon = PackageUtils.getAppIcon(CacheClearActivity.this,
                                                info);
                                bean.name = PackageUtils.getAppName(CacheClearActivity.this,
                                                info);
                                bean.cacheSize = stats.cacheSize;
                                bean.packageName = packageName;

                                if (bean.cacheSize > 0) {
                                        mCacheCount++;
                                        mCacheSize += bean.cacheSize;
                                }
                                task.updateProgress(bean);
                        } catch (NameNotFoundException e) {
                                e.printStackTrace();
                        }

                }
        };
注意要添加权限:

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

清理缓存的实现
首先是单个条目的点击清理,由于系统没有提供实现清理单个应用程序的API,所以这里需要我们跳转到应用程序的信息界面手动去清理,找到adpater中的清理icon,设置点击事件:
为了防止清理完毕后回到我们界面数据的刷新,在onStart方法中调用startScan方法即可,不过记得将之前的initData方法中的startScan方法去掉。
[Java] 纯文本查看 复制代码
   @Override
   protected void onStart() {
      super.onStart();
      // 开始扫描
      startScan();
   }
最后就是一键清理的实现,可以借助于PackageManager中的freeStorageAndNotify的方法来让系统自动帮我们清理所有应用程序的缓存,记得拷贝IPackageDataObserver.aidl文件,代码如下:
[Java] 纯文本查看 复制代码
        /**一键清理*/
        private void clickClear() {
                // 清理所有的缓存数据
                // freeStorageAndNotify(long freeStorageSize, IPackageDataObserver
                // observer)
                try {
                        Method method = PackageManager.class.getDeclaredMethod(
                                        "freeStorageAndNotify", Long.TYPE,
                                        IPackageDataObserver.class);
                        method.setAccessible(true);
                        method.invoke(mPm, Long.MAX_VALUE, null);

                        // 重新的扫描
                        startScan();
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }
记得添加权限:
<uses-permissionandroid:name="android.permission.CLEAR_APP_CACHE" />
最后记得修改两个潜在的bug,这里不贴代码自行实现。

【济南校区】Android/php/JavaEE课程笔记同步+面试技巧汇总链接:


1 个回复

倒序浏览
多谢分享
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马