MobileSafe Day12

Day12##

12.1 复习 #

解锁操作,这个软件锁当时做时,bug就比较多一些,比较琐碎一些,而且这个思路在整个项目里边来说是最乱的,所以要好好消化



解锁操作这里,重要的就是发送广播的操作要会,发送广播,接收广播



	比如说要给服务发送一个消息或数据

		1.一般常用的就是广播

		2.当然也可以用回调

		但是一般首先想到的就是广播,用广播去进行操作



还有就是在服务中注册广播接收广播事件这个操作,前边也简绍过

首先是通过intent去setAction("content://cn.itcast.mobilesafexian02.unlock"),这个是设置发送的广播事件

这个事件"content://cn.itcast.mobilesafexian02.unlock"是我们自己定义的

一般都是用我们的包名,后边加上一个唯一标识来表示,当然你写其他的,aabbcc,eeff等等都可以

只要接收的和发送的是一致的就可以,然后通过sendBroadcast(intent)去发送

然后紧接着在服务中注册广播进行接收我们这个自定义的事件了

我们在注册广播时,通过intentFilter去addAction("content://cn.itcast.mobilesafexian02.unlock")

接收的这个广播事件和我们发送的广播事件是一致的,然后紧接着就是在onReceive方法中去做处理了

这个逻辑就比较简单,用传递过来的包名做一下判断,判断包名是否在数据库中



bug处理里边的启动模式是面试重点



解锁数据库优化操作,主要是通过内容观察者去进行操作,这个数据库发生变化了是要通过内容解析者,自己去通知一下内容观察者

然后内容观察者接收到我们的通知uri之后,它就会去更新数据了



以前我们在去写删除通话记录时,只写了内容观察者registerContentObserver去工作,那是因为系统当中,它的数据库在改变时,它自己就帮我们调用了这段代码(通过内容解析者告诉内容观察者,数据已经发生变化,可以去更新数据),所以我们就不用写这段代码

但是我们的数据库变化时,系统是不会帮我们去通知的,所以说我们必须得通过这段代码,自己去告诉内容观察者,数据已经发生变化

可以去更新数据,然后再到观察者中去接收下我们的通知uri,它就会去更新数据了



重要的是这个地址uri,即:



	Uri uri = Uri.parse("content://cn.itcast.mobilesafexian02.unlock.change");



这个地址也是我们自定义的,这个你也可以随便写,但是发送和接收uri的一定要保持一致



获取短信的操作就比较简单了,就是通过内容解析者,去查询下数据库,和获取联系人的操作是一模一样的操作



保存短信,这里主要是生成一个xml的操作:

	我们是获取一个XmlSerializer(Xml序列器),然后通过它去setOutput设置了下文件保存位置及保存的编码

	然后紧接着就是通过startDocument去设置文件头信息,通过startTag去设置跟标签,然后在while循环中,用startTag去获取每个短信的标签以及数据的标签



进度条的两种显示方式,这个ProgressDialog和Progressbar在工作中会经常用到,目前来说,ProgressDialog用到的会比较多一些,因为ProgressDialog的用户体验要比ProgressBar要好一些,但是ProgressBar在一些特殊的地方也会用到,所以都要知道怎么使用



接下来讲了回调方法:

	1.我们是在业务类中写了一个接口interface

	2.然后将接口以参数的形式,传递给要调用的业务方法,并在其中执行相关的接口的方法

	3.在调用业务方法的时候,在方法中实现接口操作,在实现方法中去实现相应的操作	





还原短信比较简单,通过内容解析者ContentResolver去处理一些数据



可扩展的listview,也就是ExpandableListView,在工作当中可能会用到,但是用到的可能不是太多,一般用到也是继承这个ExpandableListView,然后进行自定义操作的



两种实现抽屉的效果,第一种方式SlidingDrawer大家了解下,重要的是第二种方式DrawerLayout(V4包下的)



杀毒历史这,现在大家用的都是黑名单方式,因为白名单要把检测过的应用放到数据库,但是andrioid出来的app太多了,全部放入数据库不太现实,所以用黑名单方式,把已有的病毒特征码放到数据库中,这个操作还是比较落后的



杀毒界面这里,重要的是图层layer-list, 要知道怎么用,就是在drawable文件夹下创建一个xxx.xml,在xxx.xml中用这个图层layer-list,layer-list里边又有一些item,然后在布局文件中ProgressBar控件中使用的时候,是通过属性:

	android:progressDrawable="@drawable/anti_progressbar"

去使用的



接下来讲了扫描程序,这里重要的有两点,旋转动画的使用,和扫描这块,扫描这块主要就是一些关于进度条的使用了



progressbar里边封装了一个handler,所以才能直接在子线程中去更新它的UI(pb_antivirus_progress.setProgress(progress)),而不像textview,要去setText更新UI时,还要放在runOnUiThread里边去

12.2 获取签名信息 #

前边把扫描程序的操作实现了,接下来实现杀毒的操作,要去匹配杀毒了



对比金山卫士的病毒查杀,它每次也是扫描,扫描到病毒之后,会以不同颜色作为区分标明出来



前面说了android目前用的是黑名单查杀病毒,将E:\itcast\20150721-JAVAME\二期\王松\手机卫士day01\Day01资料\安全卫士\APK资料\com.ijinshan.mguard.1324644228599\assets中的antivirus.db(反病毒程序,抗病毒素)

拖到sqlite expert中打开,点击查看datable,首先可以看出它是一个MD5,后边的desc是病毒的描述信息

重点是看前边的MD5,它是病毒的一个特征码



所以它在扫描时,是拿到一个程序的特征码,然后MD5一下转化成相应的特征码,去和数据库antivirus.db里面比较,如果数据库中有,就认为你是病毒,没有说明不是病毒,这个就是黑名单查杀



怎么去获取程序特征码呢?



程序的特征码其实就是一个程序的唯一标识,这个唯一标识不是我们的包名,我们都知道,上线发布到应用市场上的程序都是打过签名的,这个签名就是应用程序在市场上的唯一标识,它就是拿到签名MD5一下,转化为了特征码,然后进行操作的,所以需要获取一下签名信息



到AntivirusActivity中的scanner方法中的“7.设置扫描的文字”之后,去获取下应用的签名



从packageInfo里边去获取一个signatures,返回一个应用的签名数组Signature[]



为什么返回的是数组呢?

	在工作时,可能会遇到的机率很小,一般在给银行工作时会出现这种问题,一个app由两家公司来开发,两家公司每家开发一半

	

	这样做的原因是为了每家公司都不能拿到完整的信息,保证安全性,但两家公司开发就会出现一个问题,一般打包签名时,是每家公司都会打包一个签名



	它那个签名是按照一定规则做的,后期可以整合成一个,但是你在获取签名时,有一定机率是获取到两个签名,它还可能没有打包成一个签名,所以两个签名分别代表的是一半应用程序,所以它这里返回的是签名数组



	当然这种情况很少见,在银行会用到,其他地方应该都用不到



那它返回给的是一个签名数组,但是应用程序只有一个签名,那singnatures[0]就代表的是这个唯一签名

然后对singnatures[0]用toCharsString()转化成字符串,这样它就返回的是String类型的签名了,那先打印出来看下



运行程序,点击手机杀毒,发现奔溃了

	原因: 	java.lang.NullPointerException

			at cn.itcast.mobilesafexian02.AntivirusActivity1.run(AntivirusActivity.java:87)

说AntivirusActivity.java里边的run方法里边报空指针异常了,在第87行



跳转过去,即:

	//将获取签名信息转化成字符串

	String singnature = singnatures[0].toCharsString();

	

这行为空了,那在上边那行,即:

	//获取应用的签名数组

	Signature[] singnatures = packageInfo.signatures;

	//将获取签名信息转化成字符串

	String singnature = singnatures[0].toCharsString();

	

在上边这行packageInfo.signatures获取的是签名信息,获取签名信息时,上边就要改了,即:



//2.获取安装的程序

List<PackageInfo> installedPackages = pm.getInstalledPackages(0);



这行是获取所有已安装的应用程序,getInstalledPackages(flags),它的参数flags,看到注释中说如果是获取签名,那参数就必须要设置标签GET_SIGNATURES才能获取到签名



那将上边获取安装的程序这行代码改为:



//2.获取安装的程序   参数必须设置标签,才可以获取到(在log中)签名信息(特别长) 否则会报空指针异常

List<PackageInfo> installedPackages = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);	

			

运行程序,点击手机杀毒,在logcat中看到它打印出了所有已安装的应用的签名,每个的签名都有20,30行之多



那这个签名特别长,但是数据库中保存的是md5,这个md5不是太长,即签名信息特别长,所以转化成MD5



以前已经写过一个md5工具类MD5Utils.java,在这里那就可以直接用了,用MD5Utils.digestPassword(singnature),将这个singnature传进去,然后在等号左边再赋值给singnature,此时String类型的singnature就是转化为MD5的了



	/**
  • 扫描程序
     */
     private void scanner() {
     //1.获取包的管理者
     final PackageManager pm = getPackageManager();
     tv_antivirus_name.setText(“正在初始化64核扫描引擎…”);
     new Thread(){
     public void run() {
     //5.睡1秒,让程序延时1秒钟扫描
     SystemClock.sleep(1000);

      			//2.获取安装的程序   参数必须设置标签,才可以获取到(在log中)签名信息(特别长) 否则会报空指针异常
    
      			List<PackageInfo> installedPackages = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
    
    
    
    
    
      			//3.设置progerssbar的总进度
    
      			pb_antivirus_progressbar.setMax(installedPackages.size());
    
      			//4.设置当前进度
    
      			int progress = 0;
    
      			for (PackageInfo packageInfo : installedPackages) {
    
      				//5.睡100毫秒,增加进度显示的真实性
    
      				SystemClock.sleep(100);
    
      				//4.设置当前进度
    
      				progress++;
    
      				pb_antivirus_progressbar.setProgress(progress);
    
      				//6.获取应用的名称
    
      				final String name = packageInfo.applicationInfo.loadLabel(pm).toString();
    
      				runOnUiThread(new Runnable() {
    
      					
    
      					@Override
    
      					public void run() {
    
      						//7.设置扫描的文字
    
      						tv_antivirus_name.setText("正在扫描:"+name);
    
    
    
    
    
      						//获取应用的签名数组
    
      						Signature[] singnatures = packageInfo.signatures;
    
      						//将获取签名信息转化成字符串
    
      						String singnature = singnatures[0].toCharsString();
    
      						//转化成特征码   签名信息特别长,所以转化成MD5
    
      						singnature = MD5Utils.digestPassword(singnature);
    
    
    
      						System.out.println(name+"签名信息:"+singnature);
    
      					
    
    
    
      						//9.设置在下方空白处显示扫描显示的应用成名称
    
      						TextView textView = new TextView(getApplicationContext());
    
      						textView.setText(name);
    
      						textView.setTextColor(Color.BLACK);
    
      						ll_antivirus_safeapk.addView(textView, 0);//index:将view添加到哪个位置
    
      					}
    
      				});
    
      			}
    
      			runOnUiThread(new Runnable() {
    
      				
    
      				@Override
    
      				public void run() {
    
      					//8.设置扫描完成的文字和停止动画操作
    
      					tv_antivirus_name.setText("扫描完成,很安全");
    
      					//停止动画
    
      					iv_antivirus_scanner.clearAnimation();
    
      				}
    
      			});
    
      		};
    
      	}.start();
    
      }
    

    运行程序,看到打印出来的签名信息,就只有半行了,这就是MD5的一个方便之处

    获取签名信息步骤总结如下:

    1.添加标签

      //2.获取安装的程序   参数必须设置标签,才可以获取到(在log中)签名信息(特别长) 否则会报空指针异常
    
      List<PackageInfo> installedPackages = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
    

    注意:

    为什么参数必须设置标签?

      因为获取的时候,只要是flags里面标明的“GET_ACTIVITIES,GET_GIDS,GET_CONFIGURATIONS,GET_INSTRUMENTATION,GET_SIGNATURES”这些标签,都是在0的时候获取不到的,因为所有的里边是不包含这些标签的,所以参数设置成这个PackageManager.GET_SIGNATURES标签,就表明我是要额外的再去拿一下它的,也就是手动的自己去拿一下这个GET_SIGNATURES标签才行
    

    2.获取应用程序的签名信息

    在AntivirusActivity.java中的“7.设置扫描的文字”之后添加如下代码:

      //获取应用的签名数组
    
      Signature[] singnatures = packageInfo.signatures;
    
    
    
      //将获取签名信息转化成字符串 我们的应用程序就一个签名,所以0代表的就是我们的签名
    
      String singnature = singnatures[0].toCharsString();
    
    
    
      //转化成特征码
    
      singnature = MD5Utils.digestPassword(singnature);
    

12.3 杀毒操作 #

已经拿到了所有应用程序的MD5特征码,接下来就可以和数据库antivirus.db(病毒数据库)中保存的MD5特征码进行匹配了

那接下来又要进行一些拷贝数据库,查询数据的操作



这里已经准备了一个数据库,找到E:\itcast\20150721-JAVAME\二期\王松\手机卫士day01\Day01资料\安全卫士\上课资料\binddu下的antivirus.db数据库



注意:

	这个数据库经过特殊处理,加上getkey.apk,t2t.apk这两个所谓的病毒,在这里边加上特征码,以便测试



将antivirus.db数据库拷贝到项目的assets目录下,然后要去拷贝数据库,然后才能和拿到的所有应用程序的MD5特征码进行匹配了



到SplashActivity中的onCreate方法中我们调用过一个copyDb()方法,以前只拷贝address.db这一个数据库,所以在copyDb方法中直接把名字写死的



现在还要拷贝病毒的数据库,所以为了区分,把以前的copyDb方法:



		@Override

		protected void onCreate(Bundle savedInstanceState) {

			super.onCreate(savedInstanceState);

			setContentView(R.layout.activity_splash);

			System.out.println("splash界面启动了......");

			

			AdManager.getInstance(this).init("b55327eed7258e53", "b486156834be6e52", true);

			

			sp = getSharedPreferences("config", MODE_PRIVATE);

			tv_splash_versionname = (TextView) findViewById(R.id.tv_splash_versionname);

			tv_spalsh_plan = (TextView) findViewById(R.id.tv_spalsh_plan);

			tv_splash_versionname.setText("版本号:" + getVersionName());// 设置显示的版本号

	

			if (sp.getBoolean("update", true)) {

				update();

			} else {

				// 不能让主线程睡两秒钟,原因:主线程做一些渲染界面的操作,布局文件中控件都是通过主线程渲染出来,

				// 让主线程睡两秒就没有办法渲染界面,但是activity我们还是打开的,activity默认没有布局文件时候就是空白界面

				new Thread() {

					public void run() {

						SystemClock.sleep(2000);

						enterHome();

					};

				}.start();

			}

	

	

			copyDb();

			

	

			// 开启服务

			// Intent intent = new Intent(this,AddressService.class);

			// startService(intent);

			shortCut();

		}	





		/**
  • 拷贝数据库
     */
     private void copyDb() {
     File file = new File(getFilesDir(), “address.db”);
     // 5.判断file是否存在,存在不去拷贝
     if (!file.exists()) {
     // 1.获取assets管理者
     AssetManager assets = getAssets();
     InputStream in = null;
     FileOutputStream out = null;
     try {
     // 2.通过assets管理者打开数据库
     in = assets.open(“address.db”);
     // getCacheDir() : 获取缓存路径,getFilesDir():获取文件的路径
     out = new FileOutputStream(file);
     // 3.读写操作
     // 缓冲区
     byte[] b = new byte[1024];
     int len = -1;
     while ((len = in.read(b)) != -1) {
     out.write(b, 0, len);
     }
     } catch (IOException e) {
     e.printStackTrace();
     } finally {// 有没有异常都会执行
     // out.close();
     // in.close();
     // 4.关流
     IOUtils.closeQuietly(out);
     IOUtils.closeQuietly(in);
     }
     }
     }

    改成数据库名字address.db不要写死的方式,而是在onCreate中调用copyDb方法时将数据库的名字address.db传递给copyDb方法

    然后在copyDb方法中用参数name的形式传递进去

    那这样,我们就可以在onCreate方法中调用copyDb方法,将病毒数据库的名字antivirus.db也传递给拷贝数据库的方法copyDb,然后在copyDb方法中用参数name的形式传递进去

    即:

    @Override

    protected void onCreate(Bundle savedInstanceState) {

      super.onCreate(savedInstanceState);
    
      setContentView(R.layout.activity_splash);
    
      System.out.println("splash界面启动了......");
    
      
    
      AdManager.getInstance(this).init("b55327eed7258e53", "b486156834be6e52", true);
    
      
    
      sp = getSharedPreferences("config", MODE_PRIVATE);
    
      tv_splash_versionname = (TextView) findViewById(R.id.tv_splash_versionname);
    
      tv_spalsh_plan = (TextView) findViewById(R.id.tv_spalsh_plan);
    
      tv_splash_versionname.setText("版本号:" + getVersionName());// 设置显示的版本号
    
    
    
      if (sp.getBoolean("update", true)) {
    
      	update();
    
      } else {
    
      	// 不能让主线程睡两秒钟,原因:主线程做一些渲染界面的操作,布局文件中控件都是通过主线程渲染出来,
    
      	// 让主线程睡两秒就没有办法渲染界面,但是activity我们还是打开的,activity默认没有布局文件时候就是空白界面
    
      	new Thread() {
    
      		public void run() {
    
      			SystemClock.sleep(2000);
    
      			enterHome();
    
      		};
    
      	}.start();
    
      }
    
    
    
      copyDb("address.db");
    
      copyDb("antivirus.db");
    
      
    
      // 开启服务
    
      // Intent intent = new Intent(this,AddressService.class);
    
      // startService(intent);
    
      shortCut();
    

    }

    /**

  • 拷贝数据库
     */
     private void copyDb(String name) {
     File file = new File(getFilesDir(), name);
     // 5.判断file是否存在,存在不去拷贝
     if (!file.exists()) {
     // 1.获取assets管理者
     AssetManager assets = getAssets();
     InputStream in = null;
     FileOutputStream out = null;
     try {
     // 2.通过assets管理者打开数据库
     in = assets.open(name);
     // getCacheDir() : 获取缓存路径,getFilesDir():获取文件的路径
     out = new FileOutputStream(file);
     // 3.读写操作
     // 缓冲区
     byte[] b = new byte[1024];
     int len = -1;
     while ((len = in.read(b)) != -1) {
     out.write(b, 0, len);
     }
     } catch (IOException e) {
     e.printStackTrace();
     } finally {// 有没有异常都会执行
     // out.close();
     // in.close();
     // 4.关流
     IOUtils.closeQuietly(out);
     IOUtils.closeQuietly(in);
     }
     }
     }

    运行程序,打开DDMS,在data目录中找到files,看到有antivirus.db数据了,这就说明拷贝病毒数据库成功了

    接下来还要写一个查询数据库的操作,来到AddressDao.java中,仿照查询号码归属地这个操作

    也创建一个AntivirusDao.java,然后创建一个方法,注意,要判断应用是否是病毒,这个方法需要返回一个boolean值

    把这个方法叫做isAntiViruse,仿照AddressDao.java中的“根据号码查询号码归属地”的方法queryAddress(String num,Context context),这个isAntiViruse方法也需要一个上下文,比较时比较的是md5值,也就是查询时,查询的是md5值

    接下来在isAntiViruse方法中,要写打开数据库的操作,可以完全拷贝AddressDao的queryAddress方法中打开数据库的操作

    接下来可以去查询数据库,用database调用query(table,columns,selection,selectionArgs,groupBy,having,orderBy,Limit)

      table表名:通过sqliteopenhelper打开antivirus.db,看到是datable
    
      要查询什么字段,不需要查询字段,看下有没有数据就可以了,那将columns可以写成null
    
      selection:查询条件,查询时,要根据MD5来看下数据库中有没有对应的MD5值,那将selection写成"md5=?"
    
      selectionArgs:查询参数,写成new String[]{md5}
    
      然后后边的参数都可以不用了
    
    
    
      返回一个cursor,接下来可以判断这个cursor中有没有数据,有数据直接返回一个true表明有病毒数据库中的MD5特征码,就表明有病毒,反之 没有病毒
    
    
    
      	public class AntiVirusDao {
    
      		/**
    
  • 查询是否是病毒

  • @param context

  • @param md5

  • @return
     */
     public static boolean isAntiViruse(Context context,String md5){
     File file = new File(context.getFilesDir(), “antivirus.db”);
     //打开数据库
     SQLiteDatabase database = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
     //查询数据库
     Cursor cursor = database.query(“datable”, null, “md5=?”, new String[]{md5}, null, null, null, null);
     if (cursor.moveToNext()) {
     return true;
     }
     return false;
     } 
     }

    是通过md5去查询,每个MD5对应的是一条记录,所以说用的是if,如果每个MD5对应的是多条记录,就用while循环

    到AntivirusActivity中的scanner方法中就可以调用这个isAntiViruse方法,调用查询数据库的操作,判断应用是否是病毒

    返回一个boolean值,可以判断下,当是病毒时,应该让扫描出来的应用名称字体为红色,反之为黑色

    运行程序,点击手机杀毒,没有检测出病毒来,那安装下携带有病毒的getkey.apk,t2t.apk到模拟器中

      用命令行将apk运行到模拟器中
    
    
    
      打开命令行,输入adb -s emulator-5554 install 将getkey.apk拖动到命令行中,它就会变成:
    
      adb -s emulator-5554 install G://讲课资料\xian02\手机卫士day01\Day01资料\安全卫士\上课资料\binddu\getkey.apk
    
      然后回车,提示Success便安装成功了
    
      同理,可以将t2t.apk也安装到模拟器中
    

    再点击手机杀毒,看到扫描出来的应用程序名称中,Tt,Getkey是红色的,但是既然扫描出病毒了,还提示“扫描完成,很安全”

    那前边通过packageInfo.pacakgeName,这个pacakgeName是表明一个应用程序

    在AntivirusActivity的成员变量处,定义一个String类型的list集合,专门用来存储病毒应用的包名

    然后在onCreate方法中new出来这个ArrayList,然后当发现病毒时,就可以给list添加这个病毒了,添加时可以添加它的包名packageInfo.packageName

    那发现病毒之后,要到下边的这个runOnUiThread的run方法中去处理,在run方法中判断,如果list.size()大于0

    就给TextView设置“扫描完成,发现病毒”,else也就是list.size()=0,设置"扫描完成,很安全"

    注意,停止动画这个操作要放在这个判断外边,因为扫描完成发信病毒了,也要停止动画

    运行程序,扫描完之后,就提示"扫描完成,发现病毒"了,那你光发现有用吗?还要帮用户处理一下

    如果发现病毒,提醒用户是否卸载,那要用到一个对话框

    来个AlertDialog,在new Builder(context)时,上下文参数不能用getApplicationContext,因为要让dialog知道它要挂载到哪个activity里边,所以写成AntivirusActivity.this

      public class AntivirusActivity extends Activity {
    
      
    
      	private ImageView iv_antivirus_scanner;
    
      	private ProgressBar pb_antivirus_progressbar;
    
      	private TextView tv_antivirus_name;
    
      	private LinearLayout ll_antivirus_safeapk;
    
    
    
      	/**
    
  • 存储病毒应用的包名
     */
     private List list;

      	@Override
    
      	protected void onCreate(Bundle savedInstanceState) {
    
      		super.onCreate(savedInstanceState);
    
      		setContentView(R.layout.activity_antivirus);
    
    
    
      		list = new ArrayList<String>();
    
    
    
      		iv_antivirus_scanner = (ImageView) findViewById(R.id.iv_antivirus_scanner);
    
      		pb_antivirus_progressbar = (ProgressBar) findViewById(R.id.pb_antivirus_progressbar);
    
      		tv_antivirus_name = (TextView) findViewById(R.id.tv_antivirus_name);
    
      		ll_antivirus_safeapk = (LinearLayout) findViewById(R.id.ll_antivirus_safeapk);
    
      		
    
      		//旋转动画
    
      		//参数1:开始的角度
    
      		//参数2:结束的角度
    
      		//剩下的参数:控制动画的位置
    
      		RotateAnimation rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f,  Animation.RELATIVE_TO_SELF, 0.5f);
    
      		
    
      		rotateAnimation.setDuration(2000);
    
      		//旋转的次数
    
      		rotateAnimation.setRepeatCount(Animation.INFINITE);//INFINITE : 一直旋转
    
      		//解决旋转停顿的问题
    
      		LinearInterpolator interpolator = new LinearInterpolator();
    
      		rotateAnimation.setInterpolator(interpolator);
    
      		iv_antivirus_scanner.startAnimation(rotateAnimation);
    
      		
    
      		scanner();
    
      	}
    
      	/**
    
  • 扫描程序
     */
     private void scanner() {
     //1.获取包的管理者
     final PackageManager pm = getPackageManager();
     tv_antivirus_name.setText(“正在初始化64核扫描引擎…”);
     new Thread(){
     public void run() {
     //5.睡1秒,让程序延时1秒钟扫描
     SystemClock.sleep(1000);

      				//2.获取安装的程序   参数必须设置标签,才可以获取到(在log中)签名信息(特别长) 否则会报空指针异常
    
      				List<PackageInfo> installedPackages = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
    
      
    
      				//3.设置progerssbar的总进度
    
      				pb_antivirus_progressbar.setMax(installedPackages.size());
    
      				//4.设置当前进度
    
      				int progress = 0;
    
      				for (PackageInfo packageInfo : installedPackages) {
    
      					//5.睡100毫秒,增加进度显示的真实性
    
      					SystemClock.sleep(100);
    
      					//4.设置当前进度
    
      					progress++;
    
      					pb_antivirus_progressbar.setProgress(progress);
    
      					//6.获取应用的名称
    
      					final String name = packageInfo.applicationInfo.loadLabel(pm).toString();
    
      					runOnUiThread(new Runnable() {
    
      						
    
      						@Override
    
      						public void run() {
    
      							//7.设置扫描的文字
    
      							tv_antivirus_name.setText("正在扫描:"+name);
    
      
    
      							//获取应用的签名数组
    
      							Signature[] singnatures = packageInfo.signatures;
    
      							//将获取签名信息转化成字符串
    
      							String singnature = singnatures[0].toCharsString();
    
      							//转化成特征码   签名信息特别长,所以转化成MD5
    
      							singnature = MD5Utils.digestPassword(singnature);
    
      
    
      							System.out.println(name+"签名信息:"+singnature);
    
      
    
      							// ③ 调用查询数据库的操作,判断应用是否是病毒
    
      							boolean b = AntiVirusDao.isAntiViruse(getApplicationContext(), singnature);
    
      
    
      							//9.设置在下方空白处显示扫描显示的应用成名称
    
      							TextView textView = new TextView(getApplicationContext());
    
      							textView.setText(name);
    
      							textView.setTextColor(Color.BLACK);
    
      
    
      							// ④ 根据返回的boolean值,设置显示的应用名称文本颜色
    
      							if (b) {
    
      								textView.setTextColor(Color.RED);
    
      
    
      								list.add(packageInfo.packageName);
    
      
    
      							}else{
    
      								textView.setTextColor(Color.BLACK);
    
      							}
    
      
    
      							ll_antivirus_safeapk.addView(textView, 0);//index:将view添加到哪个位置
    
      						}
    
      					});
    
      				}
    
      
    
      				runOnUiThread(new Runnable() {
    
      					
    
      					@Override
    
      					public void run() {
    
      			
    
      
    
      						if (list.size() > 0) {
    
      						tv_antivirus_name.setText("扫描完成,发现病毒");
    
      						
    
      						
    
      
    
      						//发现病毒,提醒用户是否卸载
    
      						AlertDialog.Builder builder = new Builder(AntivirusActivity.this);
    
      						builder.setTitle("警告!");
    
      						builder.setIcon(R.drawable.ic_launcher);
    
      						builder.setMessage("发现"+list.size()+"个病毒");
    
      						builder.setPositiveButton("卸载", new DialogInterface.OnClickListener() {
    
      								
    
      								@Override
    
      								public void onClick(DialogInterface dialog, int which) {
    
      									
    
      									}
    
      								}
    
      							});
    
      							builder.setNegativeButton("暂不处理", null);
    
      							builder.show();
    
      
    
      						}else{
    
      							//8.设置扫描完成的文字和停止动画操作
    
      							tv_antivirus_name.setText("扫描完成,很安全");
    
      						}
    
      						//停止动画
    
      						iv_antivirus_scanner.clearAnimation();
    
      					}
    
      				});
    
      			};
    
      		}.start();
    
      		}
    
      	}
    

    那在onClick中就可以执行卸载的操作,卸载的操作在讲软件管理SoftManagerActivity的时候讲过,直接拷贝过来

      //卸载的操作
    
      Intent intent = new Intent();
    
      intent.setAction("android.intent.action.DELETE");
    
      intent.addCategory("android.intent.category.DEFAULT");
    
      intent.setData(Uri.parse("package:"+appInfo.getPacakgeName()));//content://
    
      startActivityForResult(intent, 0);
    

    这里还会需要一个包名,那这个病毒的包名保存在了list集合里边了,又因为有两个病毒,所以用循环去处理,那用普通for循环去处理,并将包名appInfo.getPacakgeName()改成list.get(i)

      runOnUiThread(new Runnable() {
    
      			
    
      	@Override
    
      	public void run() {
    
      		if (list.size() > 0) {
    
      			tv_antivirus_name.setText("扫描完成,发现病毒");
    
      			//发现病毒,提醒用户是否卸载
    
      			AlertDialog.Builder builder = new Builder(AntivirusActivity.this);
    
      			builder.setTitle("警告!");
    
      			builder.setIcon(R.drawable.ic_launcher);
    
      			builder.setMessage("发现"+list.size()+"个病毒");
    
      			builder.setPositiveButton("卸载", new DialogInterface.OnClickListener() {
    
      				
    
      				@Override
    
      				public void onClick(DialogInterface dialog, int which) {
    
      					//卸载的操作
    
      					for (int i = 0; i < list.size(); i++) {
    
      						Intent intent = new Intent();
    
      						intent.setAction("android.intent.action.DELETE");
    
      						intent.addCategory("android.intent.category.DEFAULT");
    
      						intent.setData(Uri.parse("package:"+list.get(i)));//content://
    
      						startActivityForResult(intent, 0);	
    
      					}
    
      				}
    
      			});
    
      			builder.setNegativeButton("暂不处理", null);
    
      			builder.show();
    
      		}else{
    
      			//8.设置扫描完成的文字和停止动画操作
    
      			tv_antivirus_name.setText("扫描完成,很安全");
    
      		}
    
      		//停止动画
    
      		iv_antivirus_scanner.clearAnimation();
    
      	}
    
      });
    

    运行程序,弹出了对话框,提示“发现2个病毒”,点击卸载就可以一路卸载掉了,那退出程序,再进去扫描,就提示“扫描完成,很安全”了

    到这,扫描病毒,杀死病毒的操作就完成了

    那杀毒操作总结如下:

    1.拷贝数据库

      找到splashactivity.java中的onCreate方法,在这里边有个copyDb();注意,以前在这里拷贝address.db数据库的时候,是把address.db数据库名字写死了,即:
    
    
    
      	private void copyDb(){
    
      		File file = new File(getFilesDir(), "address.db");
    
      	以及
    
      		// 2.通过assets管理者打开数据库
    
      			in = assets.open("address.db");
    
    
    
      	此时我们又要拷贝antivirus.db数据库,写死了是肯定不行的,所以可以这样干,在private void copyDb()方法中,改为:
    
    
    
      	private void copyDb(String name) {
    
      	File file = new File(getFilesDir(), name);
    
      	以及
    
      	// 2.通过assets管理者打开数据库
    
      		 in = assets.open(name);
    
    
    
      	然后把onCreate中调用的方法改为:
    
    
    
      		copyDb("address.db");
    
      		copyDb("antivirus.db");
    
    
    
      	就可以实现拷贝多个数据库了
    
      
    
      接下来运行一下应用中的手机杀毒功能,看数据库有没有拷贝成功,找到ddms,打开data/data 找到我们的应用cn.itcast.mobilesafexian02下的files,里面有antivirus.db数据库,这就说明我们拷贝成功了
    

    2.查询数据,参考addressDao.java,创建AntiVirusDao.java如下:

         public class AntiVirusDao {
    
      		/**
    
  • 查询是否是病毒
     */
     //判断应用否是病毒,返回的是boolean值,参数需要上下文,我们比较的时候是比较MD5值,所以还要传入MD5过来
     public static boolean isAntiViruse(Context context,String md5){
     File file = new File(context.getFilesDir(), “antivirus.db”);
     //打开数据库
     SQLiteDatabase database = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
     //查询数据库 参数:1:表名 antivirus.db数据库中的datable表 3:查询条件 4.查询参数 
     Cursor cursor = database.query(“datable”, null, “md5=?”, new String[]{md5}, null, null, null, null);
     if (cursor.moveToNext()) {
     //数据库中有查询的数据的话返回true 表明是病毒
     return true;
     }
     return false;
     }
     }

      通过MD5查询条件查询每个MD5,对应的是一条数据,所以这里用的是if,如果查询的是多条记录就要用while
    

    查询数据完成后,就要调用这个方法了

    3.调用查询数据库的操作,判断应用是否是病毒(在AntiVirusActivity.java中//转化成特征码 后边添加如下代码:)

      boolean b = AntiVirusDao.isAntiViruse(getApplicationContext(), singnature);
    

    4.根据返回boolean值设置显示应用的名称文本颜色 对比金山卫士病毒查杀的下拉列表,当是病毒的时候,它的颜色就会不一样

      if (b) {
    
      	//如果是true,表明是病毒 那就将它的值改成红色
    
      	textView.setTextColor(Color.RED);
    
      	list.add(packageInfo.packageName);
    
      }else{
    
      	textView.setTextColor(Color.BLACK);
    
      }
    

    此时在运行应用测试一下,发现病毒查杀的下拉列表中没有病毒,为了测试用,我们可以安装两个上边准备好的病毒getkey.apk,t2t.apk

    cmd打开dos窗口,输入命令adb -s emulator-5554 install 然后将getkey.apk拖进来,回车,即可将病毒getkey.apk安装到相应的模拟器中,重复上边的步骤,将病毒t2t.apk也安装进去

    安装成功后,再次运行应用的手机杀毒功能,就会在下来列表出现两个红色名称的应用,但问题又来了,发现两个病毒了,还显示扫描完成,很安全,显然是不行的,所以还要改改这个显示

    5.创建一个list集合,用来存储病毒应用的包名,并在发现有病毒的时候,添加到集合中

    6.在扫描完成后根据list集合的长度,判断是否有病毒,有 提醒并卸载

      if (list.size() > 0) {
    
      		tv_antivirus_name.setText("扫描完成,发现病毒");
    
      		//发现病毒,提醒用户是否卸载
    
      		AlertDialog.Builder builder = new Builder(AntivirusActivity.this);
    
      		builder.setTitle("警告!");
    
      		builder.setIcon(R.drawable.ic_launcher);
    
      		builder.setMessage("发现"+list.size()+"个病毒");
    
      		builder.setPositiveButton("卸载", new DialogInterface.OnClickListener() {
    
      			
    
      			@Override
    
      			public void onClick(DialogInterface dialog, int which) {
    
      				//卸载的操作
    
      				for (int i = 0; i < list.size(); i++) {
    
      					Intent intent = new Intent();
    
      					intent.setAction("android.intent.action.DELETE");
    
      					intent.addCategory("android.intent.category.DEFAULT");
    
      					intent.setData(Uri.parse("package:"+list.get(i)));//content://
    
      					startActivityForResult(intent, 0);	
    
      				}
    
      			}
    
      		});
    
      		builder.setNegativeButton("暂不处理", null);
    
      		builder.show();
    
      	}else{
    
      		//8.设置扫描完成的文字和停止动画操作
    
      		tv_antivirus_name.setText("扫描完成,很安全");
    
      	}
    
      	//停止动画
    
      	iv_antivirus_scanner.clearAnimation();
    

12.4 缓存清理框架 #

下边实现下缓存清理

到HomeActivity中gridView的onItemClick方法的swich方法中,增加:

	case 6://缓存清理

		Intent intent6 = new Intent(HomeActivity.this,ClearCacheActivity.class);

		startActivity(intent6);

		break;

	需要一个ClearCacheActivity,new出来,然后到清单文件注册,然后重写onCreate方法,并用setContentView加载布局,需要activity_clearcache.xml,创建出来:



	activity_clearcache.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

	        android:id="@+id/textView1"

	        android:layout_width="match_parent"

	        android:layout_height="wrap_content"

	        android:text="缓存清理" 

	        android:textSize="25sp"

	        android:gravity="center_horizontal"

	        android:paddingTop="10dp"

	        android:paddingBottom="10dp"

	        android:background="#8866ff00"/>

	    <!-- 是fragment用来替换的布局控件 

	    	layout_weight : 渲染优先级,值越大优先级越低,靠后渲染

	    -->

	    <RelativeLayout 

	        android:id="@+id/rl_clearcache_relative"

	        android:layout_width="match_parent"

	        android:layout_height="match_parent"

	        android:layout_weight="1"

金山卫士中缓存清理界面是什么样?下边是三个切换卡,分别是“缓存清理”,“sd卡清理”,“痕迹清理”,那这个效果,使用Fragment就可以实现



Fragment的使用:



	一起看下api文档,打开sdk/docs/index.html

	选择APIGuides,再点击Acrtivities下的Fragments,然后点击Fragment



	看到Fragment生命周期,onAttach,onCreate,onCreateView,onActivityCreated,onViewStateRestored,onStart,onResume,onPause,onStop,onDestroy,onDetach,



	看下onCreateView方法:



		参数LayoutInflater:布局文件加载器

		参数ViewGroup: 容器 这个容器一般不用,但是有时候就需要将这个加载后的布局放到容器里边

		参数savedInstanceState:是否保存状态



		inflater.inflate(resource,root,attachToRoot)中的这三个参数,对应的就是onCreateView的这三个参数

		参数resource:布局文件

		参数root:容器

		参数attachToRoot:是否挂载,一般都不会用这个东西,所以一般写成false





需要创建两个Fragment,一个是缓存清理,一个是sd卡清理,先创建一个Fragment



创建一个CacheFragment,继承自V4包下Fragment,然后实现下onCreateView方法



	public class CacheFragment extends Fragment {



		@Override

		public View onCreateView(LayoutInflater inflater, ViewGroup container,

				Bundle savedInstanceState) {

	

			return inflater.inflate(resource,root,attachToRoot);

		}

	}



fragment的onCreateView方法类似activity的oncreate方法用来加载布局



那这个resource布局文件还没有,创建出来:



	fragment_cache.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 

	          android:id="@+id/tv_antivirus_name"

	          android:layout_width="match_parent"

	          android:layout_height="wrap_content"

	          android:text="正在扫描"

	          android:layout_marginTop="25dp"

	          android:layout_marginLeft="10dp"

	          android:layout_marginRight="10dp"

	          android:singleLine="true"

	          />

			<!-- progress : 设置progerssbar的进度 -->

	      <ProgressBar

	          android:id="@+id/pb_antivirus_progressbar"

	          style="?android:attr/progressBarStyleHorizontal"

	          android:layout_width="match_parent"

	          android:layout_height="wrap_content"

	          android:layout_marginLeft="10dp"

	          android:layout_marginRight="10dp"

	          android:progressDrawable="@drawable/anti_progressbar_drawable"

	          android:layout_marginTop="5dp"

	          />

	      <Button 

	          android:id="@+id/btn_cache_clear"

	          android:layout_width="match_parent"

	          android:layout_height="wrap_content"

	          android:text="立即清理"

	          android:background="@drawable/selector_contact_button"

	          android:visibility="gone"

	          />

	      <ListView 

	          android:id="@+id/lv_cache_caches"

	          android:layout_width="match_parent"

	          android:layout_height="match_parent"

以上布局,视频中有给添加背景为红色#ff0000来简单测试,此处略,直接使用了代码中的布局

然后就可以在参数resource处加载布局,参数root就写成container,参数attachToRoot写成false



	public class CacheFragment extends Fragment {



		@Override

		public View onCreateView(LayoutInflater inflater, ViewGroup container,

				Bundle savedInstanceState) {

	

			return inflater.inflate(R.layout.fragment_cache,container,false);

		}

	}



接下来可以拷贝一份,改成SDFragment和fragment_sd.xml,然后给fragment_sd.xml添加背景为蓝色#0000ff测试,此处略,直接使用了代码中的布局



	public class SDFragment extends Fragment {



		@Override

		public View onCreateView(LayoutInflater inflater, ViewGroup container,

				Bundle savedInstanceState) {

	

			return inflater.inflate(R.layout.fragment_sd,container,false);

		}

	}



fragment_sd.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" 

	    android:background="#0000ff"

	

	</LinearLayout>



这两个Fragment都有了之后,接下来让它显示到缓存清理界面ClearCacheActivity中去,要想对Fragment进行操作,

必须让ClearCacheActivity继承自V4包下的FragmentActivity

然后就可以在onCreate方法中new出来这两个Fragment



	public class ClearCacheActivity extends FragmentActivity {



		private CacheFragment cacheFragment;

		private SDFragment sdFragment;



		@Override

		protected void onCreate(Bundle savedInstanceState) {

			super.onCreate(savedInstanceState);

			setContentView(R.layout.activity_clearcache);



			cacheFragment = new CacheFragment();

			sdFragment = new SDFragment();

			

	    }

	}



接下来把fragment添加到ClearCacheActivity的布局文件activity_clearcache.xml里边

要想把Fragment添加到布局文件里边,必须得有一个控件告诉fragment,你要进行替换才行



那接下来就是把缓存界面activity_clearcache.xml写出来,它底部应该有两个按钮,一个是缓存清理,一个是SD卡清理

并用android:background="@drawable/selector_contact_button"属性,给这两个按钮设置了状态选择器



还应该有一个承载Fragment的控件,那用RelativeLayout来给Fragment占位,id设置为rl_clearcache_relative	

意思是将来会用Fragment把这个RelativeLayout控件给替换掉	



	activity_clearcache.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

	        android:id="@+id/textView1"

	        android:layout_width="match_parent"

	        android:layout_height="wrap_content"

	        android:text="缓存清理" 

	        android:textSize="25sp"

	        android:gravity="center_horizontal"

	        android:paddingTop="10dp"

	        android:paddingBottom="10dp"

	        android:background="#8866ff00"

			/>

	    <!-- 是fragment用来替换的布局控件 

	    	layout_weight : 渲染优先级,值越大优先级越低,靠后渲染

	    -->

	    <RelativeLayout 

	        android:id="@+id/rl_clearcache_relative"

	        android:layout_width="match_parent"

	        android:layout_height="match_parent"

	        android:layout_weight="1"

到ClearCacheActivity的onCreate中,初始化RelativeLayout控件

需要获取fragment的管理者,并调用它里边的beginTransaction,来保证fragment切换的一致性

然后用beginTransaction去add(arg0,arg1)这两个Fragment

	参1表示被替换的控件的id,参数2表示替换的fragment

那这里被替换的控件就是那个RelativeLayout,它的id为rl_clearcache_relative

最后记得要commit()提交事务



用Fragment替换占位的RelativeLayout时,除了用add(arg0,arg1)方法,还可以用replace(arg0,arg1)方法

参数和add的相似



布局下边有两个按钮,给缓存清理按钮设置点击事件cache,SD卡清理按钮设置sd



到ClearCacheActivity中实现点击方法



那上边的代码中如果不调用beginTransaction.hide(sdFragment)这行代码将sdFragment隐藏掉的话

显示的将是这个蓝色的sdFragment,它会将红色的cacheFragment掩盖掉,调用这行代码之后,显示的就是红色的cacheFragment了



我们的需求是点击缓存清理按钮,显示的是缓存清理的cacheFragment

点击SD卡清理按钮,显示的是SD卡清理的sdFragment



那可以在cache方法中,用fragmentManager去调用beginTransaction()拿到事务

拿到事务beginTransaction()之后可以调用hide方法将sdFragment隐藏掉

紧接着它里边有个show方法,显示我们的cacheFragment,最后commit提交事务,在sd方法中操作相反



		public class ClearCacheActivity extends FragmentActivity {

	

			private CacheFragment cacheFragment;

			private SDFragment sdFragment;

			private RelativeLayout rl_clearcache_relative;

			private FragmentManager fragmentManager;

	

			@Override

			protected void onCreate(Bundle savedInstanceState) {

				super.onCreate(savedInstanceState);

				setContentView(R.layout.activity_clearcache);

				

				rl_clearcache_relative = (RelativeLayout) findViewById(R.id.rl_clearcache_relative);

	

				cacheFragment = new CacheFragment();

				sdFragment = new SDFragment();

	

				//1.获取fragment的管理者

				fragmentManager = getSupportFragmentManager();

	

				//2.获取事务,保证fragment切换的一致性

				FragmentTransaction beginTransaction = fragmentManager.beginTransaction();

	

				//3.添加fragment

				//参数1:被替换的控件的id

				//参数2:替换的fragment

				beginTransaction.add(R.id.rl_clearcache_relative, cacheFragment);

	

				//跟add效果相似

				//beginTransaction.replace(arg0, arg1)

				beginTransaction.add(R.id.rl_clearcache_relative, sdFragment);

	

				//3.1隐藏fragment

				beginTransaction.hide(sdFragment);

				beginTransaction.commit();

				

		    }

	

			public void cache(View v){

				FragmentTransaction beginTransaction = fragmentManager.beginTransaction();

				beginTransaction.hide(sdFragment);

				beginTransaction.show(cacheFragment);//显示fragment

				beginTransaction.commit();

			}

	

			public void sd(View v){

				FragmentTransaction beginTransaction = fragmentManager.beginTransaction();

				beginTransaction.hide(cacheFragment);

				beginTransaction.show(sdFragment);//显示fragment

				beginTransaction.commit();

			}

		}



运行程序,点击缓存清理,然后点击缓存清理按钮,就显示的是红色的CacheFragment,点击SD卡清理,就显示的是蓝色的SDFragment



到这里,缓存清理的框架就搭完了



缓存清理的框架总结如下:



1.创建两个fragment,继承v4包下的fragment



	public class CacheFragment extends Fragment {

	}



	public class SDFragment extends Fragment {

	}



2.给两个Fragment分别设置布局布局文件,并加载



	public class CacheFragment extends Fragment {



		//类似activity中oncreate方法,加载布局

		//参数1:布局文件加载

		//参数2:容器

		//参数3:是否保存状态

		@Override

		public View onCreateView(LayoutInflater inflater, ViewGroup container,

				Bundle savedInstanceState) {

			//参数1:布局文件

			//参数2:容器

			//参数3:是否挂载,一般false

			return inflater.inflate(R.layout.fragment_cache, container, false);

		}

	}



3.将clearcacheactivity,由继承自Activity改为继承自V4包下的fragmentActivity



	public class ClearCacheActivity extends FragmentActivity {

	}



4.修改clearcacheactivity的布局activity_clearcache.xml



修改布局activity_clearcache.xml时,我一般喜欢用RelativeLayout占位,你也可以用FrameLayout,LinearLayout占位都可以





	<?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

	        android:id="@+id/textView1"

	        android:layout_width="match_parent"

	        android:layout_height="wrap_content"

	        android:text="缓存清理" 

	        android:textSize="25sp"

	        android:gravity="center_horizontal"

	        android:paddingTop="10dp"

	        android:paddingBottom="10dp"

	        android:background="#8866ff00"

			/>

	    <!-- 是fragment用来替换的布局控件 

	    	layout_weight : 渲染优先级,值越大优先级越低,靠后渲染

	    -->

	    <RelativeLayout 

	        android:id="@+id/rl_clearcache_relative"

	        android:layout_width="match_parent"

	        android:layout_height="match_parent"

	        android:layout_weight="1"

5.添加fragment,在ClearCacheActivity的oncreate中添加这两个Fragment,步骤如下:



		cacheFragment = new CacheFragment();

		sdFragment = new SDFragment();

		

		//1.获取fragment的管理者

		fragmentManager = getSupportFragmentManager();

		//2.获取事务,保证fragment切换的一致性

		FragmentTransaction beginTransaction = fragmentManager.beginTransaction();

		//3.添加fragment

		//参数1:被替换的控件的id

		//参数2:替换的fragment

		beginTransaction.add(R.id.rl_clearcache_relative, cacheFragment);

		//跟add效果相似

//		beginTransaction.replace(arg0, arg1)

		beginTransaction.add(R.id.rl_clearcache_relative, sdFragment);

		//3.1隐藏fragment

		beginTransaction.hide(sdFragment);

		beginTransaction.commit();



6.缓存清理按钮和SD卡清理按钮的操作



		public void cache(View v){

			FragmentTransaction beginTransaction = fragmentManager.beginTransaction();

			beginTransaction.hide(sdFragment);

			beginTransaction.show(cacheFragment);//显示fragment

			beginTransaction.commit();

		}

		public void sd(View v){

			FragmentTransaction beginTransaction = fragmentManager.beginTransaction();

			beginTransaction.hide(cacheFragment);

			beginTransaction.show(sdFragment);//显示fragment

			beginTransaction.commit();

		}



	到这,缓存清理的界面就有了

12.4进度条的操作#

上边将缓存清理界面框架都实现了,接下来实现它里边的操作



缓存清理和杀毒很相似,都是一个进度条来显示,可以从Activity_Antivirus.xml中把关于进度条布局拷贝到fragment_cache.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 

	          android:id="@+id/tv_antivirus_name"

	          android:layout_width="match_parent"

	          android:layout_height="wrap_content"

	          android:text="正在扫描"

	          android:layout_marginTop="25dp"

	          android:layout_marginLeft="10dp"

	          android:layout_marginRight="10dp"

	          android:singleLine="true"/>

			<!-- progress : 设置progerssbar的进度 -->

	      <ProgressBar

	          android:id="@+id/pb_antivirus_progressbar"

	          style="?android:attr/progressBarStyleHorizontal"

	          android:layout_width="match_parent"

	          android:layout_height="wrap_content"

	          android:layout_marginLeft="10dp"

	          android:layout_marginRight="10dp"

	          android:progressDrawable="@drawable/anti_progressbar_drawable"

	          android:layout_marginTop="5dp"/>

	      <Button 

	          android:id="@+id/btn_cache_clear"

	          android:layout_width="match_parent"

	          android:layout_height="wrap_content"

	          android:text="立即清理"

	          android:background="@drawable/selector_contact_button"

	          android:visibility="gone"/>

	      <ListView 

	          android:id="@+id/lv_cache_caches"

	          android:layout_width="match_parent"

	          android:layout_height="match_parent"

到CacheFragment的onCreateView中初始化id



	public class CacheFragment extends Fragment {

	

			private TextView tv_antivirus_name;

			private ProgressBar pb_antivirus_progressbar;

		



			//类似activity中oncreate方法,加载布局

			//参数1:布局文件加载

			//参数2:容器

			//参数3:是否保存状态

			@Override

			public View onCreateView(LayoutInflater inflater, ViewGroup container,

					Bundle savedInstanceState) {

				//参数1:布局文件

				//参数2:容器

				//参数3:是否挂载,一般false

				View view = inflater.inflate(R.layout.fragment_cache, container, false);

				tv_antivirus_name = (TextView) view.findViewById(R.id.tv_antivirus_name);

				pb_antivirus_progressbar = (ProgressBar) view.findViewById(R.id.pb_antivirus_progressbar);

				return view;

			}

		}



控件初始化完了之后,接下来是要执行扫描的操作



在AntivirusActivity中有扫描方法scanner(),这里的扫描操作要在CacheFragment的哪个方法里边做



来api中看下Fragment的生命周期,onCreateView方法中是用来加载布局的,紧接着执行了onActivityCreated方法

那可以把扫描的操作放到onActivityCreated方法中来做



在CacheFragment中重写onActivityCreated方法,在它里边调用scanner()方法,然后将AntivirusActivity中的扫描方法scanner()拷贝到CacheFragment中



那原来的scanner方法中调用getPackageManager	()是用了this,但是在Fragment里边是没有this的,可以写成getActivity().getPackageManager()



getActivity()是用来获取fragment挂载的activity,这样就可以获取到PacakgeManager



	public class CacheFragment extends Fragment {

	

			private TextView tv_antivirus_name;

			private ProgressBar pb_antivirus_progressbar;

		



			//类似activity中oncreate方法,加载布局

			//参数1:布局文件加载

			//参数2:容器

			//参数3:是否保存状态

			@Override

			public View onCreateView(LayoutInflater inflater, ViewGroup container,

					Bundle savedInstanceState) {

				//参数1:布局文件

				//参数2:容器

				//参数3:是否挂载,一般false

				View view = inflater.inflate(R.layout.fragment_cache, container, false);

				tv_antivirus_name = (TextView) view.findViewById(R.id.tv_antivirus_name);

				pb_antivirus_progressbar = (ProgressBar) view.findViewById(R.id.pb_antivirus_progressbar);

				return view;

			}



			@Override

			public void onActivityCreated(Bundle savedInstanceState) {

					super.onActivityCreated(savedInstanceState);

					scanner();

			}



		/**
  • 扫描
     */
     private void scanner() {
     pm = getActivity().getPackageManager();
     tv_antivirus_name.setText(“正在初始化64核缓存清理引擎…”);
     new Thread() {
     public void run() {
     // 5.睡1秒,让程序延时1秒钟扫描
     SystemClock.sleep(1000);
     // 获取安装的程序
     List installedPackages = pm
     .getInstalledPackages(0);
     // 设置总进度
     pb_antivirus_progressbar.setMax(installedPackages.size());
     // 设置当前进度
     int progress = 0;
     for (PackageInfo packageInfo : installedPackages) {
     // 5.睡100毫秒,增加进度显示的真实性
     SystemClock.sleep(100);
     // 设置进度
     progress++;
     pb_antivirus_progressbar.setProgress(progress);
     // 获取应用程序名称
     final String name = packageInfo.applicationInfo.loadLabel(
     pm).toString();

      					getActivity().runOnUiThread(new Runnable() {
    
    
    
      					@Override
    
      					public void run() {
    
    
    
      						tv_antivirus_name.setText("正在扫描:"+name);
    
    
    
      					}
    
      				});
    
      				}
    
      			};
    
      		}.start();
    
      	 }
    
          }
    

    运行程序,点击缓存清理,正在扫描xxx,此时点击物理返回键,会导致奔溃

    原因:退出时,正在扫描,这个是在fragment中经常出现的bug

    从logcat中看到是空指针异常,在CacheFragment中的第68行:

      getActivity().runOnUiThread(new Runnable() {
    

    一般空指针异常都是null.方法,说明是这个getActivity()为null

    你在退出的时,"正在扫描xxx"文字是在子线程当中的,所以这个还是在执行的,所以在退出时,子线程还在运行,还没被及时处理

    所以这个getActivity出现为null的现象

    既然为空了,判断一下就可以了,退出之后,子线程中的正在扫描xxx因为看不到了,显示不显示都无所谓了,退出之后,判断这个getActivity()为null了,那你还得运行一段时间,那我不管你了,你去运行你的

      if (getActivity() != null) {
    
      	getActivity().runOnUiThread(new Runnable() {
    
      				@Override
    
      				public void run() {
    
      					tv_antivirus_name.setText("正在扫描:"+name);
    
      				}
    
      			});
    
      }
    

    运行程序,点击缓存清理,显示正在扫描xxx,点击返回键就不会出现奔溃了

    当扫描完了之后,应该让"正在扫描xxx"变为显示扫描出现的数据“共扫描到1项缓存数据,总大小4.00KB”

    那当for循环扫描完了之后,还得写一个runOnUiThread方法,在它里边先隐藏掉进度条和textView文本:

      public class CacheFragment extends Fragment {
    
      
    
      		private TextView tv_antivirus_name;
    
      		private ProgressBar pb_antivirus_progressbar;
    
      	
    
    
    
      		//类似activity中oncreate方法,加载布局
    
      		//参数1:布局文件加载
    
      		//参数2:容器
    
      		//参数3:是否保存状态
    
      		@Override
    
      		public View onCreateView(LayoutInflater inflater, ViewGroup container,
    
      				Bundle savedInstanceState) {
    
      			//参数1:布局文件
    
      			//参数2:容器
    
      			//参数3:是否挂载,一般false
    
      			View view = inflater.inflate(R.layout.fragment_cache, container, false);
    
      			tv_antivirus_name = (TextView) view.findViewById(R.id.tv_antivirus_name);
    
      			pb_antivirus_progressbar = (ProgressBar) view.findViewById(R.id.pb_antivirus_progressbar);
    
      			return view;
    
      		}
    
    
    
      		@Override
    
      		public void onActivityCreated(Bundle savedInstanceState) {
    
      				super.onActivityCreated(savedInstanceState);
    
      				scanner();
    
      		}
    
    
    
      	/**
    
  • 扫描
     */
     private void scanner() {
     pm = getActivity().getPackageManager();
     tv_antivirus_name.setText(“正在初始化64核缓存清理引擎…”);
     new Thread() {
     public void run() {
     // 5.睡1秒,让程序延时1秒钟扫描
     SystemClock.sleep(1000);
     // 获取安装的程序
     List installedPackages = pm
     .getInstalledPackages(0);
     // 设置总进度
     pb_antivirus_progressbar.setMax(installedPackages.size());
     // 设置当前进度
     int progress = 0;
     for (PackageInfo packageInfo : installedPackages) {
     // 5.睡100毫秒,增加进度显示的真实性
     SystemClock.sleep(100);
     // 设置进度
     progress++;
     pb_antivirus_progressbar.setProgress(progress);
     // 获取应用程序名称
     final String name = packageInfo.applicationInfo.loadLabel(
     pm).toString();

      					if (getActivity() != null) {
    
      						getActivity().runOnUiThread(new Runnable() {		
    
      							@Override
    
      							public void run() {
    
      	
    
      								tv_antivirus_name.setText("正在扫描:"+name);
    
      	
    
      							}
    
      					  });
    
      					}
    
      				}
    
    
    
      				if (getActivity() != null) {
    
      					getActivity().runOnUiThread(new Runnable() {
    
      						@Override
    
      						public void run() {
    
    
    
      								// 扫描完成隐藏进度条和textivew文本
    
      								tv_antivirus_name.setVisibility(View.GONE);
    
      								pb_antivirus_progressbar.setVisibility(View.GONE);
    
    
    
      								}
    
      							});
    
      						}
    
      			};
    
      		}.start();
    
      	 }
    
          }
    

    运行程序,点击缓存清理,出现“正在扫描xxx”,扫描结束后,textView和progressbar直接就隐藏掉了

    到这,进度条的操作就做完了,这个就是模仿前边的操作

    进度条的操作总结如下:

    参考杀毒进度条操作

      /**
    
  • 扫描
     */
     private void scanner() {
     //1.获取包的管理者
     //getActivity() : 获取fragment挂载的activity
     final PackageManager pm = getActivity().getPackageManager();
     tv_antivirus_name.setText(“正在初始化64核缓存清理引擎…”);
     new Thread(){
     public void run() {
     //5.睡1秒,让程序延时1秒钟扫描
     SystemClock.sleep(1000);
     //获取安装的程序
     List installedPackages = pm.getInstalledPackages(0);
     //设置总进度
     pb_antivirus_progressbar.setMax(installedPackages.size());
     //设置当前进度
     int progress = 0;
     for (PackageInfo packageInfo : installedPackages) {
     //5.睡100毫秒,增加进度显示的真实性
     SystemClock.sleep(100);
     //设置进度
     progress++;
     pb_antivirus_progressbar.setProgress(progress);
     //获取应用程序名称
     final String name = packageInfo.applicationInfo.loadLabel(pm).toString();
     //在退出的时候,子线程还在运行,还没被及时处理,所以getActivity()会出现为null的现象
     if (getActivity() != null) {
     getActivity().runOnUiThread(new Runnable() {

      						@Override
    
      						public void run() {
    
      							tv_antivirus_name.setText("正在扫描:"+name);
    
      						}
    
      					});
    
      				}
    
      			}
    
      			if (getActivity() != null) {
    
      				getActivity().runOnUiThread(new Runnable() {
    
      					
    
      					@Override
    
      					public void run() {
    
      						//隐藏进度条和textivew文本
    
      						tv_antivirus_name.setVisibility(View.GONE);
    
      						pb_antivirus_progressbar.setVisibility(View.GONE);
    
      					}
    
      				});
    
      			}
    
      		};
    
      	}.start();
    
      }
    

12.5获取缓存的操作#

上节课实现了扫描的操作,扫描就是为了看哪些应用有缓存



什么是缓存呢?

	打卡DDMS,点击data/cn.itcast.mobilesafexian02/cache

	每个应用都有这么个cache目录,在这个目录下边会保存一些数据



写一个示例工程“写入缓存”,先写入一个缓存给大家演示下



写入缓存很简单,就是java基础班的知识,找到MainActivity

	首先要拿到缓存目录,所以先在onCreate方法中new一个File(dir,name)

		参数dir:写成getCacheDir()来拿到缓存的目录,参数name写成“aaa.txt”



接下来就要往里边去写数据了,使用一个FileWriter,往里边写的是字符,所以就用一个字符流就可以了,这个有了异常,捕获下



然后writer中有个write(str),它可以输入一个字符串,这里边写个“aaabbbcccddd”,然后要刷新一下,最后再close()关闭掉



		public class MainActivity extends Activity{

	

			@Override

			protected void onCreate(Bundle savedInstanceState){

					super.onCreate(savedInstanceState);

					setContentView(R.layout.activity_main);

					

					File file = new File(getCacheDir(),"aaa.txt");

					

					try{

					FileWriter writer = new FileWriter(file);

					writer.write("aaabbccdd");

					writer.flush();

					writer.close();

					} catch(IOException e){

						e.printStackTrace();

					}

			}

		}



运行示例工程,然后打开DDMS,找到示例工程“写入缓存”的data/cache/cn.itcast.cache/cache

有一个aaa.txt,那导出来看下,内容就是"aaabbccdd"



那把示例工程运行到低版本的模拟器2.3.3上(因为金山卫士是运行在了这个模拟器上),然后打开金山卫士中的垃圾清理,扫描出这个“写入缓存”项目中有缓存,4kb



现在可以写入缓存了,那就获取下缓存,怎么去获取缓存大小



在模拟器的设置中心看到示例工程“写入缓存”的缓存大小是4KB



看下系统是怎么获取到这个缓存的,参考下系统的做法,这就是前边教大家的,怎么借鉴别人的代码



	首先看到模拟器设置中心中显示的示例工程“写入缓存”的缓存信息这有 “Cache”这样一个文字

	右键eclipse中的Settings项目,然后ctrl+搜索下,并设置是在*xml中去找

	然后会在strings.xml中找到如下:

	<string name="Cache_header_label">Cache</string>

	<string name="clear_size_label">Cache</string>



	然后一路去追踪,最后发现它是new了一个IPackageStatesObserver.Stub,既然是new出来的,就表明它是内部类

	既然是一个内部类,那我们也可以拿它来用了,在它里边有一个onGetStatsCompleted这样一个方法,里边有个参数是PackageStates,通过这个参数就可以获取到缓存大小cacheSize



再创建一个示例工程,名称为“获取缓存”



一般Ixxx.Stub,是远程服务,那还得拷贝一下

找到IPackageStatesObserver.aidl,打开看到它是android.content.pm包下的

先在示例工程“获取缓存”中将这个包名创建出来,然后将IPackageStatesObserver.aidl拷贝到这个包下



拷贝过来后会报错,原因是它导了一个android.content.pm.PackageStates,那找到PackageStates.aidl,同样放到这个包下



然后在示例工程“获取缓存”的MainActivity中写如下代码



因为IPackageStatesObserver.Stub是一个内部类,所以要有分号结尾,不然会报错



用参数pStats去调用cacheSize(),返回一个long类型的cacheSize,它还有一个codeSize(),同样获取出来

看下这个codeSize是什么东西,它还有一个dataSize(),它返回的都是long类型的,可以用Formater去转换成相应的格式

然后输出一下



它是内部类,内部类使用时,都是以参数形式传递过去,就像前边写的回调方法的那个接口一样,那个接口是以参数的形式传递过去



那它既然是以参数的形式进行回调的,那从模拟器系统代码Settings的ApplicationState.java中找,找到了如下:



	mPm.getPackageSizeInfo(mCurComputingSizingPkg,mStatsObserver);



在这一行,它用getPackageSizeInfo这个方法进行了回调,这个参数mCurComputingSizingPkg中的Pkg是package的一个简写

包名mPm是什么,它是PackageManager



也就是说要通过PackageManager去获取getPackageSizeInfo这个操作,通过参数包名,还有mStatsObserver

就可以拿到相应的包名对应的缓存信息



那在示例工程“获取缓存”的MainActivity的onCreate方法中,也可以获取下这个PackageManager

并用它去调用getPackageSizeInfo这个方法,并把IpackageStateObserver.Stub拿出去,因为它是一个内部类



那示例工程“写入缓存”的包名是“com.itcast.cache”(清单文件中的package),那在获取缓存项目中就是为了获取写入缓存这个项目中的缓存信息,所以这里用到了aidl远程通信



那现在有个问题,getPackageSizeInfo这个方法会报红,通过查看PackageManager.class,发现这个getPackageSizeInfo上边的注释是@hide,隐藏了,通过反射去获取它



那在示例工程“获取缓存”的onCreate方法中,进行反射操作,那反射操作,是通过类名MainActivity.class先拿到一个类加载器getClassLoader()再拿到一个loadClass(className) ,那这个是获取相应类的class,这个相应类就是PackageManager,参数className就写成PacakgeManger的包名



然后紧接着获取相应的方法,通过loadClass去调用getDeclaredMethod(name,parameterTypes)

参数name,是需要获取的方法的名称,写成getPackageSizeInfo

参数parameterTypes,是需要获取的方法的参数类型,那getPackageSizeInfo方法有两个参数

第一个参数是String类型,第二个参数是IPackageStatsObserver



获取到相应的方法之后,就该调用invoke(receiver,args)执行相应的方法

	参数receiver:类型是object,需要类的对象,这个类的对象就是PackageManager,写成pm

	参数args:是方法的参数名,这个方法是getPackageSizeInfo,它的参数是"com.itcast.cache",mStatsObserver



	那这里有个错误,需要捕获下



	public class MainActivity extends Activity{



	@Override

	protected void onCreate(Bundle savedInstanceState){

		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_main);

		

		PacakgeManager pm = getPackageManager();

		//pm.getPackageSizeInfo("com.itcast.cache",mStatsObserver);

	

		//反射操作

		//获取类加载器 获取相应类的class

		Class<?> loadClass 

		try{ 



		loadClass = MainActivity.class.getClassLoader().loadClass("android.content.pm.PackageManager"); 

		

		//获取相应的方法

		Method method loadClass.getDeclaredMethod("getPackageSizeInfo",String.class,IPackageStatsObserver.class);



		//执行相应的方法

		method.invoke(pm,"com.itcast.cache",mStatsObserver);



		} catch(Exception e){

			e.printStackTrace();

		}

	}



	IPackageStatesObserver.Stub mStatesObserver = new IPackageStatesObserver.Stub(){

			

			@Override

			public void onGetStateCompleted(PackageStats pStats,boolean succeeded) throws RemoteException {

		

				long cacheSize = pStats.cacheSize;

				long codeSize = pStats.codeSize;

				long dataSize = pStats.dataSize;



				String cache = Formatter.formatFileSize(getApplicationContext(),cacheSize);

				String code = Formatter.formatFileSize(getApplicationContext(),codeSize);

				String data = Formatter.formatFileSize(getApplicationContext(),dataSize);

				

				System.out.println("cachesize:"+cache+"    datasize:"+data+"   codesize:"+code);



			}

		};

	}



那执行完method.invoke(pm,"com.itcast.cache",mStatsObserver)就相当于执行了pm.getPackageSizeInfo("com.itcast.cache",mStatsObserver),所以将原来的这行代码注释掉,改用反射去执行了





要获取的是另外一个应用的缓存大小,所以还需要添加一个权限“获取应用程序的大小”:



	android.permission.GET_PACKAGE_SIZE



运行程序,看能输出出来不,看到输出出来为:



		System.out  cachesize:4.00KB  datasize:0.0B  codesize:0.96MB

找到模拟器设置中心中的“写入缓存”项目的信息处,看到:



		Total 0.96MB

		Application 0.96MB

		Data 0.00B

		Cache  4.00KB



		Total,Application代表的都是应用程序的大小

		data:代表的是应用程序数据的大小

		Cache:代表的是应用程序缓存的大小



(以后要想获取应用程序的缓存大小,数据大小,应用程序的大小,都可以使用aidl远程通信来实现)



获取缓存的操作就写完了,这个操作写起来比较复杂一些,涉及aidl远程通信的写法,还有反射的写法



获取缓存的操作总结如下:



1.反射获取相应的方法



	PackageManager pm = getPackageManager();

	// pm.getPackageSizeInfo("com.itcast.cache", mStatsObserver);



	// 反射操作

	// 获取类加载器,获取相应类的class

	Class<?> loadClass;

	try {

		loadClass = MainActivity.class.getClassLoader().loadClass(

				"android.content.pm.PackageManager");

		// 获取相应的方法

		Method method = loadClass.getDeclaredMethod("getPackageSizeInfo",

				String.class, IPackageStatsObserver.class);

		// 执行相应的方法

		method.invoke(pm, "com.itcast.cache", mStatsObserver);

	} catch (Exception e) {

		// TODO Auto-generated catch block

		e.printStackTrace();

	}



2.获取缓存大小



	IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {



		@Override

		public void onGetStatsCompleted(PackageStats pStats, boolean succeeded)

				throws RemoteException {

			long cacheSize = pStats.cacheSize;

			long codeSize = pStats.codeSize;

			long dataSize = pStats.dataSize;

			String cache = Formatter.formatFileSize(getApplicationContext(),

					cacheSize);

			String code = Formatter.formatFileSize(getApplicationContext(),

					codeSize);

			String data = Formatter.formatFileSize(getApplicationContext(),

					dataSize);

			System.out.println("cachesize:" + cache + "     datasize:" + data

" codesize:" + code);
 }
 };

12.6显示缓存#

获取缓存的操作上边已经实现了,接下来可以移植到项目中了



我们是在CacheFragment中进行扫描操作的,那将上边获取缓存的操作拷贝到CacheFragment中的Scanner方法的for循环中,表示去获取每个应用的缓存信息



将MainActivity.class.getClassLoader()改为getActivity().getClassLoader()

不写成getActivity().class.getClassLoader()是因为这个class是拿不到的,这里通过getActivity()拿到的就是一个FragmentActivity,这个要注意了,这样拿到的是FragmentActivity的class文件



然后IPpackageStatsObserver.class也报红没有,同样把它拷贝到代码中



将IPackageStatesObserver.Stub拷贝到代码中后也报红没有,这时继续将aidl文件也拷贝到我们项目中来

aidl文件有IPackageStatsObserver.aidl,PackageStats.aidl,将他两拷贝到项目的android.content.pm包下



将aidl文件拷贝过来之后,IPackageStatesObserver.Stub这个内部类还是会报错,出现不能让你导包的问题



这个在使用aidl时,经常会遇到这个问题,明明已经将aidl文件都拷贝过来了,但是代码中就是报红说找不到这个IPackageStatesObserver,你想导包,却又没法导包,clean项目也不管用



最后发现问题是,在使用aidl时,必须保证gen这个文件夹下有你这个"android.itcast.pm"包



我们处理的办法是:将代码和aidl文件全部删掉,重新clean以后,gen文件夹下才出现了"android.itcast.pm"这个包

这个可能和工具有关,搞了老半天,最后先拷贝了aidl文件和aidl代码IPackageStatesObserver.Stub,然后再拷贝的反射代码



那在拷贝aidl代码IPackageStatesObserver.Stub时,它里边的getApplicationContext()会报错,原因是在Fragment中不能使用getApplicationContext(),改成getActivity,在Fragment中,上下文必须得是使用getActivity	



这里只需要获取缓存cache,所以将codeSize,dataSize都注释掉



每循环一下,就要去获取一个应用的缓存信息,那在invoke(pm,"com.itcast.cache",mStatsObserver)这里就不能写固定的包名"com.itcast.cache"了,所以应该写成packageInfo.packageName



	public class CacheFragment extends Fragment {

	

			private TextView tv_antivirus_name;

			private ProgressBar pb_antivirus_progressbar;

		



			//类似activity中oncreate方法,加载布局

			//参数1:布局文件加载

			//参数2:容器

			//参数3:是否保存状态

			@Override

			public View onCreateView(LayoutInflater inflater, ViewGroup container,

					Bundle savedInstanceState) {

				//参数1:布局文件

				//参数2:容器

				//参数3:是否挂载,一般false

				View view = inflater.inflate(R.layout.fragment_cache, container, false);

				tv_antivirus_name = (TextView) view.findViewById(R.id.tv_antivirus_name);

				pb_antivirus_progressbar = (ProgressBar) view.findViewById(R.id.pb_antivirus_progressbar);

				return view;

			}



			@Override

			public void onActivityCreated(Bundle savedInstanceState) {

					super.onActivityCreated(savedInstanceState);

					scanner();

			}



		/**
  • 扫描
     */
     private void scanner() {
     pm = getActivity().getPackageManager();
     tv_antivirus_name.setText(“正在初始化64核缓存清理引擎…”);
     new Thread() {
     public void run() {
     // 5.睡1秒,让程序延时1秒钟扫描
     SystemClock.sleep(1000);
     // 获取安装的程序
     List installedPackages = pm
     .getInstalledPackages(0);
     // 设置总进度
     pb_antivirus_progressbar.setMax(installedPackages.size());
     // 设置当前进度
     int progress = 0;
     for (PackageInfo packageInfo : installedPackages) {
     // 5.睡100毫秒,增加进度显示的真实性
     SystemClock.sleep(100);
     // 设置进度
     progress++;
     pb_antivirus_progressbar.setProgress(progress);
     // 获取应用程序名称
     final String name = packageInfo.applicationInfo.loadLabel(
     pm).toString();

      					try {
    
      					Class<?> loadClass = getActivity().getClassLoader()
    
      							.loadClass("android.content.pm.PackageManager");
    
      					// 获取相应的方法
    
      					Method method = loadClass.getDeclaredMethod(
    
      							"getPackageSizeInfo", String.class,
    
      							IPackageStatsObserver.class);
    
      					// 执行相应的方法
    
      					method.invoke(pm, packageInfo.packageName,
    
      							mStatsObserver);
    
      				} catch (Exception e) {
    
      					// TODO Auto-generated catch block
    
      					e.printStackTrace();
    
      				}
    
      
    
      					
    
      					if (getActivity() != null) {
    
      						getActivity().runOnUiThread(new Runnable() {		
    
      							@Override
    
      							public void run() {
    
      	
    
      								tv_antivirus_name.setText("正在扫描:"+name);
    
      	
    
      							}
    
      					  });
    
      					}
    
      				}
    
    
    
      				if (getActivity() != null) {
    
      					getActivity().runOnUiThread(new Runnable() {
    
      						@Override
    
      						public void run() {
    
    
    
      								// 扫描完成隐藏进度条和textivew文本
    
      								tv_antivirus_name.setVisibility(View.GONE);
    
      								pb_antivirus_progressbar.setVisibility(View.GONE);
    
    
    
      								}
    
      							});
    
      						}
    
      			};
    
      		}.start();
    
      	 }
    
    
    
      	IPackageStatesObserver.Stub mStatesObserver = new IPackageStatesObserver.Stub(){
    
      			
    
      			@Override
    
      			public void onGetStateCompleted(PackageStats pStats,boolean succeeded) throws RemoteException {
    
      		
    
      				long cacheSize = pStats.cacheSize;
    
      				//long codeSize = pStats.codeSize;
    
      				//long dataSize = pStats.dataSize;
    
    
    
      				String cache = Formatter.formatFileSize(getApplicationContext(),cacheSize);
    
      				//String code = Formatter.formatFileSize(getApplicationContext(),codeSize);
    
      				//String data = Formatter.formatFileSize(getApplicationContext(),dataSize);
    
      				
    
      				//System.out.println("cachesize:"+cache+"    datasize:"+data+"   codesize:"+code);
    
      				
    
      				System.out.println(pStats.packageName+"  cachesize:"+cache);
    
      			}
    
      		};
    
          }
    

    因为要获取其他应用程序的缓存信息,所以最后要记得添加权限:

      	android.permission.GET_PACKAGE_SIZE
    

    那这时已经可以获取每个应用的缓存信息了,运行程序,输出看下,看到一直在输出每个应用的缓存大小

    既然这里已经可以拿到缓存信息了,那可以像金山卫士一样,当你有缓存时,应该将有缓存的这个应用程序的信息(图标,名称,缓存大小)显示到列表里边,那这个列表用listView来实现

    在fragment_cache.xml中添加listView:

      <?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 
    
                android:id="@+id/tv_antivirus_name"
    
                android:layout_width="match_parent"
    
                android:layout_height="wrap_content"
    
                android:text="正在扫描"
    
                android:layout_marginTop="25dp"
    
                android:layout_marginLeft="10dp"
    
                android:layout_marginRight="10dp"
    
                android:singleLine="true"
    
                />
    
      		<!-- progress : 设置progerssbar的进度 -->
    
            <ProgressBar
    
                android:id="@+id/pb_antivirus_progressbar"
    
                style="?android:attr/progressBarStyleHorizontal"
    
                android:layout_width="match_parent"
    
                android:layout_height="wrap_content"
    
                android:layout_marginLeft="10dp"
    
                android:layout_marginRight="10dp"
    
                android:progressDrawable="@drawable/anti_progressbar_drawable"
    
                android:layout_marginTop="5dp"
    
                />
    
            <Button 
    
                android:id="@+id/btn_cache_clear"
    
                android:layout_width="match_parent"
    
                android:layout_height="wrap_content"
    
                android:text="立即清理"
    
                android:background="@drawable/selector_contact_button"
    
                android:visibility="gone"
    
                />
    
            <ListView 
    
                android:id="@+id/lv_cache_caches"
    
                android:layout_width="match_parent"
    
                android:layout_height="match_parent"
    

然后到CacheFragment中初始化ListView,那要显示应用程序的缓存信息,在扫描完成之后才显示

那在扫描完成之后,隐藏了进度条和textView文本,那在扫描完成这里,就可以显示应用程序的缓存信息



在这里给listView去setAdapter(adapter),需要一个adapter,把这个adapter创建出来,叫做Myadapter

并实现它的方法,在getCount方法中返回的应该是有多少个条目,那条目去拿,拿到的条目是哪个应用有缓存就把它显示出来



检测有没有缓存时,是在aidl远程通信的IPackageStatsObserver这个内部类中去做的

在这里通过pStats.packageName,可以获取到应用的包名,就在IPackageStatsObserver这个内部类中判断下

如果cacheSize大于0,将应用保存到集合中



那这个集合中应该保持哪些信息,首先应用的图标应该有,还有应用名称,缓存大小,所以接下来还要写一个bean类来接收应用的这些信息,你可以在bean包下单独写成CacheInfoBean.java,那我们这里就直接简单的写在CacheFragment.java中,

那通过包名可以获取到应用的图标和名称,那在bean中就只需要写packagename

那还需要写一个缓存大小cachesize

接着就可以写它两的get,set方法,然后写多个参数的构造方法(方便我们去get,set),toSting方法我们就不要了



bean类写完了之后,接着应该在CacheFragment的成员变量处声明一个集合List,并在onCreateView方法中new出来



然后为了保证我每次扫描应用缓存的时候,都正常显示,我先在onCreateView方法中每次clear()清空一下,保证每次去扫描的时候,这个list集合中都没有以前的数据,



然后紧接着当你扫描完成之后,当你每扫描一个,就去判断下你这个应用里边的缓存如果大于0的话,就将这个应用保存到集合中



	public class CacheFragment extends Fragment {

	

			private TextView tv_antivirus_name;

			private ProgressBar pb_antivirus_progressbar;

		

			private List<CacheInfo> list;



			//类似activity中oncreate方法,加载布局

			//参数1:布局文件加载

			//参数2:容器

			//参数3:是否保存状态

			@Override

			public View onCreateView(LayoutInflater inflater, ViewGroup container,

					Bundle savedInstanceState) {

			

				list = new ArrayList<CacheFragment.CacheInfo>();



				//参数1:布局文件

				//参数2:容器

				//参数3:是否挂载,一般false

				View view = inflater.inflate(R.layout.fragment_cache, container, false);

				tv_antivirus_name = (TextView) view.findViewById(R.id.tv_antivirus_name);

				pb_antivirus_progressbar = (ProgressBar) view.findViewById(R.id.pb_antivirus_progressbar);

				

				ListView lv_cache_caches = (ListView) view.findViewById(R.id.lv_cache_caches);



				return view;

			}



			@Override

			public void onActivityCreated(Bundle savedInstanceState) {

					super.onActivityCreated(savedInstanceState);

					scanner();

			}



		/**
  • 扫描
     */
     private void scanner() {
     pm = getActivity().getPackageManager();
     tv_antivirus_name.setText(“正在初始化64核缓存清理引擎…”);
     new Thread() {
     public void run() {
     // 5.睡1秒,让程序延时1秒钟扫描
     SystemClock.sleep(1000);
     // 获取安装的程序
     List installedPackages = pm
     .getInstalledPackages(0);
     // 设置总进度
     pb_antivirus_progressbar.setMax(installedPackages.size());
     // 设置当前进度
     int progress = 0;
     for (PackageInfo packageInfo : installedPackages) {
     // 5.睡100毫秒,增加进度显示的真实性
     SystemClock.sleep(100);
     // 设置进度
     progress++;
     pb_antivirus_progressbar.setProgress(progress);
     // 获取应用程序名称
     final String name = packageInfo.applicationInfo.loadLabel(
     pm).toString();

      					try {
    
      					Class<?> loadClass = getActivity().getClassLoader()
    
      							.loadClass("android.content.pm.PackageManager");
    
      					// 获取相应的方法
    
      					Method method = loadClass.getDeclaredMethod(
    
      							"getPackageSizeInfo", String.class,
    
      							IPackageStatsObserver.class);
    
      					// 执行相应的方法
    
      					method.invoke(pm, packageInfo.packageName,
    
      							mStatsObserver);
    
      				} catch (Exception e) {
    
      					// TODO Auto-generated catch block
    
      					e.printStackTrace();
    
      				}
    
      
    
      					
    
      					if (getActivity() != null) {
    
      						getActivity().runOnUiThread(new Runnable() {		
    
      							@Override
    
      							public void run() {
    
      	
    
      								tv_antivirus_name.setText("正在扫描:"+name);
    
      	
    
      							}
    
      					  });
    
      					}
    
      				}
    
    
    
      				if (getActivity() != null) {
    
      					getActivity().runOnUiThread(new Runnable() {
    
      						@Override
    
      						public void run() {
    
    
    
      								lv_cache_caches.setAdapter(new MyAdapter());
    
    
    
      								// 扫描完成隐藏进度条和textivew文本
    
      								tv_antivirus_name.setVisibility(View.GONE);
    
      								pb_antivirus_progressbar.setVisibility(View.GONE);
    
    
    
      								}
    
      							});
    
      						}
    
      			};
    
      		}.start();
    
      	 }
    
    
    
      	private class Myadapter extends BaseAdapter {
    
      
    
      		@Override
    
      		public int getCount() {
    
      			return 0;
    
      		}
    
      
    
      		@Override
    
      		public View getView(int position, View convertView, ViewGroup parent) {
    
      
    
      			return null;
    
      		}
    
      
    
      		@Override
    
      		public Object getItem(int position) {
    
      			// TODO Auto-generated method stub
    
      			return null;
    
      		}
    
      
    
      		@Override
    
      		public long getItemId(int position) {
    
      			// TODO Auto-generated method stub
    
      			return 0;
    
      		}
    
      
    
      	}
    
    
    
    
    
      	IPackageStatesObserver.Stub mStatesObserver = new IPackageStatesObserver.Stub(){
    
      			
    
      			@Override
    
      			public void onGetStateCompleted(PackageStats pStats,boolean succeeded) throws RemoteException {
    
      		
    
      				long cacheSize = pStats.cacheSize;
    
      				//long codeSize = pStats.codeSize;
    
      				//long dataSize = pStats.dataSize;
    
    
    
      				String cache = Formatter.formatFileSize(getApplicationContext(),cacheSize);
    
      				//String code = Formatter.formatFileSize(getApplicationContext(),codeSize);
    
      				//String data = Formatter.formatFileSize(getApplicationContext(),dataSize);
    
      				
    
      				//System.out.println("cachesize:"+cache+"    datasize:"+data+"   codesize:"+code);
    
      				
    
      				System.out.println(pStats.packageName+"  cachesize:"+cache);
    
    
    
      				if(cacheSize >0){
    
      					//将应用保存到集合中
    
      					list.add(new CacheInfo(pStats.packageName, cacheSize));
    
      				}
    
      			}
    
      		};
    
    
    
      	
    
      	class CacheInfo {
    
    
    
      		private String packagename;
    
      		private long cachesize;
    
      
    
      		public String getPackagename() {
    
      			return packagename;
    
      		}
    
      
    
      		public void setPackagename(String packagename) {
    
      			this.packagename = packagename;
    
      		}
    
      
    
      		public long getCachesize() {
    
      			return cachesize;
    
      		}
    
      
    
      		public void setCachesize(long cachesize) {
    
      			this.cachesize = cachesize;
    
      		}
    
      
    
      		public CacheInfo(String packagename, long cachesize) {
    
      			super();
    
      			this.packagename = packagename;
    
      			this.cachesize = cachesize;
    
      		}
    
      	  }
    
         }
    

    注意,list.add(new CacheInfo(pStats.packageName, cacheSize))这一步可以分开成几步来写

    它首先是将包名和缓存大小cacheSize添加(set)到了CacheInfo这个bean里边,然后将CacheInfo这个Bean又添加(get)给了list集合

    在使用list去调用add方法时,一直不能调用,原因可能是IPackageStatsObserver.Stub这个内部类,会根据你api的不同版本,没有办法去自动提示获取一些方法,这个时候就要手动去写了

    那将list添加成功之后,接下来就可以对adapter去进行处理了,我们上边只是new出来这个MyAdapter了,但是还没有处理

    getCount方法中,我们需要返回条目的个数,那写成list.size()

    getView方法中,需要返回条目的样式,这个简单了,凡是和ListView相关的操作,我们是不是都要去复用缓存啊

    我们先用View.inflate(getActivity(), R.layout.item_cache, null)将条目的布局初始化出来,

    那这里需要一个布局item_cache.xml,布局参考金山卫士的缓存清理的item布局

    注意,给第一个TextView加权重的意思,是让它的渲染级别变低,也就说我先渲染出ImageView,然后再渲染出第二个textView,最后才渲染第一个TextView

      item_cache.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="wrap_content"
    
          android:orientation="horizontal" 
    
          android:gravity="center_vertical"
    
          <ImageView 
    
              android:id="@+id/iv_itemcache_icon"
    
              android:layout_width="wrap_content"
    
              android:layout_height="wrap_content"
    
              android:src="@drawable/ic_launcher"
    
              />
    
          <TextView 
    
              android:id="@+id/tv_itemcache_name"
    
              android:layout_width="0dp"
    
              android:layout_height="wrap_content"
    
              android:layout_weight="1"
    
              android:text="手机卫士"
    
              android:textSize="18sp"
    
              android:textColor="#000000"
    
              />
    
          <TextView 
    
              android:id="@+id/tv_itemcache_cachesize"
    
              android:layout_width="wrap_content"
    
              android:layout_height="wrap_content"
    
              android:text="4.00KB"
    
              android:textSize="18sp"
    
              android:textColor="#000000"
    
              />
    
      
    
      </LinearLayout>
    

    item的布局有了之后,到getView方法中去加载布局,凡是用ListView,它都要复用缓存,

    那接下来写复用缓存的步骤了:

      首先先创建一个ViewHolder出来,然后在她里边声明出ImageView和两个TextView
    
      然后在getView方法中判断,如果convertView恒等于null的话,加载布局
    
      同时在这个if判断外边声明出这个ViewHolder,如果convertView恒等于null的话,加载布局,同时将ViewHolder给new出来,
    
      同时初始化这3个控件,并将控件存放到ViewHolder中
    
      最后使用view.setTag(viewHolder)将view绑定到ViewHolder中
    

    else当convertView不为null的时候,这时候就可以复用缓存了,首先我们要从convertView中得到一个view对象,然后就可以从view中getTag,把ViewHolder拿出来了,最后记得要return一个view回去

    接着我们就可以去给控件设置数据了

    首先设置一下缓存,即:viewHolder.tv_itemcache_cachesize.setText(text);

    那这个参数text是一个String类型,那我们可以通过list.get(position)拿到一个CacheInfo类型的cacheInfo,那在cacheInfo里边点上一个getCacheSize(),得到的Cache是long类型的,我们是不是还要转化成String类型啊

    long类型一般都是b字节类型(b是long类型的)的,我们要转化成kb,或者mb(kb,mb是String类型的?),那就用Formatter.formatFileSize(getActivity(),cacheInfo.getCacheSize()),它会给我们返回一个String类型的size

    那我们把这个size设置给tv_itemcache_cachesize控件即可

    那接着设置下应用名称name

    那通过cacheInfo.getPackageName()是不是可以得到包名啊,根据包名可以拿到applicationInfo啊,那在pm里边可以拿到getApplicationInfo(String pacakgeName,int flags),是不是可以根据包名拿到一个应用的applicationInfo信息啊,

    参数pacakgeName写成CacheInfo.getPackagename,参数flags写成0,会返回一个ApplicationInfo,这里有一个异常,捕获一下

    然后在ApplicationInfo里边通过loadIcon(pm),可以得到一个应用图标,它会返回一个Drawable类型的icon

    然后通过loadLabel(pm).toString(),可以得到一个String类型的name

    接下来就可以设置应用的图标,即:viewHolder.iv_itemcache_icon.setImageDrawable(icon);

    设置应用的名称,即:viewHolder.tv_itemcache_name.setText(name);

      public class CacheFragment extends Fragment {
    
    
    
      	private TextView tv_antivirus_name;
    
      	private ProgressBar pb_antivirus_progressbar;
    
      	private PackageManager pm;
    
    
    
      	private List<CacheInfo> list;
    
    
    
      	//类似activity中oncreate方法,加载布局
    
      	//参数1:布局文件加载
    
      	//参数2:容器
    
      	//参数3:是否保存状态
    
      	@Override
    
      	public View onCreateView(LayoutInflater inflater, ViewGroup container,
    
      			Bundle savedInstanceState) {
    
      	
    
      		list = new ArrayList<CacheFragment.CacheInfo>();
    
    
    
      		//参数1:布局文件
    
      		//参数2:容器
    
      		//参数3:是否挂载,一般false
    
      		View view = inflater.inflate(R.layout.fragment_cache, container, false);
    
      		tv_antivirus_name = (TextView) view.findViewById(R.id.tv_antivirus_name);
    
      		pb_antivirus_progressbar = (ProgressBar) view.findViewById(R.id.pb_antivirus_progressbar);
    
      		
    
      		ListView lv_cache_caches = (ListView) view.findViewById(R.id.lv_cache_caches);
    
    
    
      		return view;
    
      	}
    
    
    
      	@Override
    
      	public void onActivityCreated(Bundle savedInstanceState) {
    
      			super.onActivityCreated(savedInstanceState);
    
      			scanner();
    
      	}
    
    
    
      /**
    
  • 扫描
     */
     private void scanner() {
     pm = getActivity().getPackageManager();
     tv_antivirus_name.setText(“正在初始化64核缓存清理引擎…”);
     new Thread() {
     public void run() {
     // 5.睡1秒,让程序延时1秒钟扫描
     SystemClock.sleep(1000);
     // 获取安装的程序
     List installedPackages = pm
     .getInstalledPackages(0);
     // 设置总进度
     pb_antivirus_progressbar.setMax(installedPackages.size());
     // 设置当前进度
     int progress = 0;
     for (PackageInfo packageInfo : installedPackages) {
     // 5.睡100毫秒,增加进度显示的真实性
     SystemClock.sleep(100);
     // 设置进度
     progress++;
     pb_antivirus_progressbar.setProgress(progress);
     // 获取应用程序名称
     final String name = packageInfo.applicationInfo.loadLabel(
     pm).toString();

      				try {
    
      				Class<?> loadClass = getActivity().getClassLoader()
    
      						.loadClass("android.content.pm.PackageManager");
    
      				// 获取相应的方法
    
      				Method method = loadClass.getDeclaredMethod(
    
      						"getPackageSizeInfo", String.class,
    
      						IPackageStatsObserver.class);
    
      				// 执行相应的方法
    
      				method.invoke(pm, packageInfo.packageName,
    
      						mStatsObserver);
    
      			} catch (Exception e) {
    
      				// TODO Auto-generated catch block
    
      				e.printStackTrace();
    
      			}
    
    
    
      				
    
      				if (getActivity() != null) {
    
      					getActivity().runOnUiThread(new Runnable() {		
    
      						@Override
    
      						public void run() {
    
      
    
      							tv_antivirus_name.setText("正在扫描:"+name);
    
      
    
      						}
    
      				  });
    
      				}
    
      			}
    
    
    
      			if (getActivity() != null) {
    
      				getActivity().runOnUiThread(new Runnable() {
    
      					@Override
    
      					public void run() {
    
    
    
      							lv_cache_caches.setAdapter(new MyAdapter());
    
    
    
      							// 扫描完成隐藏进度条和textivew文本
    
      							tv_antivirus_name.setVisibility(View.GONE);
    
      							pb_antivirus_progressbar.setVisibility(View.GONE);
    
    
    
      							}
    
      						});
    
      					}
    
      		};
    
      	}.start();
    
       }
    
    
    
      private class Myadapter extends BaseAdapter {
    
    
    
      	@Override
    
      	public int getCount() {
    
      		return list.size();
    
      	}
    
    
    
      	@Override
    
      	public View getView(int position, View convertView, ViewGroup parent) {
    
    
    
      		View view;
    
      		ViewHolder viewHolder;
    
      		if (convertView == null) {
    
      			view = View.inflate(getActivity(), R.layout.item_cache, null);
    
      			viewHolder = new ViewHolder();
    
      			viewHolder.iv_itemcache_icon = (ImageView) view
    
      					.findViewById(R.id.iv_itemcache_icon);
    
      			viewHolder.tv_itemcache_name = (TextView) view
    
      					.findViewById(R.id.tv_itemcache_name);
    
      			viewHolder.tv_itemcache_cachesize = (TextView) view
    
      					.findViewById(R.id.tv_itemcache_cachesize);
    
      			view.setTag(viewHolder);
    
      		} else {
    
      			view = convertView;
    
      			viewHolder = (ViewHolder) view.getTag();
    
      		}
    
    
    
      		CacheInfo cacheInfo = list.get(position);
    
      		String size = Formatter.formatFileSize(getActivity(),
    
      				cacheInfo.getCachesize());
    
      		// 显示缓存大小
    
      		viewHolder.tv_itemcache_cachesize.setText(size);
    
    
    
      		try {
    
      			ApplicationInfo applicationInfo = pm.getApplicationInfo(
    
      					cacheInfo.getPackagename(), 0);
    
      			Drawable icon = applicationInfo.loadIcon(pm);
    
      			String name = applicationInfo.loadLabel(pm).toString();
    
    
    
      			// 设置显示的应用的图标和名称
    
      			viewHolder.tv_itemcache_name.setText(name);
    
      			viewHolder.iv_itemcache_icon.setImageDrawable(icon);
    
    
    
      		} catch (NameNotFoundException e) {
    
      			e.printStackTrace();
    
      		}
    
    
    
      		return view;
    
      	}
    
    
    
      	@Override
    
      	public Object getItem(int position) {
    
      		// TODO Auto-generated method stub
    
      		return null;
    
      	}
    
    
    
      	@Override
    
      	public long getItemId(int position) {
    
      		// TODO Auto-generated method stub
    
      		return 0;
    
      	}
    
    
    
      }
    
    
    
      static class ViewHolder {
    
      	ImageView iv_itemcache_icon;
    
      	TextView tv_itemcache_name, tv_itemcache_cachesize;
    
      }
    
    
    
    
    
      IPackageStatesObserver.Stub mStatesObserver = new IPackageStatesObserver.Stub(){
    
      		
    
      		@Override
    
      		public void onGetStateCompleted(PackageStats pStats,boolean succeeded) throws RemoteException {
    
      	
    
      			long cacheSize = pStats.cacheSize;
    
      			//long codeSize = pStats.codeSize;
    
      			//long dataSize = pStats.dataSize;
    
    
    
      			String cache = Formatter.formatFileSize(getApplicationContext(),cacheSize);
    
      			//String code = Formatter.formatFileSize(getApplicationContext(),codeSize);
    
      			//String data = Formatter.formatFileSize(getApplicationContext(),dataSize);
    
      			
    
      			//System.out.println("cachesize:"+cache+"    datasize:"+data+"   codesize:"+code);
    
      			
    
      			System.out.println(pStats.packageName+"  cachesize:"+cache);
    
    
    
      			if(cacheSize >0){
    
      				//将应用保存到集合中
    
      				list.add(new CacheInfo(pStats.packageName, cacheSize));
    
      			}
    
      		}
    
      	};
    
    
    
      
    
      class CacheInfo {
    
    
    
      	private String packagename;
    
      	private long cachesize;
    
    
    
      	public String getPackagename() {
    
      		return packagename;
    
      	}
    
    
    
      	public void setPackagename(String packagename) {
    
      		this.packagename = packagename;
    
      	}
    
    
    
      	public long getCachesize() {
    
      		return cachesize;
    
      	}
    
    
    
      	public void setCachesize(long cachesize) {
    
      		this.cachesize = cachesize;
    
      	}
    
    
    
      	public CacheInfo(String packagename, long cachesize) {
    
      		super();
    
      		this.packagename = packagename;
    
      		this.cachesize = cachesize;
    
      	}
    
        }
    
     }
    

    运行程序,点击缓存清理,它就会一直清理缓存,最后进度条和正在扫描xxx消失,然后会显示出有缓存的应用

    我们看到它罗列出来说Browser浏览器有588KB缓存,写入缓存有4.00KB缓存

    到这,显示缓存的操作就做完了

    显示缓存的操作总结如下:

    1.将获取缓存的操作移植到手机卫士中,移植到for循环中,表示去获取每个应用的缓存信息

    try {
    
      	Class<?> loadClass = getActivity().getClassLoader().loadClass(
    
      			"android.content.pm.PackageManager");
    
      	// 获取相应的方法
    
      	Method method = loadClass.getDeclaredMethod("getPackageSizeInfo",
    
      			String.class, IPackageStatsObserver.class);
    
      	// 执行相应的方法
    
      	method.invoke(pm, packageInfo.packageName, mStatsObserver);
    
      } catch (Exception e) {
    
      	// TODO Auto-generated catch block
    
      	e.printStackTrace();
    
      }
    

    2.在获取应用缓存的方法中,判断是否有缓存,有就添加到list集合

      if (cacheSize > 0) {
    
      	//将应用保存到集合中
    
      	list.add(new CacheInfo(pStats.packageName,cacheSize));
    
      }
    

    3.通过listview展示出来

      	private class Myadapter  extends BaseAdapter{
    
    
    
      		@Override
    
      		public int getCount() {
    
      			return list.size();
    
      		}
    
      		@Override
    
      		public View getView(int position, View convertView, ViewGroup parent) {
    
      			
    
      			View view;
    
      			ViewHolder viewHolder;
    
    
    
      			if (convertView == null) {
    
      				view = View.inflate(getActivity(), R.layout.item_cache, null);
    
      				viewHolder = new ViewHolder();
    
      				viewHolder.iv_itemcache_icon = (ImageView) view.findViewById(R.id.iv_itemcache_icon);
    
      				viewHolder.tv_itemcache_name = (TextView) view.findViewById(R.id.tv_itemcache_name);
    
      				viewHolder.tv_itemcache_cachesize = (TextView) view.findViewById(R.id.tv_itemcache_cachesize);
    
      				view.setTag(viewHolder);
    
      			}else{
    
      				view = convertView;
    
      				viewHolder = (ViewHolder) view.getTag();
    
      			}
    
    
    
      			CacheInfo cacheInfo = list.get(position);
    
      			String size = Formatter.formatFileSize(getActivity(), cacheInfo.getCachesize());
    
      			
    
      			//显示缓存大小
    
      			viewHolder.tv_itemcache_cachesize.setText(size);
    
      			try {
    
      				ApplicationInfo applicationInfo = pm.getApplicationInfo(cacheInfo.getPackagename(), 0);
    
      				Drawable icon = applicationInfo.loadIcon(pm);
    
      				String name = applicationInfo.loadLabel(pm).toString();
    
      				
    
      				//设置显示的应用的图标和名称
    
      				viewHolder.tv_itemcache_name.setText(name);
    
      				viewHolder.iv_itemcache_icon.setImageDrawable(icon);
    
      			} catch (NameNotFoundException e) {
    
      				e.printStackTrace();
    
      			}
    
      			return view;
    
      		}
    
    
    
      		@Override
    
      		public Object getItem(int position) {
    
      			// TODO Auto-generated method stub
    
      			return null;
    
      		}
    
      
    
      		@Override
    
      		public long getItemId(int position) {
    
      			// TODO Auto-generated method stub
    
      			return 0;
    
      		}
    
      	}
    

12.9 清理缓存 #

缓存大小已经能显示到缓存清理界面了,那接下来就该清理缓存了



金山卫士中,点金山卫士中显示出来有缓存的应用时,它会跳转到应用的详情页面(手机系统页面),在详情页面中有"clearCache"按钮



之所以跳转到手机系统页面中的应用详情页去删除缓存,是因为在安卓当中,我们的应用是没法删除其他任何应用的缓存

必须是系统自己才能去删除应用的缓存,程序员是没有办法去实现这个功能的



所以说金山卫士中做清理缓存的操作,是跳转到了手机系统的应用详情页,让系统帮我们去清理缓存



那跳转到手机系统的应用详情页面,前边在软件管理中也做过,那这里我们是点击listview条目的时候,进行跳转的



那首先我们要给listView增加一个条目点击事件了



在CacheFragment中的onCreateView方法中,给lv_cache_caches增加条目点击事件



	public View onCreateView(LayoutInflater inflater, ViewGroup container,

					Bundle savedInstanceState) {

			

				list = new ArrayList<CacheFragment.CacheInfo>();



				//参数1:布局文件

				//参数2:容器

				//参数3:是否挂载,一般false

				View view = inflater.inflate(R.layout.fragment_cache, container, false);

				tv_antivirus_name = (TextView) view.findViewById(R.id.tv_antivirus_name);

				pb_antivirus_progressbar = (ProgressBar) view.findViewById(R.id.pb_antivirus_progressbar);

				

				ListView lv_cache_caches = (ListView) view.findViewById(R.id.lv_cache_caches);





				lv_cache_caches.setOnItemClickListener(new OnItemClickListener() {

					@Override

					public void onItemClick(AdapterView<?> parent, View view,

							int position, long id) {



					}

				});





				return view;

			}



然后在onItemClick方法中就可以跳转页面了,那onItemClick方法参数中有个position,那找下软件管理页面SoftManagerActivity,看下它跳转到手机系统的应用详情页的intent是怎么写的,拷贝出来,即:



	Intent intent = new Intent();

	intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");

	intent.setData(Uri.parse("package:"+appInfo.getPackageName()));

	startActivity(intent);



看到setData这里,需要传递一个包名过去,可以通过list.get(position).getPackagename()得到包名



	public View onCreateView(LayoutInflater inflater, ViewGroup container,

				Bundle savedInstanceState) {

		

			list = new ArrayList<CacheFragment.CacheInfo>();



			//参数1:布局文件

			//参数2:容器

			//参数3:是否挂载,一般false

			View view = inflater.inflate(R.layout.fragment_cache, container, false);

			tv_antivirus_name = (TextView) view.findViewById(R.id.tv_antivirus_name);

			pb_antivirus_progressbar = (ProgressBar) view.findViewById(R.id.pb_antivirus_progressbar);

			

			ListView lv_cache_caches = (ListView) view.findViewById(R.id.lv_cache_caches);





			lv_cache_caches.setOnItemClickListener(new OnItemClickListener() {

				@Override

				public void onItemClick(AdapterView<?> parent, View view,

						int position, long id) {



					// 跳转到详情页面

					Intent intent = new Intent();

					intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");

					intent.setData(Uri.parse("package:"

list.get(position).getPackagename()));
 startActivity(intent);

				}

			});



			return view;

		}



运行程序,点击Brower,就可以直接进入到Brower浏览器的详情页,然后点击clearData就将缓存清理了



那缓存清理完了,应该将这个应用从listView列表中清空出去,金山卫士中没有去做,那我们也不做了

可以自己做,很简单,startActivityForResult一下,更新一下列表就可以了



接下来实现下另外一个功能



在金山卫士的缓存清理界面右上角有个立即清理按钮,点击立即清理按钮之后,它会弹出一个对话框,提醒你“确定要删除缓存文件吗?”你可以选择确定或者取消,点击确定之后,所有应用的缓存都将被清理掉,然后他会显示“垃圾文件清理成功!本次共为您节省了4.00KB的空间”



那刚才我说了,我们的应用不能单独去删除其他应用的缓存文件啊,但是注意,它这里删除的不是一个应用的缓存,它删除的是整个系统当中,所有应用的缓存文件,单个的是不允许我们去删,但是全部的是允许我们这么去干的



那我们也实现下,这里就需要PackageManager中的一个方法了,我们打开PackageManager的源码,查找一个freeStorge



找到了一个freeStorageAndNotify(long freeStoreSize,IPackageDataObserver observer)方法

这个方法是释放内存并通知的一个操作,其实这个方法不是用来释放内存的,它是用来申请内存的

参数freeStoreSize:是用来申请内存的大小



那之所以用这个方法的原因,是因为它有这样一个特性,我们要利用android系统的一个漏洞

android系统有这样一个特性,比如系统当中有100MB内存,我们要申请50MB,那系统就会给我们50MB,

那系统当中还是有100MB内存,我要申请500MB,它这时候会这么干,把100MB内存里边的东西全部清掉,同时再给你释放400MB内存

总共让你有100MB+400MB=500MB内存



那我就是使用这样一个特性,比如系统有100MB内存,那我们要申请500MB缓存,那你是不是会把前边100MB的缓存也给清空掉啊,再给我400MB缓存,加起来是不是500MB啊,这个是系统自己给我们分配的



那重新讲下,比如系统当中有600MB内存,我们用了100MB内存,那现在需要500MB,那它会这么干,把100MB给释放掉,然后再给你400MB,加起来刚好500MB



缓存也是一样,比如有600MB的缓存空间,用了100MB缓存空间,现在需要500MB缓存空间,那它会把前边用掉的100MB缓存空间里的缓存给释放掉,然后再给你400MB缓存空间,加起来就是你需要的500MB缓存空间



总结起来就是,当需要的缓存空间大于已使用的缓存空间时,系统会把已使用的缓存空间释放掉,并入到你需要的缓存空间中



它是为了避免资源浪费,但是好多人就拿它来做清理缓存的操作



比如说我在这里来个需要最大的缓存空间,这个时候就可以把系统中所有的缓存文件给删除掉,这个时候系统就会把所有的缓存空间给我们



那我们用一下PacakgeManager中的这个freeStorageAndNotify方法,我们看到它也是@hide,那是隐藏方法,就表明我们需要去反射才能使用它



那首先是点击“立即清理”按钮,才会去清理所有的缓存,所以我们先在fragment_cache.xml中添加一个按钮



金山卫士中是不是在缓存清理界面的右上角显示的这个“立即清理”按钮啊,看下sd卡清理界面中,也是在右上角显示的“立即清理”



这样做起来不是特别好,比较麻烦一点,因为我们用的是fragment,fragment和activity切换的时候,不太好做,所以我直接把清理的按钮放到里边来做,放到ListView的上方,便于我们去操作,而且可以通过添加属性android:background="@drawable/selector_contact_button"来给它添加状态选择器



那如果没有缓存的时候,就没有必要显示这个按钮,所以默认设置成gone



	<?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 

	          android:id="@+id/tv_antivirus_name"

	          android:layout_width="match_parent"

	          android:layout_height="wrap_content"

	          android:text="正在扫描"

	          android:layout_marginTop="25dp"

	          android:layout_marginLeft="10dp"

	          android:layout_marginRight="10dp"

	          android:singleLine="true"

	          />

			<!-- progress : 设置progerssbar的进度 -->

	      <ProgressBar

	          android:id="@+id/pb_antivirus_progressbar"

	          style="?android:attr/progressBarStyleHorizontal"

	          android:layout_width="match_parent"

	          android:layout_height="wrap_content"

	          android:layout_marginLeft="10dp"

	          android:layout_marginRight="10dp"

	          android:progressDrawable="@drawable/anti_progressbar_drawable"

	          android:layout_marginTop="5dp"

	          />

	      <Button 

	          android:id="@+id/btn_cache_clear"

	          android:layout_width="match_parent"

	          android:layout_height="wrap_content"

	          android:text="立即清理"

	          android:background="@drawable/selector_contact_button"

	          android:visibility="gone"

	          />

	      <ListView 

	          android:id="@+id/lv_cache_caches"

	          android:layout_width="match_parent"

	          android:layout_height="match_parent"

在CacheFragment中初始化这个btn_cache_clear

	private Button btn_cache_clear;



	public View onCreateView(LayoutInflater inflater, ViewGroup container,

			Bundle savedInstanceState) {

	

		list = new ArrayList<CacheFragment.CacheInfo>();



		//参数1:布局文件

		//参数2:容器

		//参数3:是否挂载,一般false

		View view = inflater.inflate(R.layout.fragment_cache, container, false);

		tv_antivirus_name = (TextView) view.findViewById(R.id.tv_antivirus_name);

		pb_antivirus_progressbar = (ProgressBar) view.findViewById(R.id.pb_antivirus_progressbar);

		

		ListView lv_cache_caches = (ListView) view.findViewById(R.id.lv_cache_caches);

		

		btn_cache_clear = (Button) view.findViewById(R.id.btn_cache_clear);



		lv_cache_caches.setOnItemClickListener(new OnItemClickListener() {

			@Override

			public void onItemClick(AdapterView<?> parent, View view,

					int position, long id) {



				// 跳转到详情页面

				Intent intent = new Intent();

				intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");

				intent.setData(Uri.parse("package:"

list.get(position).getPackagename()));
 startActivity(intent);
 }
 });

		return view;

	}



然后再来看,我们是什么时候点击“立即清理”按钮的,是在扫描完了之后,有缓存的话,才去清理



我们扫描完了之后,先显示了缓存信息,然后将进度条和textView文本隐藏掉了,那我们就在它下边判断有没有缓存,如果list.size()大于0的话,将“立即清理”按钮显示出来,然后给按钮设置点击事件,然后在onClick方法中就可以通过反射去调用freeStorageAndNotify方法实现清理缓存的操作了



那首先通过getActivity().getClassLoader().laodClass(className),那这个类名className还是我们"android.content.pm.PackageManager"

然后会返回一个Class<?>类型的loadClass,这里会有一个ClassNotFoundException异常,try,catch捕获下,

然后用loadClass去getDeclaredMethod(name,parameterTypes)加载方法了,方法名name为"freeStorageAndNotify",这个方法还有两个参数,一个是long类型的freeStorageSize,一个是IPackageDataObserver类型的observer,即:



	public abstract void freeStorageAndNotify(long freeSorageSize,IPackageDataObserver observer)



那这个IPackageDataObserver又是一个远程服务aidl,那先把这个long类型的参数写出来,注意我怎么写了,写成Long.TYPE,它的返回值我们看到是一个long类型

那有些同学可能会这样写,他会写成Long.class,注意啊,这个是一个包装类型,你包装类型返回的是一个包装类型的class文件,我们参数这里要的是小写的long,

这个long是我们的一个基础类型,所以要写成Long.TYPE,它会获取到相应的类型



那第二个参数我们写成IPackageDataObserver.class,那IPackageDataObserver.class是不是还没有啊,那搜索下,并打开看下他的路径,也是在android.content.pm包下,所以我们拷贝到这个包下即可



但是又出现了使用aidl经常出现的问题,就是IPackageDataObserver.class文件我们已经拷贝到项目中了,但是代码中参数处的IPackageDataObserver.class却无法导包的问题,我们clean一下,这次倒是好了



它会给我们返回一个Method类型的method,那方法得到了之后,我们就可以method.invoke(receiver,args)去执行这个方法了,

参数receiver我们写成pm,args写成Long.MAX_VALUE,new MyIPackageDataObserver()

Long.MAX_VALUE我们写成最大的,第二个参数new了一个MyIPackageDataObserver()



那我们创建出来 并实现它的onRemoveCompleted方法,这个就是缓存清理完后调用这个方法,在它里边我们什么都不用写,我们什么都不操作



	/**
  • 扫描
     */
     private void scanner() {
     pm = getActivity().getPackageManager();
     tv_antivirus_name.setText(“正在初始化64核缓存清理引擎…”);
     new Thread() {
     public void run() {
     // 5.睡1秒,让程序延时1秒钟扫描
     SystemClock.sleep(1000);
     // 获取安装的程序
     List installedPackages = pm
     .getInstalledPackages(0);
     // 设置总进度
     pb_antivirus_progressbar.setMax(installedPackages.size());
     // 设置当前进度
     int progress = 0;
     for (PackageInfo packageInfo : installedPackages) {
     // 5.睡100毫秒,增加进度显示的真实性
     SystemClock.sleep(100);
     // 设置进度
     progress++;
     pb_antivirus_progressbar.setProgress(progress);
     // 获取应用程序名称
     final String name = packageInfo.applicationInfo.loadLabel(
     pm).toString();

      				try {
    
      					Class<?> loadClass = getActivity().getClassLoader()
    
      							.loadClass("android.content.pm.PackageManager");
    
      					// 获取相应的方法
    
      					Method method = loadClass.getDeclaredMethod(
    
      							"getPackageSizeInfo", String.class,
    
      							IPackageStatsObserver.class);
    
      					// 执行相应的方法
    
      					method.invoke(pm, packageInfo.packageName,
    
      							mStatsObserver);
    
      				} catch (Exception e) {
    
      					// TODO Auto-generated catch block
    
      					e.printStackTrace();
    
      				}
    
    
    
      				// 在退出的时候,子线程还在运行,还没被及时处理,所以getActivity()会出现为null的现象
    
      				if (getActivity() != null) {
    
      					getActivity().runOnUiThread(new Runnable() {
    
    
    
      						@Override
    
      						public void run() {
    
      							tv_antivirus_name.setText("正在扫描:" + name);
    
      						}
    
      					});
    
      				}
    
      			}
    
      	if (getActivity() != null) {
    
      		getActivity().runOnUiThread(new Runnable() {
    
    
    
      		@Override
    
      		public void run() {
    
      			myadapter = new Myadapter();
    
      			lv_cache_caches.setAdapter(myadapter);
    
    
    
      			// 扫描完成隐藏进度条和textivew文本
    
      			tv_antivirus_name.setVisibility(View.GONE);
    
      			pb_antivirus_progressbar.setVisibility(View.GONE);
    
    
    
    
    
      			if (list.size() > 0) {
    
      				btn_cache_clear.setVisibility(View.VISIBLE);
    
      				btn_cache_clear.setOnClickListener(new OnClickListener() {
    
    
    
      					@Override
    
      					public void onClick(View v) {
    
    
    
      					// 清理全部缓存
    
      					// freeStorageAndNotify
    
      					try {
    
    
    
      						Class<?> loadClass = getActivity().getClassLoader().loadClass("android.content.pm.PackageManager");
    
    
    
      						Method method = loadClass.getDeclaredMethod("freeStorageAndNotify",Long.TYPE,IPackageDataObserver.class);
    
      						
    
      						method.invoke(pm,Long.MAX_VALUE,new MyIPackageDataObserver());
    
    
    
      					} catch (Exception e) {
    
      						// TODO Auto-generated catch
    
      						// block
    
      						e.printStackTrace();
    
      					}
    
      				}
    
      			  });
    
      		     }
    
      		   }
    
      	    });
    
      	  }
    
          };
    
        }.start();
    
      }	
    
    
    
      private class MyIPackageDataObserver extends IPackageDataObserver.Stub {
    
    
    
      	@Override
    
      	public void onRemoveCompleted(String packageName, boolean succeeded)
    
      			throws RemoteException {
    
      		// 缓存清理完之后会调用的方法
    
      	}
    
      }
    

    运行程序,点击缓存清理,扫描完之后,立即清理按钮是不是直接就弹出来了,点击立即清理,发现”写入缓存“这个应用的缓存还是显示4.00KB,那重新扫描下,发现"写入缓存"应用还在我们这个ListView中显示

    这里有两个问题

    1.点击立即清理之后,没有去更新ListView列表

      那当我们清理完全部缓存之后,就要去更新界面,在更新界面之前,是不是还要对list进行清理啊,那用list.clear()去清理下list
    
      然后myadapter.notifyDataSetChanged()去更新界面
    

    2.添加清理缓存的权限

    到这更新界面的操作我们就做完了,那接着再来看,我们要去清理缓存,是不是调用了系统的freeSorageAndNotify这个方法啊,而且你清理清理缓存是侵犯用户隐私的,所以还需要添加权限:

      /**
    
  • 扫描
     */
     private void scanner() {
     pm = getActivity().getPackageManager();
     tv_antivirus_name.setText(“正在初始化64核缓存清理引擎…”);
     new Thread() {
     public void run() {
     // 5.睡1秒,让程序延时1秒钟扫描
     SystemClock.sleep(1000);
     // 获取安装的程序
     List installedPackages = pm
     .getInstalledPackages(0);
     // 设置总进度
     pb_antivirus_progressbar.setMax(installedPackages.size());
     // 设置当前进度
     int progress = 0;
     for (PackageInfo packageInfo : installedPackages) {
     // 5.睡100毫秒,增加进度显示的真实性
     SystemClock.sleep(100);
     // 设置进度
     progress++;
     pb_antivirus_progressbar.setProgress(progress);
     // 获取应用程序名称
     final String name = packageInfo.applicationInfo.loadLabel(
     pm).toString();

      				try {
    
      					Class<?> loadClass = getActivity().getClassLoader()
    
      							.loadClass("android.content.pm.PackageManager");
    
      					// 获取相应的方法
    
      					Method method = loadClass.getDeclaredMethod(
    
      							"getPackageSizeInfo", String.class,
    
      							IPackageStatsObserver.class);
    
      					// 执行相应的方法
    
      					method.invoke(pm, packageInfo.packageName,
    
      							mStatsObserver);
    
      				} catch (Exception e) {
    
      					// TODO Auto-generated catch block
    
      					e.printStackTrace();
    
      				}
    
    
    
      				// 在退出的时候,子线程还在运行,还没被及时处理,所以getActivity()会出现为null的现象
    
      				if (getActivity() != null) {
    
      					getActivity().runOnUiThread(new Runnable() {
    
    
    
      						@Override
    
      						public void run() {
    
      							tv_antivirus_name.setText("正在扫描:" + name);
    
      						}
    
      					});
    
      				}
    
      			}
    
      	if (getActivity() != null) {
    
      		getActivity().runOnUiThread(new Runnable() {
    
    
    
      		@Override
    
      		public void run() {
    
      			myadapter = new Myadapter();
    
      			lv_cache_caches.setAdapter(myadapter);
    
    
    
      			// 扫描完成隐藏进度条和textivew文本
    
      			tv_antivirus_name.setVisibility(View.GONE);
    
      			pb_antivirus_progressbar.setVisibility(View.GONE);
    
    
    
    
    
      			if (list.size() > 0) {
    
      				btn_cache_clear.setVisibility(View.VISIBLE);
    
      				btn_cache_clear.setOnClickListener(new OnClickListener() {
    
    
    
      					@Override
    
      					public void onClick(View v) {
    
    
    
      					// 清理全部缓存
    
      					// freeStorageAndNotify
    
      					try {
    
    
    
      						Class<?> loadClass = getActivity().getClassLoader().loadClass("android.content.pm.PackageManager");
    
    
    
      						Method method = loadClass.getDeclaredMethod("freeStorageAndNotify",Long.TYPE,IPackageDataObserver.class);
    
      						
    
      						method.invoke(pm,Long.MAX_VALUE,new MyIPackageDataObserver());
    
    
    
      					} catch (Exception e) {
    
      						// TODO Auto-generated catch
    
      						// block
    
      						e.printStackTrace();
    
      					}
    
    
    
      					//更新界面
    
      					list.clear();
    
      					myadapter.notifyDataSetChanged();
    
    
    
      				}
    
    
    
      			  });
    
      		     }
    
      		   }
    
      	    });
    
      	  }
    
          };
    
        }.start();
    
      }	
    
    
    
      private class MyIPackageDataObserver extends IPackageDataObserver.Stub {
    
    
    
      	@Override
    
      	public void onRemoveCompleted(String packageName, boolean succeeded)
    
      			throws RemoteException {
    
      		// 缓存清理完之后会调用的方法
    
      	}
    
      }
    

    到这,清理全部应用缓存的操作就做完了

    接下来可以实现SD卡清理,这个SD卡清理比较简单,我就不做了,这个主要是和数据库有关的给大家简绍下怎么做

    在我给大家的资料中,找到clearpath.db,你sd卡的清理,大家的缓存文件是不是都保存在sd里边啊,那sd里边的目录我们是不是自己就可以删掉的,不像手机里的目录,我们是不能删,但是sd里边的目录,比如说那个文件夹,我们自己可以用java代码删掉

    那我们打开clearpath.db看下里边都什么东西,点击safedetail表,它里边储存的都是市面上比较流行的一些软件,softEnglishname是它的软件名称,apkname是他的包名,filePath就是它的缓存目录

    SD卡清理实现起来非常简单,给大家说下思路,首先你是不是得把数据库拷贝到我们手机里边啊,之后你是不是可以拿到每一个应用程序的包名啊,然后你是不是可以判断下它有没有缓存啊,或者说不判断有没有缓存,直接对filePath这里进行删除,那删除这个文件的操作是不是属于java基础的操作啊,你直接删除这个文件就可以了,就这么简单,这就是sd卡的清理,说白了就是把每个应用在sd里边相应的缓存目录删除就可以了,因为应用它每次启动的时候,它有时候会自己创建出来,其实你清不清理这个sd卡,没有多大作用,所以简单说下实现思路即可

    清理全部缓存步骤总结如下:

    1.进入详情界面

      lv_cache_caches.setOnItemClickListener(new OnItemClickListener() {
    
    
    
      	@Override
    
      	public void onItemClick(AdapterView<?> parent, View view,
    
      			int position, long id) {
    
      		// 跳转到详情页面
    
      		Intent intent = new Intent();
    
      		intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
    
      		intent.setData(Uri.parse("package:"
    

list.get(position).getPackagename()));
 startActivity(intent);
 }
 });

2.全部清理系统中应用程序缓存



	if (list.size() > 0) {

			btn_cache_clear.setVisibility(View.VISIBLE);

			btn_cache_clear

					.setOnClickListener(new OnClickListener() {



						@Override

						public void onClick(View v) {

							// 清理全部缓存

							// freeStorageAndNotify

							try {

								Class<?> loadClass = getActivity()

										.getClassLoader()

										.loadClass(

												"android.content.pm.PackageManager");

								Method method = loadClass

										.getDeclaredMethod(

												"freeStorageAndNotify",

												Long.TYPE,

												IPackageDataObserver.class);

								method.invoke(

										pm,

										Long.MAX_VALUE,

										new MyIPackageDataObserver());

							} catch (Exception e) {

								// TODO Auto-generated catch

								// block

								e.printStackTrace();

							}

							//更新界面

							list.clear();

							myadapter.notifyDataSetChanged();

						}

						

					});

		}



	private class MyIPackageDataObserver extends IPackageDataObserver.Stub {

	

		@Override

		public void onRemoveCompleted(String packageName, boolean succeeded)

				throws RemoteException {

			// 缓存清理完之后会调用的方法

		}

	}



权限: <uses-permission android:name="android.permission.CLEAR_APP_CACHE"/>



这个缓存清理在哪里会用到呢?

大家在使用app的时候,是不是会有个清理缓存的操作啊,现在有一些比较好的app里边都有这个操作,它就是这么实现的

其实说白了就是反射调用了下freeStorageAndNotify方法,把所有的缓存全部干掉,这样就相当于把自己的缓存也干掉了,它就是这么实现的

12.10 全局bug收集 #

到这里,整个应用就做完了,接下来还要给大家简绍一个小细节,我们的应用开发完了之后,后边剩下的就只是维护项目,那用户在用的时候,会有各种bug报出来,那怎么收集用户遇到的各种bug呢?



你总不能这么干吧,比如用户用着用着奔溃了,你给用户说你下载eclipse,运行程序,把报出的红色logcat文字发送给我们,这样是不现实的



所以一般在项目结束之后,会添加自动捕获那个红色异常的功能,这个在每个app里边都有,你捕获之后,可以保存到文件夹里边,或者发送到服务器,或者说保存到本地,让用户直接发送给你,也是可以的



看下清单文件里边,是不是有一个application标签,那现在我创建一个MyApplication.java,继承自Application,然后重写它的onCreate方法

然后给清单文件中的application标签设置一个android:name=".MyApplication",也就是设置成我们上边自己写的MyApplication



到这,应用的application就变成了我们的MyApplication了,在它的onCreate方法中输出下,即:



	//应用启动的时候,先调用就是application,才去调用的主界面

	public class MyApplication extends Application {

		

		@Override

		public void onCreate() {

			super.onCreate();

			System.out.println("application启动了...");

			

		}

	}



然后紧接着我们第一个界面是不是splash界面啊,来到SplashActivity.java的onCreate方法中,也输出下,即:



	public class SplashActivity extends Activity {



	@Override

	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_splash);



		System.out.println("splash界面启动了......");



		}

	}



运行程序,我们看logcat中打印出来的,是不是先打印的是application启动了...,下边才打印的是splash界面启动了......



这是因为应用启动的时候,先调用的是application,才去调用的主界面



那正是因为它先走的是application,所以在application中的onCreate方法中:



我们用Thread去调用currentThread(),然后再去调用setUncaughtExceptionHandler(handler),



参数handler是一个UncaughtExceptionHandler类型,它是接口类型,那这个参数我们写成new MyUncaughtExceptionHandler()



然后在onCreate下边创建一个MyUncaughtExceptionHandler,让它实现自UncaughtExceptionHandler,并实现uncaughtException方法



这个uncaughtException方法就是在有未捕获的异常时会调用的方法,那在这里输出下"哥捕获了异常!!!!"



	//应用启动的时候,先调用就是application,才去调用的主界面

	public class MyApplication extends Application {



		@Override

		public void onCreate() {

			super.onCreate();

			System.out.println("application启动了...");



			Thread.currentThread().setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());

		}



		private class MyUncaughtExceptionHandler implements UncaughtExceptionHandler{



			//有未捕获的异常时会调用的方法,临终遗言

			//Throwable : Exception和Error的父类

			@Override

			public void uncaughtException(Thread thread, Throwable ex) {

				System.out.println("哥捕获了异常!!!!");

			}

		}

	}



为了让输出这句话,我这么干了,在HomeActivity中故意来个异常,比如在加载布局完了之后,来个:

		

	public class HomeActivity extends Activity {



		@Override

		protected void onCreate(Bundle savedInstanceState) {

				super.onCreate(savedInstanceState);

				//加载布局

				setContentView(R.layout.activity_home);

			

				int i = 3;		

				int z = i/0;

		

		}

	}



那这个肯定会出现一个异常啊,那出现异常了之后,我们看它会不会走MyApplication中的uncaughtException方法,打印出"哥捕获了异常!!!!"



运行程序,打印出了"哥捕获了异常!!!!",然后程序是不是就变黑了,那我们看下log日志中报了一个错误:说HomeActivity中出错了,

但是你在这捕获了有什么用啊,程序已经黑屏了,你是不是什么也做不了啊,那你捕获了是不是也相当于没有什么意义啊



不对,也有意义,比如说我们可以在uncaughtException方法中,让程序在黑屏前留下遗言,那当程序出现异常之后,是不是肯定就会走这个uncaughtException方法啊,那我在这个方法中没有做处理,程序是不是就黑屏了啊



那接下来我可以这么干了,uncaughtException方法的参数中是不是有个Throwable啊,这个我要给大家说下了,它其实是Error和Exception的一个父类了,那既然你是父类,那我们平时见到红色,是不是由printStackTrace(err)它拿出来的啊,参数err类型是PrintStream, 那printStackTrace(err)参数我们写成new PrintStream(file),它这个参数需要一个file,那这个file参数我们写成new File(),

然后在File的参数处,我们来个"/mnt/sdcard/log.txt",那File这里有个异常,我们捕获下



这个操作的意思就相当于将出现的异常保存到本地文件中,而"/mnt/sdcard/log.txt"就是相当于给文件指定保存的路径和名称,



这个做完了之后,程序是不是还是黑屏的啊,紧接着我们就要做下一步操作了,那怎么杀死程序呢?很简单,自杀,怎么自杀呢?



在android.os.Process下边有个killProcess(pid),这个killProcess(pid)就是杀死进程的方法,那参数需要一个pid,

在android.os.Process下边有个myPid(),这个操作就会得到自己的pid,



	//应用启动的时候,先调用就是application,才去调用的主界面

	public class MyApplication extends Application {



		@Override

		public void onCreate() {

			super.onCreate();

			System.out.println("application启动了...");

			Thread.currentThread().setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());

		}



		private class MyUncaughtExceptionHandler implements UncaughtExceptionHandler{

			//有未捕获的异常时会调用的方法,临终遗言

			//Throwable : Exception和Error的父类

			@Override

			public void uncaughtException(Thread thread, Throwable ex) {

				System.out.println("哥捕获了异常!!!!");

				try {

					//将出现的异常保存到本地文件中

					ex.printStackTrace(new PrintStream(new File("/mnt/sdcard/log.txt")));

				} catch (FileNotFoundException e) {

					e.printStackTrace();

				}

				//杀死程序,自杀,android.os.Process.myPid():就会得到自己的pid

				android.os.Process.killProcess(android.os.Process.myPid());

			}

		}

	}



运行程序,首先打印了“哥捕获异常了...”,然后我们看到程序是不是直接退出了啊,并不会在出现黑屏了



那我们打开DDMS,在"/mnt/sdcard/log.txt"路径下是不是有个log.txt,那你把log导出来看下



看到这个log.txt中的内容就是经常报红的异常,看下报什么错误,在HomeActivity中什么东西除0了



这个就是我们在开发中经常会用到的一些手段,当然你在这里捕获到异常之后,你是不是还可以开一个线程,把这个log.txt上传到服务器上啊



比如你用个第三方xUtils去上传,或者你保存到用户手机中,出问题了你让用户找下并发送给你也是可以的



这个就是捕获异常的方式,在开发中的最后阶段,每个应用都会加上的,一方面是为了捕获这些异常

还有一个比如说你们老板就是专门做了一个beta版,放到市场上让用户使用,根据用户提供的异常信息,去优化版本



你们在工作中,是有个手机奔溃率的,比如你可能要保持在5%,也就是说可能有5%的手机可能会奔溃,那这个还算正常



因为android机型比较多,所以它不同机型上,产生的各种bug是不一样的,所以这些都可以搜集到服务器





全局bug收集总结如下:	



1.创建一个application



	//应用启动的时候,先调用就是application,才去调用的主界面

	public class MyApplication extends Application {

	

		

		@Override

		public void onCreate() {

			super.onCreate();

			System.out.println("application启动了...");

			Thread.currentThread().setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());

		}

		private class MyUncaughtExceptionHandler implements UncaughtExceptionHandler{

			//有未捕获的异常时会调用的方法,临终遗言

			//Throwable : Exception和Error的父类

			@Override

			public void uncaughtException(Thread thread, Throwable ex) {

				System.out.println("哥捕获了异常!!!!");

				try {

					//将出现的异常保存到本地文件中

					ex.printStackTrace(new PrintStream(new File("/mnt/sdcard/log.txt")));

				} catch (FileNotFoundException e) {

					e.printStackTrace();

				}

				//杀死程序,自杀,android.os.Process.myPid():就会得到自己的pid

				android.os.Process.killProcess(android.os.Process.myPid());

			}

			

		}

	}



2.将清单文件中的application的name设置为我们的application



	<application

    	android:name=".MyApplication"

12.11 代码混淆(涉及打包,反编译)#

bug收集讲完了之后,我们整个应用就全部做完了,但是还有一些后续工作我们要做一下



接下来讲代码混淆,这个在开发完了之后,是必须要做的



也就是不管哪个应用,在开发完了之后,都要进行“bug收集”,“代码混淆”,“打包签名apk”,"上架"



那为什么要用代码混淆呢?

	

	那在资料中找到360MobileSafe.apk,那如果你这个360没有经过代码混淆的话,我们可以看到它源码啊,那他们公司代码就泄露了,所以为了避免别人反编译去学习apk的一些技术,才有了这个代码混淆的概念



我们把这个360MobileSafe.apk用第10天资料中的Android逆向助手给大家反编译演示下:



将apk拖动到Android逆向助手的源文件目录处,那我要得到的是java代码,所以选择dex转jar,它是不是会生成一个jar包360MobileSafe_dex2jar.jar啊,然后它会自动帮我们打开这个jar包,我们看到都是些aa,bb之类的东西,凡是aa开头的,其实都是一个文件,它把他们拆分成了很多文件



这里注意,混淆之后有这样一个特性,比如说你代码中类里边有一个内部类,它就会把内部类专门摘出来,给它命名一个aay或者aax,还有些不能混淆的,就没有混淆,包名org,这明显就是一个第三方的东西



那现在来看我们的代码了,选中mobilesafexian02,右键Export,Exprot Android Application,然后next,password设置为123456,再点击next,Alias写成xian02,password还是输入123456,然后点击finish,过一会,它就会在桌面上给我们打包出来一个mobilesafexian02.apk



那这时候也可以把我们打包出来的这个mobilesafexian02.apk反编译下,过一会儿它就在桌面给我们生成一个mobilesafexian02_dex2jar.jar

并自动打开了,我们看到有bean,activity等等,所有代码都看的清清楚楚,除了setConentView(2130903042),这个布局文件在反编译的时候,自动转化成了数字,但是其他的全部都是我们的代码,连“正在初始化64核缓存清理引擎...”这些汉字都能看到



那如果你把apk就这样上架到应用市场,别人把你的apk直接反编译下,就可以看到源码



我见过一些app就没有混淆,有些程序员会把你的代码改吧改吧,然后用这个Android逆向助手中的重新打包apk选项,应用就成为人家的了



所以为了避免这种情况发生,一般在应用做完之后,上线时对应用进行混淆操作



那怎么对应用进行混淆操作?



在mobilesafexian02项目的目录最下边中有个project.properties



看到里边有一句话:

proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

那去掉#号,就相当于把这句话放开了:

	proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt



这句话意思就相当于要找到混淆文件,${sdk.dir}这个其实是linux下的指令操作,这里可能会出现问题



所以一般会这么去干,我们看到proguard-android.txt它是不是在sdk文件夹下的tools/proguard/文件夹下



那找到proguard-project.txt,并拷贝到mobilesafexian02工程中,将project.properties中的那行代码改为:

	proguard.config=proguard-android.txt:proguard-project.txt

表明proguard-project.txt这个文件指向的就是我们mobilesafexian02工程中的这个proguard-project.txt



那现在重新打包mobilesafexian02,在桌面生成mobilesafexian021.apk,

然后将这个mobilesafexian021.apk通过Android逆向助手重新反编译下,在桌面生成mobilesafexian021_dex2jar.jar

它会自动帮我们打开,这时候我们看到的就是一些aa,bb之类的东西,但是我们还看到AToolsActivity,AddressActivity之类的activity,

这是因为activity永远都是不能混淆的,因为我们这个activity是不是在清单文件中去配置的啊



如果你混淆了activity,那它在清单文件中找不到了,所以activity是不能混淆的



此外,我们还看到有自定义控件Home_TextView,这说明自定义控件也不能混淆,这是因为自定义控件在布局文件中使用的时候,我们直接使用的全类名,即:<cn.itcast.mobilesafexian02.ui.Home.TextView,

那你如果将自定义控件混淆成a.a.a.a这样的东西,那使用它的这个布局文件是不是就不知道了



注意,四大组件和自定义控件是混淆不掉的



这个就是我们代码混淆的操作,就2步:



1.将proguard-project.txt这个sdk目录下的文件拷贝到项目中

2.然后修改下project.properties中这个文件的路径



非常简单,就这2步就实现了混淆的功能



那我们打开proguard-project.txt,看下他都是什么东西,我们看到有:



	-keeppattributes Annotation

	-keep public class com.google.vending.licensing.ILicensingService

	-keepclasseswithmembernames class *

	-keepclassmembers public class * extends android.view.View

	-keepclassmembers class * extends android.app.Activity



那这个只要是有-keep的,都是不会被混淆掉的,比如说一些class文件,android.view.View(其实就是自定义控件),activity



大家在工作中混淆的时候,可能会遇到一个问题,比如你是用百度的sdk,支付宝,或者一些其他的第三方工具,那这些第三方工具是事先已经混淆过了的,你就不用再去混淆这些第三方工具了,那这个时候你怎么办呢?



模仿proguard-project.txt中-keep的写法,在你混淆自己项目的时候,将你项目中使用到的这些第三方工具都添加-keep,

那这个-keep后边跟的都是一些它的包名,比如说这个“-keepclassmembers class * extends android.app.Activity”



那activity是不是都是在android.app下啊,所以说在这里你来个包名就可以了



稍后我们在讲广告的时候,也会给大家说怎么在混淆自己的项目时防止将项目中的第三方给混淆掉



这里大家要注意,凡是第三方的,已经混淆过的控件或者sdk,你就要在proguard-project.txt这个文件中配置-keep,让它不要被混淆掉,否则的话,你是没有办法打包编译的



代码混淆步骤总结如下:



1.将sdk\tools\proguard\目录中的proguard-android.txt



拷贝到   工程的根目录下,是和清单文件一个目录



2.修改project.properties中proguard-android.txt文件的路径为:



	proguard.config=proguard-android.txt:proguard-project.txt



注意:四大组件,自定义控件,已经混淆过的第三方sdk,不能混淆



这个代码混淆必须要会,工作中项目做完了,必须混淆之后才能打包上架到应用市场



混淆之后,在工作空间中找到mobilesafexian02项目,它的目录中会多了一个proguard文件夹,在这个目录中有个mapping.txt文件



这个文件中的内容如下:

	

	android.content.pm.IPacakgeDataObserver -> android.a.a.a:

	int commit() -> a

	int commitInternal(boolean) -> a

	int run() -> run

	android.support.v4.app.BackStackRecord$Op -> android.support.v4.app.c;



比如混淆时混淆成了android.a.a.a,那这个android.a.a.a对应的就是项目中的android.content.pm.IPacakgeDataObserver

commit代表的是a,commitInternal(boolean)代表的是a,run()代表的及时run

再比如说V4包下的app.BackStackRecord$Op包名,直接来一个app.c



全部都是一一对应的,有一些工具就会拿你这个mapping.txt去反编译,去找这些反编译后的东西,原来是什么东西



所以这个mapping.txt文件一定不能丢,而且一定不能让别人看



这个proguard文件夹一般在我们打包签名的时候,就会自动隐藏掉的,注意,不是删除掉,是隐藏掉了



混淆过的代码,可读性会非常差,目的就是为了防止别人反编译拿到代码



但是有些比较牛的人,即使你反编译了,他还是可以拿到你代码的,人家只不过看你的代码有没有什么可拿性了



android是没有什么绝对的安全的,你代码就算写的防护再好,也是可以拿到的



后边应该会给你们说一个混淆编译加密的操作,这个操作之后,你在反编译apk时,需要输入密码才能反编译

12.13 广告 #

混淆给大家简绍完了,接下来就是给大家简绍下广告



广告这个很简单,市面上现在有两种形式的广告:	



1.厂商:直接找你,然后你在你的代码里就镶嵌一对专门显示它的商品等等一些的log,图片,比如说我们手机卫士九宫格下边是不是还有一片空白的地方,那在这里我可以放一张图片,专门显示你厂商log,或者自己制作一张,这个是一种形式,这种形式在我们中国不常见

因为在中国,大家都喜欢免费的软件,而且大家很讨厌广告,你在这来个广告,大家会很烦



而且现在厂商都会找一些第三方广告平台合作,让它的广告通过广告平台的形式去展示出来,具体是以谁展示,厂商不管,厂商给广告平台钱,然后平台根据点击次数给每个app应用多少钱,现在平台给app定的价格好像是点击1000次1毛钱,



别看1毛钱很少,像前段时间比较火的内容叫笨鸟先飞,那个哥们儿一个星期赚了满盆,人生第一桶金就拿到了,它是怎么干的?那个笨鸟先飞我看了下,很简单,只要鸟碰到那个桶,鸟会死掉并弹出你走了多少关啊,它在那里就有一个隐藏广告,因为特别容易死,那每隔几秒钟你就死一次,死一次就展示一次广告,一天一直玩的话,一天展示1000次不成问题,那一个人一天展示1000次,十万人一天展示多少次啊,那一天这个小伙光广告收入就多少呢,就一周时间就下架了,那时候可不是只有10万人在玩,那时候好像是百万级的人在玩,这个主要看用户基数,用户越多你挣得越多,比如微博,qq空间,都会有一些广告,微博广告在最上边,你刷新一次,广告就变一次,现在广告还行,有一些app现在杜绝用广告盈利了,但是前两年用广告盈利是非常普遍的手段



传智就有一个老学员,它做了一个游戏应用,一天能挣5万美金,这个游戏在当初美国排行榜很高,在google下载量第一的

那如果你在游戏中在来点广告,那就更挣钱,而且在外国,app一般是不是都是收费的啊,在中国你要收费的话,铁定得死掉,

天天快跑那个游戏一开始在国外是收费的,在国内它也想火起来,但是火不起来,没办法它只能在国内免费,免费了之后,反而火起来了



那个神庙逃亡其实出来很久了,但它一直不挣钱,因为它是收费的,到中国之后免费,基本上一夜之间它就暴富了



2.第三方的广告平台:现在app想去植入广告的话,一般都会去找第三方的广告平台,国内比较知名的应该有一个有米广告,国外常见的一个叫做startapp,这个我可能只能打开个界面,还可能要翻墙



	今天主要带大家看的是国内的这个有米广告,进入它的官网之后,点击登录之后就直接进入后台了,我们看到它的后台界面默认展示的是数据中心: 今天收入: ¥0.000   昨天收入: ¥0.000 总收入: ¥0.000  账户余额: ¥0.000



	然后在左侧有一排列表:后台首页 应用中心 应用列表 详细信息 广告设置 添加应用 SDK下载 数据中心 收入报表 应用报表 用户报表 财务中心 



那我们看下应用列表,在应用列表中,有我放入的mobilesafe应用,广告状态标签显示未上传,这是因为我用了一个测试模式,测试模式是不会上传的



我们在看下详细信息,这个就是你上传的应用的相信信息,里边有:



	应用名称:mobilesafe 应用状态:未上传 上传应用:上传APK 发布ID: d83eaf316037b9cd 复制

	应用密钥: dc00a3698d2865f6 复制 应用类型:关键字:手机卫士



这个就是你把你的应用上传到它这个平台,并植入广告,就可以挣钱了



发布ID,应用密钥:意思是每一个应用的广告,都会有一个id和密钥的,必须用这个id和密钥,才能去进行广告的展示

	

在来看下广告设置,里边有 积分墙广告设置 视频广告设置 广告过滤设置 在线参数设置



我们看到积分墙广告设置里边,100积分=1元,就是你靠积分去获取钱		



在看下添加应用,就是你可以添加应用,申请一个ID和密钥,然后把sdk下载下来,你就可以用了



在看下SDK下载,这里边就是各种广告的sdk,有积分广告sdk,无积分广告SDK,视频广告sdk等等,想展示哪种广告你随便选



在看下收入报表:这个就是展示我收入的,因为我是测试的,所以都没钱



在看下应用报表:其实就是你上传的各个应用的广告收入,效果数之类的,还有选择业务:积分墙 积分插屏 广告条 插屏 视频广告 自定义广告



在看下用户报表:里边有新增用户数,活跃用户数





那现在我们看怎么镶嵌广告到我们的应用中?



首先选择添加应用,然后输入 



	应用名称: mobilesafexian02 应用类型:[软件]安全软件 适合平台:android 关键字:手机卫士

	应用简绍:手机卫士采用现有技术已经能够实现SDS,要全面覆盖现有的数据中心网络情况,需要采用以下两类关键技术:

			 基于SDK/Openflow技术....



这个应用介绍是我随便找的一段话



然后勾选上用户群体,是否收费:否 用户性别:不分性别 用户年龄:不分年龄



最后点击下一步,就会跳转到"获取ID和下载SDK"页面 



它里边已经生成了应用密钥:

			发布ID: d83eaf316037b9cd 复制

			应用密钥: dc00a3698d2865f6 复制



它里边还有各种广告的sdk,我给大家下载好了,是资料中的有米文件夹中的YoumiAndroidsdk



这个YoumiAndroidsdk中各种demo可以看,还有doc各种开发文档,这也是一般的第三方sdk常见的方式,你点击这个index.html,

然后点击查看有米Android无积分广告开发者文档



一般第三方的sdk都会有一个开发者文档供你看的,我们点击“有米Android无积分广告开发者文档”之后,按照文档中的步骤,

1.导入SDK,2.权限配置 3.广告组件配置4.初始化 5.混淆配置 6.打包(重要) 7.设置 渠道号(可选)



一步一步去做,就可以用了



libs文件夹下的YoumiSdk_v5.00_2015-01-08.jar,其实就是它的jar包



那YoumiNormalAdsDemo_v5.0.0_2015-01-08.apk,YoumiOffersAdsDemo_v5.0.0_2015-01-08.apk就是它demo中对应生成的两个apk,你可以直接安装到手机查看对应demo的效果





集成有米广告到项目中的步骤:





1.那我现在把这个YoumiAndroidsdk中libs文件夹下的YoumiSdk_v5.00_2015-01-08.jar拷贝到我们mobilesafexian02项目的libs目录中,然后右键->build Path下



然后紧接着看它的开发文档中,第一步我们是不是做完了啊,紧接着它说第二步权限配置,它上边写到在清单文件中配置



2.将开发文档第二步中的几个权限拷贝到我们项目的清单文件中:



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

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

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

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

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

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

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

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



注意:将清单文件中已有的权限进行去重



如何去重?比如选中INTERNET,然后按住ctrl+k键,如果清单文件中已经有这个权限了,它会自动跳转到另外一个INTERNET所在的位置,那你将它删除掉就即可,然后再选中READ_PHONE_STATE,依次排查是否已经有这些权限了



3.按照开发文档中的第三步,将以下配置代码复制到AndroidManifest.xml文件中:



	 <application

        android:hardwareAccelerated="true"

        android:icon="@drawable/ic_launcher"

        android:label="@string/app_name"

        android:theme="@style/MyTheme.NotitleBar.CustomBackground" 



		<activity

			android:name="net.youmi.android.AdBrowser"

			android:configChanges="keyboard|keyboardHidden|orientation|screenSize"

			android:theme="@android:style/Theme.Light.NoTitleBar">

		</activity>



		<service

			android:name="net.youmi.android.AdService">

		</service>

	

		<receiver

			android:name="net.youmi.android.AdReceiver"

			<intent-filter>

				<action android:name="android.intent.action.PACKAGE_ADDED"/>

				<data android:scheme="package"/>

			</intent-filter>

		 </receiver>



	</application>



这个application标签在我们清单文件中已经有了,所以只需要拷贝它里边的activity,service,receiver



注意:由于新版插屏使用到动画效果,开启硬件加速能更流畅的显示动画,由于部分手机系统较老没有默认开启硬件加速,请在application或者插屏所在activity标签中加入: android:hardwareAcceleterated="true"



那这个硬件加速,不加也可以,那我们还是加下吧,我们在application标签中给它增加这条属性即可



4.按照开发文档第四步:请务必在应用第一个activity(启动的第一个类)的onCreate中调用以下代码:



import net.youmi.android.AdManager;

...

AdManager.getInstance(Context context).init(String appId,String appSecret,boolean isTesMode);



注意:这里说的启动的第一个类不是说的我们的application,说的是activity,那将上边这行代码拷贝到我们SplashActivity的onCreate方法中





然后将getInstance(Context context)方法的参数上下文直接用this,



我们看到开发文档第四步写着:



	注意:appid和appSecret分贝为应用的发布ID和密钥,由有米后台自动生成,通过在有米后台->应用详细信息 可以获得;

	isTestModel为是否开启测试模式,true为是,false为否(上传有米审核及发布到市场版本,请设置为false)

	

	那我们mobilesafexian02在有米后台生成如下:

			发布ID: b55327eed7258e53 复制

			应用密钥: b486156834be6e52 复制





那将init(String appId,String appSecret,boolean isTesMode)方法中

	参数appId写为"b55327eed7258e53",

	参数appSecret写为"b486156834be6e52",

	参数isTesMode写为true 也就是我们开启测试模式



	public class SplashActivity extends Activity {



	@Override

	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_splash);

	

		AdManager.getInstance(this).init("b55327eed7258e53", "b486156834be6e52", true);



		}

	}



5.按照开发文档中的第五步混淆配置:

	1)如果您的项目使用了Proguard混淆打包,为了避免SDK被二次混淆导致无法正常获取广告,请务必在proguard-project.txt中添加一下代码:



	-dontwarn net.youmi.android.**

	-keep class.net.youmi.android.**{

		*;

	}



	(它这几行代码就表示你在混淆mobilesafexian02时,就不会在混淆youmi了,一般像youmi这些第三方开发文档中,都会提醒你要对混淆进行配置,过滤掉人家已经混淆过的第三方



	如果有些已经混淆过的第三方,在它的文档中没有提醒你,那你就只能自己在proguard-project.txt文档中添加:

	

	-keep class net.youmi.android.**{

			*;	

		}



	你自己添加的话,这个包名你可以打开YoumiSdk_v5.00_2015-01-08.jar,第一个包名:

		net.youmi.android

	再加上“.**”,就是你要拼写的东西了,它表示这个net.youmi.android以及它后边的所有东西都不用混淆)





	2)并在project.properties中指向Android混淆文件



	proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt



	这个我们已经在上一节课已经改过了,所以这个就不用管了



6.按照开发文档中的第六步打包:

	

	注意:打包时,如果您在AndroidManifest.xml文件中制定了android:targetSdkVersion并且targetSdkVersion>=17,那么需要在配置文件project.properties中指定target-sdk为17以上。



那看下我们项目的清单文件中为:

	android:targetSdkVersion="17"



那我们的这个刚好等于17,所以我们需要来到project.properties文件里边,将sdk版本指定为17以上



那看下这个文件中,我们指定的sdk是多少,即:



project.properties.txt

This file is automatically generated by Android Tools.
Do not modify this file – YOUR CHANGES WILL BE ERASED!

This file must be checked in Version Control Systems.

To customize properties used by the Ant build system edit
“ant.properties”, and override values to adapt the script to your
project structure.

To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
proguard.config=proguard-android.txt:proguard-project.txt

Project target.
target=android-18

看到指定的是18,那刚好大于17,也就是刚好符合人家的要求,所以这个也不用管了	



7.按照开发文档中的设置渠道号



	目前,有米已经与多家应用商店合作,当您发布的目标应用商店有要求您加上有米的渠道号时,您可以按以下操作加入渠道号进行打包,这样可以保证您的应用在该应用商店上顺利审核通过



	请在AndroidManifest.xml中添加以下代码:



	<meta-data android:name="YOUMI_CHANNEL"

		android:value="这里替换为非负整数的渠道号">

	</meta-data>



它的意思是比如它已经和应用汇或者安卓市场等这些应用商店合作了,如果你要上架的应用商店有需要,那就在mobilesafexian02项目的清单文件中添加上边那几行代码,并将渠道号添加到value处即可



我们继续往下看开发文档:



二:无积分广告调用(重要)



1.插屏广告调用

......

2.广告条调用

2.1广告条尺寸

AdSize提供了五种广告条尺寸提供给开发者使用:



*AdSize.FIT_SCREEN //自适应屏幕宽度

*AdSize.SIZE_320x50 //手机

*AdSize.SIZE_300X250 //手机,平板

*AdSize.SIZE_468x60  //平板

*AdSize.SIZE_728x90



2.2嵌入广告条

2.2.1普通布局(适用于应用)



1)配置布局文件

复制以下代码到要展示广告的Activity的layout文件中,并且放在合适的位置:



	<LinearLayout>

		android:id="@+id/adLayout"

		android:layout_width="fill_parent"

		android:layout_height="wrap_content"

		android:gravity="center_horizontal">

	</LinearLayout>



2)将AdView加入布局

在展示广告的Activity类中,添加如下代码:



	//实例化广告条

	AdView adView = new AdView(this,AdSize.FIT_SCREEN);

	//获取要嵌入广告条的布局

	LinearLayout adLayout = (LinearLayout)findViewById(R.id.adLayout);

	//将广告条加入到布局中

	adLayout.addView(adView);



这些“二”中就是各种广告怎么去设置



在这里给大家简单加一种广告,就选广告条

那在2.1中提供了5种分辨率,那这个分辨率我们就简单看下就行了,没必要做啥



那就看2.2嵌入广告条的操作,“1)”中说复制以下代码到要展示广告的Activity的layout文件中,并且放在合适的位置,那我们就将这边这个LinearLayout控件复制到activity_home.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" >

	

	    <!--

	    gravity : 控制控件的内容的位置 

	    layout_gravity : 控件在父控件中的位置

	    paddingTop : 距离控件内边框顶部的距离 -->

	

	    <TextView

	        android:id="@+id/textView1"

	        android:layout_width="match_parent"

	        android:layout_height="wrap_content"

	        android:background="#8866ff00"

	        android:gravity="center_horizontal"

	        android:paddingBottom="10dp"

	        android:paddingTop="10dp"

	        android:text="功能列表"

	        android:textSize="25sp" />

	    <!--

	    singleLine : 单行显示

	    ellipsize : marquee滚动

	    android:marqueeRepeatLimit="marquee_forever" : 永远滚动   marquee_forever = -1 

	    none : 省略后面的内容

	    start : 隐藏前面的内容

	    middle : 隐藏中间的内容

	    end : 隐藏后面的内容

	    marquee : 滚动,获取焦点才能滚动

	    focusableInTouchMode : 触摸获取焦点

	    focusable :是否获取焦点  true:可以获取    false: 不可以

	    android中默认的滚动次是3次

	

	    -->

	

	    <cn.itcast.mobilesafexian02.ui.Home_TextView

	        android:layout_width="match_parent"

	        android:layout_height="wrap_content"

	        android:ellipsize="marquee"

	        android:focusableInTouchMode="true"

	        android:marqueeRepeatLimit="marquee_forever"

	        android:singleLine="true"

	        android:text="手机卫士,真64核杀毒引擎,超神速度,打开7次可以召唤神龙,辅助杀毒!!!" />

	

		<--有米广告-->

	    <LinearLayout

	        android:id="@+id/adLayout"

	        android:layout_width="fill_parent"

	        android:layout_height="wrap_content"

	        android:gravity="center_horizontal" >

	    </LinearLayout>

	    <!--

	    GridView : 跟listview相似 

	    numColumns : 设置每行显示的个数,其实就是设置显示列数

	    verticalSpacing : 设置行与行之间的距离

	    layout_marginTop : 距离控件顶部的距离

	

	    -->

	

	    <GridView

	        android:id="@+id/gv_home_gridview"

	        android:layout_width="match_parent"

	        android:layout_height="match_parent"

	        android:layout_marginTop="20dp"

	        android:numColumns="3"

	        android:verticalSpacing="10dp" >

	    </GridView>

	

	</LinearLayout>



那再看2.2嵌入广告条的操作,“2)”中说将AdView加入布局,那我们就将上边初始化LinearLayout控件的那段代码复制到HomeActivity.java中的onCreate方法中,



		public class HomeActivity extends Activity {

	

			@Override

			protected void onCreate(Bundle savedInstanceState) {

			super.onCreate(savedInstanceState);

		

			// 实例化广告条

			AdView adView = new AdView(this, AdSize.FIT_SCREEN);

			// 获取要嵌入广告条的布局

			LinearLayout adLayout = (LinearLayout)findViewById(R.id.adLayout);

			// 将广告条加入到布局中

			adLayout.addView(adView);

	

			}

		}



这个广告条的展示功能就做完了,运行程序,看看能不能展示广告条



看到展示出来了,有点慢,等一会儿才出来的



这个就是调用第三方广告平台,来展示广告的一种效果,这个就是给大家简单的简绍下怎么给应用中嵌入广告的使用步骤



大家有可能自己做一些自己的app,那这个时候你就可以添加一些广告,来达到挣钱的目的,比如说你一天挣个100块钱,那一个月也是3000块钱呢



当然不限于展示广告条这一种,你还可以展示其他类型的广告



其实我从头到尾,是不是都只是看着开发文档来写的啊,将来你们在用百度地图的sdk,或者支付宝的sdk时,全都是像有米广告sdk一样,sdk中其他东西都不重要,你只需要根据它的开发文档一步一步来就完事了,如果到哪不懂了,就看下他的demo就可以了



就说你用这些第三方sdk就是这么简单,不要想的太难



当然第三方的sdk一般都是需要一个id和密钥key的,最少都是需要一个密钥key的,那个key其实就是类似于我们有米广告这里的id和秘钥的操作

Day12 15手机卫士重点 #

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值