MobileSafe Day10

10.1 复习 #

10.2 widgets控件 #

看金山卫士在桌面有一个包含:正在运行软件:14,可用内存:419.43MB,一键清理等的控件


多看一会你会发现这个控件里边的数字是会变化的,从420MB变成了419.20MB了

那点击下这个控件里边的清理按钮,控件中的:正在运行软件变成1了,可用内存变成431.57MB了

这个控件是怎么实现的,先拖动这个控件将它放入到回收框中删除掉,然后长按手机屏幕,就会出来一个add to home screen的弹框

点击它里边的widgets进去,又会弹出一个Choose Widget弹框,它里边会有一些选项,选择底部的金山手机卫士,它就会再次在屏幕上显示出刚才删除掉的那个控件,它就是一个桌面小控件



看下api的文档里边是怎么介绍这个手机桌面小控件的

找到itcast/itcastEclipse/adt-bundle-windows-x86_64_20140101/adt-bundle-windows-x86_64_20140101/sdk/docs/index.xml,用浏览器打开这个index.xml,然后点击浏览器的文件,点击脱机工作,选择develop,再选择APIGuides

往下看它有一个App Widgets,看下它的英文介绍,意思是这个app Widgets其实就是一个迷你的应用程序的view

它能镶嵌到其他的应用程序当中,比如说是桌面上等等,看到它下边有一张图,这个就是音乐播放器的widgets桌面小控件了

它是可以镶嵌到桌面上或者其他应用里边的,其实它就是一个迷你的应用的views对象



往下看,要创建一个AppWidgetProvider,这个AppWidgetProvider它不是内容提供者,是广播接收者



1.在receiver包下去创建名称为MyWidgets的广播接收者,继承自AppWidgetsProvider,即:



public class MyWidgets extends AppWidgetProvider {



}



2.到清单文件中注册广播接收者MyWidgets



点击看下AppWidgetProvider的源码,它继承自BroadcastReceiver,它就是一个广播接收者

当创建好这个MyWidgets之后,要到清单文件中去注册,即:



	<receiver android:name="cn.itcast.mobilesafexian02.receiver.MyWidgets" >

        <intent-filter>

            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

        </intent-filter>



        <meta-data

            android:name="android.appwidget.provider"

            android:resource="@xml/example_appwidget_info" />

	</receiver>



将name为ExampleAppWidgetProvider改为cn.itcast.mobilesafexian02.receiver.MyWidgets

meta-data原数据中的resource中的xml文件名称为example_appwidget_info的报红



3.到res文件夹下的xml文件夹中创建一个xml文件:example_appwidget_info.xml



创建完之后,它会跑到layout文件夹下,没关系,再把它拖到xml文件夹下



在这个example_appwidget_info.xml中要怎么去干了,接着看api文档,可以直接把它的例子拷贝过来,即:



<?xml version="1.0" encoding="utf-8"?>

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"

    android:minWidth="272dp"

    android:minHeight="72dp"

    android:updatePeriodMillis="0"

	android:previewImage="@drawable/preview"

	android:initialLayout="@layout/example_appwidget"

	android:configure="com.example.android.ExampleAppWidgetConfigure"

    android:resizeMode="horizontal|vertical"

    android:widgetCategory="home_screen|keyguard"

	android:initialKeyguardLayout="@layout/example_keyguard"

</appwidget-provider>



<!-- 

	1.minWidth、minHeight :最小的宽度和高度

	updatePeriodMillis : widgets更新时间,毫秒值,但是不能小于30分钟((1800000)),0其实跟30分钟(1800000)一样的效果,时间到了之后,会去调用onUpdate方法

	previewImage : 预览图片,不设置,就会使用应用程序的图标

	

	2.initialLayout : widgets的布局

	

	3.configure :可选属性,创建widgets的时候启动的activity,就是你在创建widgets的时候,它会启动哪个activity,它是在这里边标识的,这个操作我们开发中一般都是用不到的,它在什么时候会用到呢?

	就是在没有activity的时候会用,比如说有一些流氓程序,它是不是只有一些服务啊,或者有一些程序,它只有服务,那这个时候,它就会在这里把服务的路径设置给configure这个属性,然后开启我们这个widgets的时候,它就会开启这个服务,所以其实我们一般是用不到的

	

	4.resizeMode : 设置widget显示尺寸的规则,设置屏幕水平和垂直切换时,显示的widget的风格(样式)保持不变,就是避免widget变形或压缩,我们按F11,模拟器就会变成横屏,那我们这里设置完这个属性之后,切换的时候,widgets的样式就会保持不变,因为我们以前学过,在屏幕横竖屏切换的时候,控件是不是会被拉伸啊,那设置完这个resizeMode属性之后,在切换的时候,它这个widgets就会根据你屏幕的规则来适配这个屏幕,可以保持样式不变

	

	5.widgetCategory : widget可以显示的地方,home_screen:桌面  keyguard:键盘,代表键盘弹出的时候应该显示出来的是什么样子的,它不是显示在键盘上,在你桌面上设置的时候,一般都会有键盘弹出等等的一些操作,那这个时候注意,当你键盘弹出来的时候,它是会设置让这个widget显示出来的,当然你不设置也没有关系,就说键盘弹出的时候,它会让widget隐藏

	

	6.initialKeyguardLayout : widget在锁屏的时候显示的布局,大家在锁屏的时候会看到一些天气啊,时间啊等的桌面小控件啊,其实就是这个属性来设置的,其实这个属性我们一般也用不到

 -->

	

什么是预览图片呢?



	当长按手机桌面之后,它会弹出一个add to home screen的弹框,再点击Widegets,会再进入一个Choose widget的弹框

	它里边会有需要添加到桌面的应用程序的图标,比如Musice(音乐播放器),Picture frame(相册),这些图片就是预览图片

	如果你在上边这个example_appwidget_info.xml中不设置previewImage属性,就会使用应用程序的图标



预览图片previewImage和可选属性configure,widget在锁屏时显示的布局initialKeyguardLayout

一般都是不会使用的



那将上边的例子拷贝到example_appwidget_info.xml中,即:



example_appwidget_info.xml



<?xml version="1.0" encoding="utf-8"?>

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"

    android:minWidth="272dp"

    android:minHeight="72dp"

    android:updatePeriodMillis="0"

    android:initialLayout="@layout/process_widget"

    android:resizeMode="horizontal|vertical"

    android:widgetCategory="home_screen|keyguard"

</appwidget-provider>



4.创建桌面显示的widget的布局process_widget.xml



看到example_appwidget布局报红,它就是桌面显示的widget的布局,在layout包下新建一个布局example_appwidget.xml

后边会将它改名为process_widget.xml,所以这里就写成process_widget.xml



即:



process_widget.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:background="@drawable/widget_bg_portrait"

    android:gravity="center_vertical" >



    <LinearLayout

        android:layout_width="0.0dip"

        android:layout_height="fill_parent"

        android:layout_marginLeft="5.0dip"

        android:layout_weight="1.0"

        android:background="@drawable/widget_bg_portrait_child"

        android:gravity="center_vertical"

        android:orientation="vertical"

        android:paddingBottom="3.0dip"

        android:paddingTop="3.0dip" >



        <TextView

            android:id="@+id/process_count"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="10.0dip"

            android:text="正在运行软件:7"

            android:textAppearance="@style/widget_text" />



        <ImageView

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:layout_marginBottom="1.0dip"

            android:layout_marginTop="1.0dip"

            android:background="@drawable/widget_bg_portrait_child_divider" />



        <TextView

            android:id="@+id/process_memory"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="10.0dip"

            android:text="可用内存:405MB"

            android:textAppearance="@style/widget_text" />

    </LinearLayout>



    <LinearLayout

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:gravity="center_horizontal"

        android:orientation="vertical" >



        <LinearLayout

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:gravity="center_vertical" >



            <ImageView

                android:layout_width="20.0dip"

                android:layout_height="20.0dip"

                android:src="@drawable/ic_launcher" />



            <TextView

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:text="@string/app_name"

                android:textColor="@color/textColorPrimary" />

        </LinearLayout>



        <Button

            android:id="@+id/btn_clear"

            android:layout_width="90.0dip"

            android:layout_height="wrap_content"

            android:layout_centerVertical="true"

            android:layout_marginTop="5.0dip"

            android:background="@drawable/button"

            android:text="一键清理"

            />

    </LinearLayout>



</LinearLayout>



	

运行程序,返回到桌面,然后长按桌面,点击widgets,然后选择手机卫士西安2号,这时候长按手机卫士西安2号,拖动到桌面

它就显示“我是安全卫士的widget桌面控件”,这个就是我们的widget控件,只不过我们的样式是没有设置过的



现在总结下widgets控件创建步骤:



	1.第一步创建一个广播接收者MyWidgets,继承自AppWidgetProvider(是广播接受者不是内容提供者,只是名字起错了)



	2.清单文件中配置广播接收者MyWidgets



	<receiver android:name="cn.itcast.mobilesafexian02.receiver.MyWidgets" >

        <intent-filter>

            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

        </intent-filter>

        <meta-data

            android:name="android.appwidget.provider"

            android:resource="@xml/example_appwidget_info" />

	</receiver>



	3.在res -> xml中创建example_appwidget_info.xml布局



	<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"

	    android:minWidth="272dp"

	    android:minHeight="72dp"

	    android:updatePeriodMillis="0"

	    android:initialLayout="@layout/process_widget"

	    android:resizeMode="horizontal|vertical"

	    android:widgetCategory="home_screen|keyguard"

	</appwidget-provider>



4.在layout包下新建一个布局process_widget.xml,它就是我们桌面widgets的布局文件



process_widget.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:background="@drawable/widget_bg_portrait"

    android:gravity="center_vertical" >



    <LinearLayout

        android:layout_width="0.0dip"

        android:layout_height="fill_parent"

        android:layout_marginLeft="5.0dip"

        android:layout_weight="1.0"

        android:background="@drawable/widget_bg_portrait_child"

        android:gravity="center_vertical"

        android:orientation="vertical"

        android:paddingBottom="3.0dip"

        android:paddingTop="3.0dip" >



        <TextView

            android:id="@+id/process_count"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="10.0dip"

            android:text="正在运行软件:7"

            android:textAppearance="@style/widget_text" />



        <ImageView

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:layout_marginBottom="1.0dip"

            android:layout_marginTop="1.0dip"

            android:background="@drawable/widget_bg_portrait_child_divider" />



        <TextView

            android:id="@+id/process_memory"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="10.0dip"

            android:text="可用内存:405MB"

            android:textAppearance="@style/widget_text" />

    </LinearLayout>



    <LinearLayout

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:gravity="center_horizontal"

        android:orientation="vertical" >



        <LinearLayout

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:gravity="center_vertical" >



            <ImageView

                android:layout_width="20.0dip"

                android:layout_height="20.0dip"

                android:src="@drawable/ic_launcher" />



            <TextView

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:text="@string/app_name"

                android:textColor="@color/textColorPrimary" />

        </LinearLayout>



        <Button

            android:id="@+id/btn_clear"

            android:layout_width="90.0dip"

            android:layout_height="wrap_content"

            android:layout_centerVertical="true"

            android:layout_marginTop="5.0dip"

            android:background="@drawable/button"

            android:text="一键清理"

            />

    </LinearLayout>

</LinearLayout>

10.3.widgets的生命周期 # (重点)

刚才widgets控件已经显示出来



先来说一个问题,刚才点击时,直接就拖出来一个“手机卫士”的widgets控件到手机桌面了

这里边就是一个textview来显示“我是安全卫士的widgets”桌面控件,现在把布局文件example_appwidgets.xml改一下

给这个布局中再拖一个EditText,然后运行程序,程序运行起来后,按home键返回,桌面的widgets控件会显示“problem loading widget”,意思是加载这个widget出现问题了,现在再拖一个“手机卫士”的widgets控件到手机桌面,它提示“problem loading widget”



原因:

	有一些控件没有办法放到widgets控件里边的,textView可以放进去显示

	看下textView的源码,看到它源码中上边有一个注解@RemoveView

	给widgets布局设置完editText之后,没有办法widgets控件了吧

	再来看下editText的源码,它的源码上边没有这个@RemoveView注解,那再看下button的源码

	它源码上边也有@RemoveView注解



注意:凡是源码中带@RemoteView注解的控件都可以显示在widgets控件的布局里面,没有就不能显示

	  RemoteView翻译后是远程视图



那刚才已经将widgets控件创建出来了(其实意思是将MyWidgets创建出来了,它继承的是AppWidgetsProvider),那接下来要给大家介绍下这个widgets控件的生命周期



在MyWidgets对象中,点击右键,source,	Override/Inplement Methods...,会看到有很多可以实现的方法,把他们都勾选上,有onReceive,onUpdate,onDeleted,onEnabled,onDisabled,分别把他们这几个方法输出一下,看它生命周期是怎么走的,即:



public class MyWidgets extends AppWidgetProvider {



@Override

public void onReceive(Context context, Intent intent) {

	super.onReceive(context, intent);

	System.out.println("onReceive");

}



@Override

public void onUpdate(Context context, AppWidgetManager appWidgetManager,

		int[] appWidgetIds) {

	super.onUpdate(context, appWidgetManager, appWidgetIds);

	System.out.println("onUpdate");

}



@Override

public void onDeleted(Context context, int[] appWidgetIds) {

	super.onDeleted(context, appWidgetIds);

	System.out.println("onDeleted");

}



@Override

public void onEnabled(Context context) {

	super.onEnabled(context);

	System.out.println("onEnabled");

}



@Override

public void onDisabled(Context context) {

	super.onDisabled(context);

	System.out.println("onDisabled");

}



运行程序,将log清空,然后拖动手机卫士到桌面创建一个widgets,查看log发现第一次创建时走的是什么方法,即:



第一次创建

08-21 01:30:02.197: I/System.out(1677): onEnabled

08-21 01:30:02.197: I/System.out(1677): onReceive

08-21 01:30:02.207: I/System.out(1677): onUpdate

08-21 01:30:02.207: I/System.out(1677): onReceive

08-21 01:30:04.947: I/System.out(1677): onReceive



将log清空一下,再来创建一个widgets(第一次创建widgets没有删除掉的情况下),注意这个widgets是可以创建多个的,我再次创建的时候,走的是什么方法,即:



第二次创建

	08-21 01:30:50.299: I/System.out(1677): onUpdate

	08-21 01:30:50.299: I/System.out(1677): onReceive

	08-21 01:30:57.099: I/System.out(1677): onReceive



将log清空,再来创建第三个widgets((第一次和第二次创建widgets没有删除掉的情况下)),它此时走的是什么方法,即:



第三次创建

08-21 01:31:26.969: I/System.out(1677): onUpdate

08-21 01:31:26.969: I/System.out(1677): onReceive

08-21 01:31:29.639: I/System.out(1677): onReceive



我们发现第三次走的方法和第二次创建时走的方法一样,那我们就没有必要再往下创建了



第一次创建时,看到走的是onEnabled,onReceive,onUpdate,onReceive,onReceive方法

第二次和第三次创建时,看到走的是onUpdate,onReceive,onReceive



接下来删除一个widgets,看到走的方法是:



删除第一个

08-21 01:32:26.990: I/System.out(1677): onDeleted

08-21 01:32:26.990: I/System.out(1677): onReceive



将log清空,接下来删除第二个widgets,走的方法是:



删除第二个

	08-21 01:32:54.831: I/System.out(1677): onDeleted

	08-21 01:32:54.831: I/System.out(1677): onReceive



将log清空,接下来删除第三个widgets,走的方法是:



删除最后一个

08-21 01:33:18.391: I/System.out(1677): onDeleted

08-21 01:33:18.391: I/System.out(1677): onReceive

08-21 01:33:18.401: I/System.out(1677): onDisabled

08-21 01:33:18.401: I/System.out(1677): onReceive



总结:

	1.第一次创建的时候会调用 onEnabled

	2.每次创建都会调用 onUpdate

	3.不管什么操作都会调用onReceive

	4.每次删除都会调用onDeleted

	5.删除最后一个调用onDisabled





注意:widgets它本身是一个广播接收者,所以所有的操作都是通过接收广播来执行,所以不管什么操作它都会走onReceive方法



这个就是widgets的生命周期,它是要遵循我们总结的上边这5条的

10.4 拷贝金山卫士widgets控件的布局# (反编译)

这个widgets可以显示出来了,但想要的效果是和金山卫士widgets一模一样的效果



抄一下金山卫士widgets的布局文件了,我给大家准备的资料里有:

Day01资料/安全卫士/APK资料/com.ijinshan.mguard.1324644228599/res

以前找资料时,都是我给你们的类似这种金山卫士的现成的源码,你们在开发中肯定没有你们想要的app的源码

你们拿到的只能是人家打包上架后从各大应用平台下载下来的apk文件



	获取apk源码的方式:



	1.改apk后缀名为zip解压(适用于没有反编译的apk)

		拿到com.ijinshan.mguard.1324644228599.apk之后,首先将后缀apk改名为zip解压一下

		要看这个应用得先看它的清单文件,打开它的androidmanifast.xml文件看下,是乱码,啥东西都看懂

		这个时候注意了,这个时候不能解压了,就要去反编译了,这种方式用于没有反编译的apk



	2.反编译 (适用于反编译后的apk)

	拿到com.ijinshan.mguard.1324644228599.apk,把它粘到桌面上,打开Android逆向助手.exe

	将com.ijinshan.mguard.1324644228599.apk拖进去到源文件路径处,选择操作中的反编译apk,然后点击操作

	它就会在桌面上生成一个文件夹,这个反编译的过程可能会比较慢,看你项目的复杂程度和大小了,项目越大反编译就越慢



	我用的是逆向助手,市面上还有很多反编译工具都可以使用,目的我们都是为了拿到它里边的一些东西,把它反编译出来就可以了



反编译出来后,点击打开里边的AndroidManifest.xml文件就不会乱码了

这个反编译出来的文件夹中还有:

	res文件夹:它里边就是我们反编译出来的这个金山卫士项目的各种资源文件

	lib文件:它里边是armeabi文件,它里边就是各种jar包或者.so文件

	assets文件:这里边就是一些数据库.db文件

	smali文件:这里边存放的都是金山卫士的一些代码,但是它都是.smali后缀



打开a.smali文件,这个一般你是看不太懂的,所以一般不会直接打开看



3.dex转jar (适用于反编译后的apk,比2反编译apk更易读)

	重新打开android逆向助手,将com.ijinshan.mguard.1324644228599.apk拖进去到源文件路径处

	然后将目标文件路径改成桌面新建的aaa文件夹,接下来选择操作时,不要再选择反编译apk了,应该选择dex转jar,然后点击操作



	完成之后会自动用java decompiler帮我们打开这个jar,那我们选择一个a.class文件打开,这里边的代码就可以看到了

	比上边我们选择“反编译apk”直接打开时更易读了一些



但还是没有人家原来写的代码易读,有些对象名都是a表示的,这个是因为这个金山卫士的源码被混淆过了,这个混淆会在后边给大家说



这个就是反编译的一些步骤和过程,以及怎么去看它的代码



反编译出这个资源之后,想要拷贝金山卫士这个widget的布局文件,在清单文件中肯定有注册广播AppWidgetProvider

那打开反编译出来的清单文件,不管你注册的是哪个widgets,你执行的这个action都是一样的,都是:

	

	<intent-filter>

            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

    </intent-filter>



在反编译出来的清单文件中查找下android.appwidget.action.APPWIDGET_UPDATE,找到一个叫做ProcessWidget的receiver



即:



	 <receiver android:name="com.keniu.security.process.ProcessWidget" >

        <intent-filter>

            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

        </intent-filter>



        <meta-data

            android:name="android.appwidget.provider"

            android:resource="@xml/process_widget_provider" />

    </receiver>



要找的就是这个meta-data源数据中的xml文件夹下的process_widget_provider.xml的布局文件



打开反编译的文件夹,打开res文件夹里边的xml,搜索下process_widget_provider.xml,找到这个xml布局文件了

那这里边是不是有个initLayout,那它就表示的是widgets的布局文件,那initLayout属性中的布局文件是process_widget.xml



打开反编译的文件夹,打开layout文件夹,在它里边搜索process_widget.xml



拷贝到我们的layout文件夹下边,并将里边的错误全都修改过来,比如background缺一个图片资源vidget_bg_portrait图片

我们在反编译的drawable文件夹下搜索出来3个图片,全部拷贝到我们的drawable文件夹下



那下边还有id错误,原因是写错了,比如id应该写成android:id="@+id/process_count"

它里边却写成了android:id="@id/process_count",那我们分别给他们添加个“+”改过来



那它还缺styles.xml中的一个样式:



	<style name="widget_text">

		<item name="android:textSize">16.0dip</item>

		<item name="android.textColor">@color/textColorPrimaryDark</item>



把它拷贝到我们的styles.xml文件中,然后它里边还有textColorPrimaryDark,同理找到并拷贝到我们的color.xml文件中



看到Button的background报错,它是在drawable下的,那肯定就是状态选择了,那换成我们以前写的一个状态选择器selector_contact_button.xml



当把所有的错误都修改过来之后,如下:



process_widget.xml



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:background="@drawable/widget_bg_portrait"

    android:gravity="center_vertical" >



    <LinearLayout

        android:layout_width="0.0dip"

        android:layout_height="fill_parent"

        android:layout_marginLeft="5.0dip"

        android:layout_weight="1.0"

        android:background="@drawable/widget_bg_portrait_child"

        android:gravity="center_vertical"

        android:orientation="vertical"

        android:paddingBottom="3.0dip"

        android:paddingTop="3.0dip" >



        <TextView

            android:id="@+id/process_count"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="10.0dip"

            android:text="正在运行软件:7"

            android:textAppearance="@style/widget_text" />



        <ImageView

            android:layout_width="fill_parent"

            android:layout_height="wrap_content"

            android:layout_marginBottom="1.0dip"

            android:layout_marginTop="1.0dip"

            android:background="@drawable/widget_bg_portrait_child_divider" />



        <TextView

            android:id="@+id/process_memory"

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:layout_marginLeft="10.0dip"

            android:text="可用内存:405MB"

            android:textAppearance="@style/widget_text" />

    </LinearLayout>



    <LinearLayout

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:gravity="center_horizontal"

        android:orientation="vertical" >



        <LinearLayout

            android:layout_width="wrap_content"

            android:layout_height="wrap_content"

            android:gravity="center_vertical" >



            <ImageView

                android:layout_width="20.0dip"

                android:layout_height="20.0dip"

                android:src="@drawable/ic_launcher" />



            <TextView

                android:layout_width="wrap_content"

                android:layout_height="wrap_content"

                android:text="@string/app_name"

                android:textColor="@color/textColorPrimary" />

        </LinearLayout>



        <Button

            android:id="@+id/btn_clear"

            android:layout_width="90.0dip"

            android:layout_height="wrap_content"

            android:layout_centerVertical="true"

            android:layout_marginTop="5.0dip"

            android:background="@drawable/button"

            android:text="一键清理"

            />

    </LinearLayout>



</LinearLayout>



运行程序,在SplashActivity的第98行报空指针异常了,即:



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



原因:是上边的所有R文件找不到

解决办法:clean之后再运行一下,这个时候就不会报错了



注意,大家在复制的时候,特别容易出现这个R文件找不到的问题,原因是你复制的太多,编译的时候它就会编译出错,有时候你还会找是不是其他问题,其实你clean一下就ok了,这个是在开发中经常会遇到的问题



再长按手机桌面,弹出的功能中选择widgets,选择它下边的手机卫士西安2号,这时候就会在手机桌面上显示出这个widgets控件了





拷贝金山卫士widgets控件布局的步骤如下:



1.反编译操作

2.根据action找到清单文件中注册的receiver

3.根据resource找到相应的xml文件

4.根据xml文件中的initialLayout找到相应的布局文件

5.拷贝布局文件,并修改错误

10.5.更新widgets中的文本# (重点)

上节课已经将widgets控件实现了,但是这个控件中的textview是写死的,那来看人家金山卫士中的这个textview,它应该是每隔一段时间就可以改变一下,也来实现下这样的操作了,要它每隔一段时间去改变一下



那现在来看下,在前边的example_appwidget_info.xml中,有一个更新时间,即:



	android:updatePeriodMillis="0"



写成0或者1800000都是30分钟去更新一次,但金山卫士的widgets控件中的文本数字,它应该是几秒钟就更新一次

它其实没有用这个更新时间,金山卫士它是写了一个服务,在服务里边写了一个每隔多少时间去更新一下这个桌面控件

那我也可以这么去做了



首先先来创建一个服务WidgetsService,让它继承自Service,然后重写它的onCreate方法

然后在onCreate方法中要去更新widgets,先获取widgets管理者

在这里调用updatesWidgets()方法,并将这个方法创建出来



既然要去更新widgets,这个操作在金山卫士中它每隔一段时间更新一次



	“每隔一段时间”的实现方式:

	1.new一个线程然后再来一个while(true),在里边让它睡上几秒钟(用的比较少)

	2.timer,这个是java中的定时器



	timer中有一个方法schedule(task,when,period)



	参数task:定时执行的任务

	参数when:延迟执行的时间,就是说我打开这个WidgetsService之后,我要执行这个timer,那我参数when写成2000,Timer就会延迟2秒钟去执行这个定时任务

	参数period:是执行的间隔时间,那我也来个2000,它就会每隔2秒钟去执行下定时任务



	看下第一个参数task类型是TimerTask,那new一个TimerTask,它里边有一个run方法,这个相当于也在它里边开启了一个子线程

	那在这个run方法中就可以去执行更新widgets的操作了



既然要更新widgets,肯定和appWidgetManager有关系,用appWidgetManager调用updateAppWidget(provider,views)



参数provider:类型是CompenentName,这个CompenentName以前讲的时候说过,它代表的是一个组件的标识

参数views:类型是RemoteViews,这个RemoteViews在讲textview为什么能够去显示在控件上时有个addRemoteViews



先在外边new一个ComponentName(pkg,cls),

参数pkg:类型是Context,也就是它需要一个上下文,写成WidgetsService.this

参数cls:需要一个class文件,这个就是我们组件的class文件,要更新的是widgets,所以这里写成MyWidgets.class,即:

	ComponentName provider = new ComponentName(WidgetsService.this,MyWidgets.class);



还要在外边new一个RemoteViews(packageName,layoutId)

参数packageName:是应用程序的包名

参数layoutId:是widgets的布局文件



可以通过getPackageName()拿到packageName,layoutId可以通过R.layout.xxx来获取这个布局文件



解释下RemoteViews

	代表的是一个远程布局,什么是远程布局?



		把模拟器中的这个桌面控件放到桌面之后,这个桌面控件就是在桌面上的,那它不是在我们的应用上边,这时这个应用的桌面控件相对应用程序来说就是一个远程布局



那有了这个远程布局文件之后,接下来要更新里边的文字textview,那用views.findViewById()去初始化下布局文件的控件



注意:

	远程布局不能通过findViewById去初始化控件的,远程布局文件提供了另外一种方式:setTextViewText(viewId,text)

	第一个参数:viewId,它是要更新的远程布局的控件的id,第二个参数:text,是要更新的内容



找到布局文件中相应的id为:process_count,它要更新的内容是实时的有多少个进程,那将text写成:“正在运行的软件:”+ProcessUtils.getAvailableProcess(WidgetsService.this),



“正在运行软件”设置完了之后,同理“可用内存”也可以通过这种方式设置



即:



public class WidgetsService extends Service {



	private Timer timer;

	private AppWidgetManager appWidgetManager;



	@Override

	public IBinder onBind(Intent intent) {

	return null;

	}



	@Override

	public void onCreate() {

	super.onCreate();



	appWidgetManager = AppWidgetManager.getInstance(this);

	updatesWidgets();

	}



	/**
  • 更新widgets
     */
     private void updatesWidgets() {

      //new Thread(){
    
      	//public void run() {
    
      		//while(true){
    
      		//	
    
      		//}
    
      	//};
    
      //}.start();
    
      
    
      timer = new Timer();
    
      //task : 定时执行的任务
    
      //when : 延迟执行的时间
    
      //period : 执行的间隔时间
    
      timer.schedule(new TimerTask() {
    
      	
    
      @Override
    
      public void run() {
    
    
    
      	System.out.println("widgets更新了");
    
    
    
      	ComponentName provider = new ComponentName(WidgetsService.this, MyWidgets.class);
    
    
    
      	//RemoteViews 远程布局
    
      	//packageName: 包名
    
      	//layoutId :widgets的布局文件
    
      	RemoteViews views = new RemoteViews(getPackageName(), R.layout.process_widget);
    
      	
    
      	//远程布局是不能findviewbyid初始化控件
    
      	//viewId : 要更新控件的id
    
      	//text : 要更新的内容
    
      	views.setTextViewText(R.id.process_count, "正在运行软件:"+ProcessUtils.getAvailableProcess(WidgetsService.this));
    
      	
    
      	views.setTextViewText(R.id.process_memory, "可用内存:"+Formatter.formatFileSize(WidgetsService.this, ProcessUtils.getAvaliableRam(WidgetsService.this)));
    
    
    
      	//更新widgets操作
    
      	appWidgetManager.updateAppWidget(provider, views);
    
      	
    
      	
    
       }			
    
        }, 2000, 2000);
    
     }
    

    }

    这里的上下文用了WidgetsService.this,当然在这里写成getApplicationContext也是没有问题的

    到这里更新文本的操作我们就做完了,可以在run方法中输出一句话测试一下,即:

      System.out.println("widgets更新了");
    

    现在服务WidgetsService有了之后,还要去清单文件中注册一下,

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

    服务WidgetsService到这就写完了,紧接着要去开启服务,应该在什么时候开启服务

    应该把这个widgets控件创建出来之后再去开启服务,那只有有这个widgets控件了才能去更新这个控件

    回到03课widgets生命周期这里来,看下第一次创建widgets控件时会调用onEnabled方法

    那就可以到onEnabled这个方法里边去做了

    找到MyWidgets.java,在这里边找到onEnabled方法,在这个方法里边开启服务,有开启就应该有关闭

    看下03课widgets生命周期里边,应该在删除最后一个时把服务关闭,所以删除最后一个MyWidgets时,在onDisabled方法中关闭服务

    即:

      @Override
    
      public void onEnabled(Context context) {
    
      	super.onEnabled(context);
    
      	System.out.println("onEnabled");
    
      	//开启widgets服务
    
      	Intent intent = new Intent(context,WidgetsService.class);
    
      	context.startService(intent);
    
      }
    
    
    
      @Override
    
      public void onDisabled(Context context) {
    
      	super.onDisabled(context);
    
      	System.out.println("onDisabled");
    
      	//关闭服务
    
      	Intent intent = new Intent(context,WidgetsService.class);
    
      	context.stopService(intent);
    
      }
    

    运行程序,清空log,清空桌面的手机卫士的widgets,然后在桌面创建一个手机卫士的widgets

    看到log中一直打印“widgets更新了”,表明widgetsService服务开启了,来到模拟器设置中心settings的Apps中的RUNNING中,打开手机卫士西安2号,看到widgetsService更新了

    再回到桌面,看下这个widgetsService,它现在显示只有20个正在运行的软件

    现在打开eclipse的DDMS,在左边删除一些shoujiweishi的进程,看到都是系统的删不掉,点击桌面的三击,再看下桌面的widgets,正在运行的软件变成21了,那原先是20个,在DDMS左边找到三击的进程,然后点击上边的红色按钮stop,变成20个了

    这就表明正在更新我们的文本操作

    更新widgets中的文本的步骤总结如下:

    1.创建一个widgetsService

    2.在WidgetsService中的oncreate方法中获取widget管理者appWidgetManager,并调用updatesWidgets方法来更新widgets

      //1.获取widgets管理者
    
      appWidgetManager = AppWidgetManager.getInstance(this);
    

    3.在widgetsService中创建这个更新widgets的方法updatesWidgets

      /**
    
  • 更新widgets
     */
     private void updatesWidgets() {
     // new Thread(){
     // public void run() {
     // while(true){
     // 
     // }
     // };
     // }.start();
     Timer timer = new Timer();
     //task : 定时执行的任务
     //when : 延迟执行的时间
     //period : 执行的间隔时间
     timer.schedule(new TimerTask() {

      		@Override
    
      		public void run() {
    
      			System.out.println("widgets更新了");
    
      			ComponentName provider = new ComponentName(WidgetsService.this, MyWidgets.class);
    
      			//RemoteViews 远程布局
    
      			//packageName: 包名
    
      			//layoutId :widgets的布局文件
    
      			RemoteViews views = new RemoteViews(getPackageName(), R.layout.process_widget);
    
      			//远程布局是不能findviewbyid初始化控件
    
      			//viewId : 要更新控件的id
    
      			//text : 要更新的内容
    
      			views.setTextViewText(R.id.process_count, "正在运行软件:"+ProcessUtils.getAvailableProcess(WidgetsService.this));
    
      			views.setTextViewText(R.id.process_memory, "可用内存:"+Formatter.formatFileSize(WidgetsService.this, ProcessUtils.getAvaliableRam(WidgetsService.this)));
    
      			//更新widgets操作
    
      			appWidgetManager.updateAppWidget(provider, views);
    
      		}
    
      	}, 2000, 2000);
    
      }
    

    4.在清单文件中注册服务widgetsService

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

    5.在Mywidgets的onEnabled方法中开启服务,在onDisabled方法中关闭服务

      @Override
    
      public void onEnabled(Context context) {
    
      	super.onEnabled(context);
    
      	System.out.println("onEnabled");
    
      	//开启widgets服务
    
      	Intent intent = new Intent(context,WidgetsService.class);
    
      	context.startService(intent);
    
      }
    
    
    
      @Override
    
      public void onDisabled(Context context) {
    
      	super.onDisabled(context);
    
      	System.out.println("onDisabled");
    
      	//关闭服务
    
      	Intent intent = new Intent(context,WidgetsService.class);
    
      	context.stopService(intent);
    
      }
    

10.6 widgets点击事件的实现# (重点)

widgets控件的布局上边还有一个“一键清理”的按钮,金山卫士的widgets中的这个一键清理按钮点击之后,会把正在运行的软件,除了它自己,全部都删除掉



实现一键清理按钮功能:



来到widgetsService.java中的run方法中,因为远程布局是不能findViewById初始化控件的,所以我们同样无法通过findviewById拿到这个“一键清理”的控件id的



没关系,还是在views里边,有一个views.setOnClickPendingIntent(viewId, pendingIntent)



参数viewId:是执行点击事件的控件的id

参数pendingIntent:是一个延迟意图,就是包装了一下intent,那它会执行什么操作呢?

	既然是延迟意图,那你点击这个按钮时才会去执行清理操作,不点击就不会去执行这个intent意图

	那这个pendingIntent相当于可以人为控制它什么时候发送,这个就是延迟意图的用法



第一个参数,点击事件的控件的id就是process_widget.xml中的btn_clear

第二个参数,延迟意图pendingIntent,可以直接创建一个出来

	pendingIntent里边有:

		getActivities:可以跳转到activity中

		getActivity:跳转到一个专门的activity中

		getBroadcast:执行发送广播的操作

		getService:执行开启服务的操作



	pendingintent中执行的这些操作和intent中执行的操作完全相似,就是在内部封装了一个intent,比如:



		PendingIntent.getBroadcast(context, requestCode, intent, flags);



			参数context:上下文

			参数requestCode: 请求码,一般都是空,或者为0

			参数intent:要包装的意图

			参数flags:执行操作的标签



	context写成WidgetService.this,requestCode写成0

	至于flags,我们要执行的是什么操作,我们的目的是要更新这个widgets,它里边专门有一个标签指明你是执行更新的操作

	即PendingIntent.FLAG_UPDATE_CURRENT

	intent参数应该怎么写,我们这个是发送了一个广播(PendingIntent.getBroadcast),要去发送一个广播,应该要去意图中设置一下发送广播的事件,先new一个intent,然后设置发送的广播事件intent.setAction(action),因为这个广播事件是给我们发送的,可以随便写,只要你接收广播时,接收和我们一样的就可以了,所以我们这里action参数可以随便写成,比如“aa.bb.cc”



即:



	public class WidgetsService extends Service {



	private Timer timer;

	private AppWidgetManager appWidgetManager;



	@Override

	public IBinder onBind(Intent intent) {

	return null;

	}



	@Override

	public void onCreate() {

	super.onCreate();



	appWidgetManager = AppWidgetManager.getInstance(this);

	updatesWidgets();

	}



	/**
  • 更新widgets
     */
     private void updatesWidgets() {

      //new Thread(){
    
      	//public void run() {
    
      		//while(true){
    
      		//	
    
      		//}
    
      	//};
    
      //}.start();
    
      
    
      timer = new Timer();
    
      //task : 定时执行的任务
    
      //when : 延迟执行的时间
    
      //period : 执行的间隔时间
    
      timer.schedule(new TimerTask() {
    
      	
    
      @Override
    
      public void run() {
    
    
    
      	System.out.println("widgets更新了");
    
    
    
      	ComponentName provider = new ComponentName(WidgetsService.this, MyWidgets.class);
    
    
    
      	//RemoteViews 远程布局
    
      	//packageName: 包名
    
      	//layoutId :widgets的布局文件
    
      	RemoteViews views = new RemoteViews(getPackageName(), R.layout.process_widget);
    
      	
    
      	//远程布局是不能findviewbyid初始化控件
    
      	//viewId : 要更新控件的id
    
      	//text : 要更新的内容
    
      	views.setTextViewText(R.id.process_count, "正在运行软件:"+ProcessUtils.getAvailableProcess(WidgetsService.this));
    
      	
    
      	views.setTextViewText(R.id.process_memory, "可用内存:"+Formatter.formatFileSize(WidgetsService.this, ProcessUtils.getAvaliableRam(WidgetsService.this)));
    
    
    
      	Intent intent = new Intent();
    
      		//设置发送的广播事件,action
    
      	intent.setAction("aa.bb.cc");
    
    
    
      	PendingIntent.getBroadcast(WidgetsService.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    
    
    
      	views.setOnClickPendingIntent(R.id.btn_clear, pendingIntent);
    
    
    
      	//更新widgets操作
    
      	appWidgetManager.updateAppWidget(provider, views);
    
      	
    
      	
    
       }			
    
        }, 2000, 2000);
    
     }
    

    }

    当执行一键清理时,它要调用views.setOnClickPendingIntent去发送一个延迟意图pendingIntent,那这个延迟意图pendingIntent又去发送了一个广播,既然去发送了一个广播,那我还要去定义一个广播接收者来接收这个广播

    在WidgetsService.java中这里边去写一个广播接收者了,名称是WidgetReceiver,并重写它的onReceive方法,即:

    private class WidgetReceiver extends BroadcastReceiver{

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

    }

    接下来要去注册一下这个广播接收者,用代码注册的方式,在WidgetsService的onCreate方法中注册,还需要一个过滤条件IntentFilter,在这里边就可以设置要接收的广播事件addAction(“aa.bb.cc”),然后用registerReceiver来注册这个广播接收者widgetReceiver

    即:

      private WidgetReceiver widgetReceiver;
    
    
    
      @Override
    
      public void onCreate() {
    
      super.onCreate();
    
    
    
      widgetReceiver = new WidgetReceiver();
    
      IntentFilter intentFilter = new IntentFilter();
    
      intentFilter.addAction("aa.bb.cc");
    
      registerReceiver(widgetReceiver, intentFilter);
    
    
    
      }
    

    有注册就有注销,重写下WidgetsService的onDestroy方法,判断下widgetReceiver不为null的话,就可以去调用unregisterReceiver注销这个widgetReceiver,同时把它置为null,这也是为后边再次接收广播去做准备了

    即:

    @Override

    public void onDestroy() {

      super.onDestroy();
    

    if (widgetReceiver != null) {

      unregisterReceiver(widgetReceiver);
    
      widgetReceiver = null;
    
      }
    

    }

    一键清理操作就是为了清理进程,那在刚才创建的WidgetRecceiver的onReceive方法中调用killprocess()方法,并创建这个方法

    这个方法就是用来清理进程的,那清理进程,首先要new一个ActivityManager

    然后通过am调用getRunningAppProcesses(),返回一个List,把它叫做runningAppProcesses

    用增强for遍历这个集合,不管是用户还是什么的进程都要删除,am里边有一个killBackgroundProcesses(packageName)

    这里边需要一个包名,那runningAppProcesses里边有一个processName,这个表明的就是应用程序的包名,进程名就是包名

    这个am.killBackgroundProcesses(runningAppProcessInfo.processName)表示把所有的应用程序都杀死,那手机卫士自己这个进程不能让它杀死, 所以还要判断如果它的包名equals(object),object写成getPackageName(),前边再给它加个"!",意思是它的包名不等于我们手机卫士的包名时才杀掉

    即:

    /**

  • 清理进程
     */
     public void killprocess() {
     ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
     List runningAppProcesses = am.getRunningAppProcesses();
     for (RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses) {
     if (!runningAppProcessInfo.processName.equals(getPackageName())) {
     am.killBackgroundProcesses(runningAppProcessInfo.processName);
     }
     }
     }

    这个清理进程的操作,前边也写过,这里再给大家回忆了一下

    运行程序,将原先在模拟器桌面上创建的手机卫士的widgets删除掉,重新创建一个widgets出来,打开DDMS,看到左边的模拟器中的手机卫士中有19个进程,点击一键清理杀掉了9个,剩下的这几个杀不掉,所以显示正在运行软件是10个,那这个一键清理的功能就实现了

    使用PendingIntent,一般都是使用这个广播接收者(getBroadcast)的

    在使用一键清理时,一键清理就是杀死进程,远程布局是不能直接去进行点击操作的,必须通过PendingIntent去执行操作,那这个延迟意图PendingIntent里边,要么去发送一个activity,要么去发送一个广播,要么去发送一个服务

    这里我们明显发送广播是最合适的,所以说这里我们才用广播去操作的

    这里比较饶了,又涉及到发送广播和接收广播的操作

    widgets点击事件的实现步骤总结如下:

    1.在timer中执行:

      views.setOnClickPendingIntent(R.id.btn_clear, pendingIntent);
    

    2.需要一个延迟意图,所以代码如下:

      		Intent intent = new Intent();
    
      		//设置发送的广播事件,action
    
      		intent.setAction("aa.bb.cc");
    
      		//context : 上下文
    
      		//requestCode :请求码
    
      		//intent : 要包装的意图
    
      		//flags : 执行操作的标签
    
      		//发送广播
    
      		PendingIntent pendingIntent = PendingIntent.getBroadcast(WidgetsService.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    
      		//viewId :执行点击事件的控件的id
    
      		//pendingIntent : 延迟意图,包装了一下intent,点击按钮的才会去执行清理的操作,不点击就不会去执行这个intent意图
    
      		views.setOnClickPendingIntent(R.id.btn_clear, pendingIntent);
    
      		//更新widgets操作
    
      		appWidgetManager.updateAppWidget(provider, views);
    

    3.因为发送了一个广播,所以在WidgetsService的Oncreate中去用代码注册一个广播

      a.创建一个广播接受者
    
      	private class WidgetReceiver extends BroadcastReceiver{
    
    
    
      		@Override
    
      		public void onReceive(Context context, Intent intent) {
    
      		}
    
      	}
    
      b.在WidgetsService的oncreate中注册
    
      	widgetReceiver = new WidgetReceiver();
    
      	IntentFilter intentFilter = new IntentFilter();
    
      	intentFilter.addAction("aa.bb.cc");
    
      	registerReceiver(widgetReceiver, intentFilter);
    
    
    
      c.在WidgetsService的ondestory中注销
    
      	@Override
    
      	public void onDestroy() {
    
      		super.onDestroy();
    
      		if (widgetReceiver != null) {
    
      			unregisterReceiver(widgetReceiver);
    
      			widgetReceiver = null;
    
      		}
    
      	}
    
    
    
      d.在广播接受者WidgetReceiver的onreceive方法中执行清理进程的操作
    
      	@Override
    
      	public void onReceive(Context context, Intent intent) {
    
      		killprocess();
    
      	}
    
      	
    
      	/**
    
  • 清理进程
     */
     public void killprocess() {
     ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
     List runningAppProcesses = am.getRunningAppProcesses();
     for (RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses) {
     if (!runningAppProcessInfo.processName.equals(getPackageName())) {
     am.killBackgroundProcesses(runningAppProcessInfo.processName);
     }
     }
     }

10.6锁屏清理进程# (重点)

进程管理条目里边,昨天实现了全选,反选,清理操作,设置的操作还没有实现

看下金山卫士的进程管理条目中的设置功能,看到它的设置里边,1.“显示系统进程”的操作,那取消,然后返回,在进程管理中就不显示系统进程了,那再点击设置,勾选这个“显示系统进程”,然后再返回,进程管理条目中的系统进程又显示出来了



这个功能我们也可以实现下,那这里边还有一个2.锁屏时清理进程的功能,这个操作在实际开发中也会经常用的,锁屏做一些事情



实现这两个功能:1.是否显示系统进程,2.是否锁屏清理进程



先来实现是否显示系统进程

	如果不显示系统进程,在进程管理条目中就会刷新界面,把原来展示的系统进程给隐藏掉



在TaskManagerActivity中,先来定义一个boolean值的isShowSystem变量,初始值给它设置为true,这个表示是否显示系统进程,即:



	//是否显示系统进程

	private boolean isShowSystem = true;



显示数据的操作都是在adapter中做的,那要想走adapter显示数据,在TaskManagerActivity中的MyAdapter中先走的是getCount方法,在getCount方法中决定了要显示多少条数据,它现在是:



@Overide

public int getCount(){

	//方便我们从不同的集合中拿出数据

	return userAppInfos.size()+systemAppInfos.size()+2;

}



它现在返回的是userAppInfos.size()加上systemAppInfos.size()再加上2,那可以改成+1,然后再加一个1,即:



@Overide

public int getCount(){

	//方便我们从不同的集合中拿出数据

	return userAppInfos.size()+1+systemAppInfos.size()+1;

}



那isShowSystem如果恒等于true,三元表达式知道吧,恒等于true,显示userAppInfos.size()+1+systemAppInfos.size()+1,如果等于false,只显示userAppInfos.size()+1,即:



//刷新界面会执行

@Override

public int getCount() {

	//方便我们从不同的集合中拿出数据

	return isShowSystem == true ? userAppInfos.size()+1+systemAppInfos.size()+1 : userAppInfos.size()+1;

}



这里有一个设置按钮,那我们这么干了,在金山卫士中它是点击设置进去之后实现的是否显示系统进程的操作

那手机卫士中直接简单去做了,点击设置就让它隐藏掉系统进程,再点击就显示出来,至于锁屏清理的功能,就直接去实现,不让他选择是否锁屏清理了,直接就实现锁屏清理的功能





首先先来到布局文件activity_taskmanager.xml中,找到我们这个设置按钮,把它的点击事件的方法我们在TaskManagerActivity中创建出来



这个方法中,让isShowSystem等于!isShowSystem,这个操作就相当于是true时,改为false,是false时改为true,最后记得还要更新界面,即:



/**
  • 设置

  • @param v
     */
     public void setting(View v){
     //true改为false false改为true
     isShowSystem = !isShowSystem;
     //更新界面
     myAdapter.notifyDataSetChanged();
     }

    运行程序, 点击设置之后,直接就把系统进程给隐藏了,再点击设置,系统进程就显示出来了

    这个就是隐藏显示系统进程的操作,比较简单,就是声明了一个变量isShowSystem,通过变量来控制MyAdapter中的getCount方法中显示数据的条数,是true显示全部数据,false表示隐藏掉系统进程,那就只显示用户的进程就可以了

    接下来实现锁屏时清理进程的操作

      其实就是锁一下屏幕,就直接清理进程
    

    既然要去锁屏,就要去监听下锁屏的操作,又要创建一个广播接收者叫做ScreenOffReceiver,即:

    public class ScreenOffReceiver extends BroadcastReceiver{

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

    }

    紧接着到清单文件中去配置,即:

    <receiver

      android:name="cn.itcast.mobilesafexian02.receiver.ScreenOffReceiver">
    
      <intent-filter>
    
      	<action android:name="android.intent.action.SCREEN_OFF" />
    
      </intent-filter>
    

    先到广播接收者ScreenOffReceiver的onReceive方法中输出一下,即:

    public class ScreenOffReceiver extends BroadcastReceiver{

      @Override
    
      public void onReceive(Context context,Intent intent){
    
      	System.out.println("锁屏了......");
    
      }
    

    }

    运行程序,点击模拟器的锁屏按钮(电源键),但是我们在log中发现它没有打印"锁屏了…"

      原因:
    
      		锁屏和解锁的广播接收者不能在清单文件中注册,必须使用代码进行注册
    

    谷歌工程师为了避免一些恶意程序的骚扰

      市面上有一种流氓软件会监听锁屏状态,监听到你锁屏的状态会给你去下载一些恶心的应用或流氓应用,监听到你解锁的状态会给你打开这个应用
    
      为了避免这种情况的发生,解锁和锁屏的操作必须得用代码注册
    
      代码注册的优势是程序运行才会去执行,这样就可以避免这种情况发生
    

    既然不能在清单文件中注册,就到广播接收者当中去注册了

    先把ScreenOffReceiver.java删除掉,把清单文件中注册的ScreenOffReceiver也删除掉

    在服务WidgetService的onCreate方法中,注册一个锁屏的广播接收者,

    并在服务WidgetService中创建这个锁屏的广播接收者ScreenOffReceiver

    有注册就应该有注销,在服务WidgetService中的onDestroy方法中注销这个广播接收者,同样的操作,先判断一下这个广播接收者不为null之后再注销,最后将这个广播接收者置为null

    接下来在锁屏时,就可以去锁屏的广播接受者ScreenOffReceiver的onReceive方法中清理进程了

    清理进程的操作就是killprocess()

    运行程序,先在模拟器上多启动几个进程,一键锁屏,三击,小火箭,振动,然后将原来的手机卫士widgets删掉

    这是因为之前创建了要重新覆盖的原因,现在重新创建一个widgets,然后点击模拟器的锁屏按钮(电源键),然后看下DDMS左边,看到直接杀掉了好多进程,那这个锁屏清理进程的操作也完成了

    问题,锁屏了,但是在log中看到这个widgets还一直在更新,即:

      System.out  widgets更新了
    
      System.out  widgets更新了
    
      System.out  widgets更新了
    
      ...
    

    当你锁屏之后,如果还有一些操作,比如服务还在执行,这个操作会很费电的,其实这个操作就可以定义为一个流氓程序,工作中要做一个有良心的程序员,要站在用户的角度去想,不能这么干

    以前搜狗地图有定位这样一个操作,它在锁屏时没有进行处理还一直在进行定位,定位要开启gps,一直要和通讯卫星进行通讯

    后果是用户锁屏之后过了两三个小时再次解锁时,发现关机了,一些电量查询软件可以查询出电量和流量都耗费到哪了

    用户发现这个搜狗地图很费电,全都不用了导致搜狗地图基本快要下架了,后来修改了更新时说了一句,进行了CPU级的省电优化

    其实就是在锁屏时停止定位,解锁时重新定位,这样就可以省很多电

    在锁屏时,需要停止更新操作,这里就是停止更新widgets,怎么停止,在timer里边执行了一个定时TimeTask任务,其实要停止这个操作很简单,在ScreenOffReceiver的onReceive方法中,调用一个方法stopupdate(),并创建这个方法

    在stopupdate()方法中判断timer不为null时,用timer调用cancel(),它是一个取消的操作

    这样就可以把更新的操作给取消掉(timer定时器取消了,timer里边更新widgets的代码也就不执行了,也就停止更新widgets了)

    stopupdate()在锁屏的广播ScreenOffReceiver中调用了一下,在widgetsService服务关闭时,也可以去调用一下让widgets停止更新

    运行程序,将原来的widgets删除,重新生成一个widgets,打开log看下,widgets开始更新了,即:

      System.out	widgets更新了
    
      System.out	widgets更新了
    
      System.out	widgets更新了
    
      ...
    

    点击模拟器的锁屏按钮,将log清空掉,发现不打印widgets更新了,那就说明widgets不在更新了

    接下来解锁之后还要让去重新更新widgets

    接下来还要去注册一个解锁的广播接收者,同样在服务WidgetsService中的onCreate方法中注册

    并在服务WidgetService中创建这个解锁的广播接收者ScreenOnReceiver

    有注册就应该有注销,在服务WidgetService中的onDestroy方法中注销这个广播接收者,同样的操作,先判断一下这个广播接收者不为null之后再注销,最后将这个广播接收者置为null

    在解锁的广播接收者ScreenOnReceiver的onReceive方法中调用更新widgets的方法updateWidgets()

    即:

    public class WidgetsService extends Service {

    private ScreenOffReceiver screenOffReceiver;

    private ScreenOnReceiver screenOnReceiver;

    @Override

    public void onCreate(){

      //注册锁屏的广播接收者
    
      screenOffReceiver = new ScreenOffReceiver();
    
      //设置过滤条件
    
      IntentFilter screenoffintentfilter = new IntentFilter();
    
      screenoffintentfilter.addAction(Intent.ACTION_SCREEN_OFF);
    
      //注册广播接收者
    
      registerReceiver(screenOffReceiver, screenoffintentfilter);
    
    
    
      //注册解锁的广播接受者
    
      screenOnReceiver = new ScreenOnReceiver();
    
      //设置过滤条件
    
      IntentFilter screenonintentfilter = new IntentFilter();
    
      screenonintentfilter.addAction(Intent.ACTION_SCREEN_ON);
    
      //注册广播接受者
    
      registerReceiver(screenOnReceiver, screenonintentfilter);
    

    }

    private class ScreenOffReceiver extends BroadcastReceiver{

      @Override
    
      public void onReceive(Context context, Intent intent) {
    
      		killprocess();
    
      		//停止更新widgets
    
      		stopupdate();
    
      	}
    

    }

    private class ScreenOnReceiver extends BroadcastReceiver{

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

    }

    @Override

    public void onDestroy() {

      super.onDestroy();
    
    
    
      //停止更新widgets
    
      stopupdate();
    
    
    
      //注销服务widgetReceiver
    
      if (widgetReceiver != null) {
    
      	unregisterReceiver(widgetReceiver);
    
      	widgetReceiver = null;
    
      }
    
    
    
      //注销锁屏的广播接受者
    
      if (screenOffReceiver != null) {
    
      unregisterReceiver(screenOffReceiver);
    
      screenOffReceiver = null;
    
      }
    
      
    
      //注销解锁的广播接受者
    
      if (screenOnReceiver != null) {
    
      	unregisterReceiver(screenOnReceiver);
    
      	screenOnReceiver = null;
    
      }
    

    }

    /**

  • 停止更新widgets
     */
     private void stopupdate(){
     if (timer != null) {
     timer.cancel();
     timer = null;
     }
     }

    }

    接下来再来运行一下,删除原来的widgets,并将log清空,重新创建一个widgets,看到log中开始打印widgets更新了,即:

      	Systemout widgets更新了
    
      	Systemout widgets更新了
    
      	Systemout widgets更新了
    
      	...
    

    进行锁屏操作,并将log清空,看到log中就不再打印“widgets更新了”,说明已经停止更新了,现在进行解锁操作,在log中又开始打印“widgets更新了”,说明widgets已经可以重新更新了,即:

      	Systemout widgets更新了
    
      	Systemout widgets更新了
    
      	Systemout widgets更新了
    
      	...
    

    然后再锁屏,在log中就不打印“widgets更新了”,那清空log,再解锁,又打印“widgets更新了”

    说明我解锁之后,让widgets继续更新的操作也完全正确了

    本节两个功能总结如下:

    1.是否显示系统进程的功能:

      a.创建一个boolean变量,表示是否显示系统进程
    
    
    
      	//是否显示系统进程
    
      	private boolean isShowSystem = true;
    
    
    
      b.在Myadapter的getCount方法进行判断
    
    
    
      	@Override
    
      	public int getCount() {
    
    
    
      		//方便我们从不同的集合中拿出数据
    
      		return isShowSystem == true ? userAppInfos.size()+1+systemAppInfos.size()+1 : userAppInfos.size()+1;
    
      	}
    
    
    
      c.在设置的点击事件中更改boolean变量的值
    
    
    
      	/**
    
  • 设置

  • @param v
     */
     public void setting(View v){
     //true改为false false改为true
     isShowSystem = !isShowSystem;
     //更新界面
     myAdapter.notifyDataSetChanged();
     }

    2.锁屏清理进程功能:

    锁屏和解锁的广播接收者不能在清单文件中注册,必须使用代码进行注册,避免一些恶意程序的骚扰

    锁屏和解锁的广播接收者实现步骤相同,即:

      a.在服务WidgetsService中创建一个广播接受者
    
      b.在服务WidgetsService的Oncreate方法中注册广播接受者
    
      c.在服务WidgetsService的ondestory方法中注销广播接受者
    
      d.在广播接受者的onreceive方法实现各自的功能
    

10.8 widgets的bug的处理#

已经可以实现时刻更新widgets控件了,到模拟器的Settings中的Apps的Running中看下,在手机卫士应用中开启了一个widgetsService服务去进行操作的,但是不要忘了,用户可以直接来到这个Settings的app下的running中直接点击widgetsService服务中的stop按钮

把这个widgetsService服务给清理掉,现在点击下这个stop,然后回到桌面看下widgets就不会进行更新了



市面上是怎么解决这个问题,这时就要用到前边讲的example_appwidget_info.xml中的更新时间的属性:

	android:updatePeriodMillis="0"



前边说过这个时间最小不能小于30分钟,这个时间到了之后会去调用MyWidgets中的onUpdate方法

所以一般会这么去干,找到MyWidgets.java,然后看下原先开启服务时,是在onEnabled方法中开启的

既然在更新时间到了之后会去调用onUpdate方法,那再看下widgets的生命周期,即:



	

	第一次创建

	08-21 01:30:02.197: I/System.out(1677): onEnabled

	08-21 01:30:02.197: I/System.out(1677): onReceive

	08-21 01:30:02.207: I/System.out(1677): onUpdate

	08-21 01:30:02.207: I/System.out(1677): onReceive

	08-21 01:30:04.947: I/System.out(1677): onReceive



看到第一次创建时,也会调用onUpdate方法,那把开启服务WidgetsService的操作从onEnabled方法中移动到onUpdate方法中,即:



	@Override

	public void onUpdate(Context context, AppWidgetManager appWidgetManager,

			int[] appWidgetIds) {

		super.onUpdate(context, appWidgetManager, appWidgetIds);

		System.out.println("onUpdate");

		//开启widgets服务

		Intent intent = new Intent(context,WidgetsService.class);

		context.startService(intent);

	}



这样就可以避免出现用户手动点击Settings中的stop把widgetsService服务给清理掉之后,桌面widgets停止更新的操作

可能用户一开始发现这个widgetsService服务很烦人给stop停止掉了,但是她并没有意识到这个服务是用来更新桌面widgets

等她发现时可能半个小时过去了,但是这时我们的更新时间到了,所以又把widgetsService服务给启动起来了

当用户再看桌面widgets时,发现widgets还是正常运行的,这样就可以减少用户发现桌面widgets更新失败的几率

因为如果用户发现你这个widgets更新失败了,就会直接删除这个widgets

我们的初衷肯定是用户使用的次数越多越好,所以将开启服务的操作由onEnabled方法移动到onUpdate方法中

可以减少这种事情的发生,这个是没有办法避免的,因为用户在设置中心中执行的所有操作,比如卸载程序,停止服务,全部都是用户的主观行为,用户主观行为没有办法阻止,所以只能用别的办法来减少这种事情的发生





总结如下:



bug : 用户从设置中心关闭服务,造成widgets无法更新的问题

updatePeriodMillis 更新时间到了之后就会去调用onUpdate方法

处理方式:将开启服务的操作移植到onUpdate方法

10.9 屏幕适配 # (重点,面试)

软件管理条目中点击listview中的某个软件,会弹出一个popwindow,当初在写这个popwindow时距离屏幕左边框距离加了50

在SoftManagerActivity的listView的条目的点击事件中,有一个气泡弹出来的操作



即:

	//3.2.显示气泡

	//parent : 表示popupwindow要挂载到那个控件中

	//gravity、x、y就是控制popupwindow显示位置

	//50 像素

	popupWindow.showAtLocation(parent, Gravity.LEFT

Gravity.TOP, x+50, y);

开一个small小分辨率的模拟器,再开一个big大分辨率的模拟器,再加上使用的这个分辨率的模拟器

将手机卫士分别运行在这三个模拟器上,三个模拟器上手机卫士的主界面没有太大区别,就是big大分辨率的模拟器下方的空白比较多

我们经常使用的这个模拟器上显示的比较好,small小分辨率的模拟器上显示的有点小



分别点开这三个模拟器中的手机卫士中的软件管理界面,分别将popwindow点开,popwindow显示的位置稍微有点不一样



用size.exe软件分别测量,发现3个模拟器上,popwindow距离左边屏幕都是50px,应该距离左边屏幕的距离不一样显示才正确



三种屏幕中显示时出现差别是因为用px代替dp去设置了,android中使用的是dp,所以要将px转化为dp去设置



	dp和px的区别:

		dp:是屏幕像素和屏幕尺寸的比例

		px:屏幕上的像素点



1.使用DensityUtils工具类去适配



资料里边有"android dp和px之间转换.txt",打开看到是一个DensityUtil工具类



把这个工具类创建出来,直接拷贝"android dp和px之间转换.txt"



即:



DensityUtil.java



public class DensityUtil {

	/** 
  • 根据手机的分辨率从 dip 的单位 转成为 px(像素)
     */ 
     public static int dip2px(Context context, float dpValue) { 
     final float scale = context.getResources().getDisplayMetrics().density; //通过资源获取屏幕的密度
     return (int) (dpValue * scale + 0.5f); //四舍五入 3.7 3 3.7+0.5 = 4.2 4
     }

      /** 
    
  • 根据手机的分辨率从 px(像素) 的单位 转成为 dp
     */ 
     public static int px2dip(Context context, float pxValue) { 
     final float scale = context.getResources().getDisplayMetrics().density; 
     return (int) (pxValue / scale + 0.5f); 
     } 
     }

    final float scale = context.getResources().getDisplayMetrics().density;

    这一行代码就是通过资源获取屏幕的密度,方法dip2px参数处的dpValue指的是传入的dp值,去乘以上这个屏幕密度,然后加上个0.5f,就转化成px了,之所以加0.5f,是因为要四舍五入

    程序中的四舍五入有点特别,比如3.7四舍是3,程序中加0.5操作是:3.7加上0.5等于4.2,四舍就是4

    工作中用到屏幕适配了,把DensityUtil类拷贝过去用就行,很少有人去记这个

    一般都是人手保存一份,用的时候直接粘贴复制就可以了

    其实它就是一个计算的过程,这个工具类有了之后到上边那个问题处,即:

    //3.2.显示气泡

    //parent : 表示popupwindow要挂载到那个控件中

    //gravity、x、y就是控制popupwindow显示位置

    //50 像素

    popupWindow.showAtLocation(parent, Gravity.LEFT|Gravity.TOP, x+50, y);

    这个50是像素,最终要得到的是像素px,那用DensityUtil.dip2px(getApplicationContext,50),y)

    即:

    //3.2.显示气泡

    //parent : 表示popupwindow要挂载到那个控件中

    //gravity、x、y就是控制popupwindow显示位置

    //50 像素

    popupWindow.showAtLocation(parent, Gravity.LEFT|Gravity.TOP, x+DensityUtil.dip2px(getApplicationContext(), 50), y);

    依次运行程序到3个模拟器,依次打开手机卫士中软件管理界面中的popwindow,他们的位置就一样了

    用size.exe软件再分别测量:

      	small小分辨率的模拟器上,popwindow距离左边屏幕40px
    
      	经常使用的模拟器上,popwindow距离左边屏幕50px
    
      	big大分辨率模拟器上,popwindow距离左边屏幕70px
    

    三个屏幕上测量出来距离左边屏幕的距离不一样,这才算显示正确

    所以上边不应该用50px,而应该用50dp根据不同屏幕分辨率转化后的px,这就是屏幕适配

    其实就是将像素转化成dp,或该显示像素时,要用dp转化成像素再显示

    popwindow距离屏幕左边框这里用到了像素,还有什么地方用到像素了?

    在SetUpBaseActivity中的onFling方法中的:

    if (Math.abs(startY-endY) > 50) {

    中的这个50,以及:

    //下一步

    if ((startX-endX) >100) {

    中的这个100,以及:

    //上一步

    if ((endX-startX) > 100) {

    中的这个100

    这三个都是像素,都是可以去dip2px屏幕适配

    还有一些比如说DragViewActivity(控件随着手势移动而移动的activity)中的:

    if (mHits[0] >= (SystemClock.uptimeMillis()-500)) {//判断是否是第三次点击

    注意,这里这个500不是px,继续往下看,即:

    //判断控件是否移出屏幕

    if (l < 0 || r > width || t < 0 || b > height - 25) {

      break;
    

    }

    这里有个25,它也要转化一下,其实都是用DensityUtil.java中的dip2px转化成像素去操作的就可以了

    这个DensityUtil.java用的很多,基本每个项目必有,所以一定要知道而且会用

    还有另一种屏幕适配方式也要给大家简绍一下了,这种也是比较常用的:

    分别将三个模拟器上的手机卫士进入到设置中心界面,看着没什么问题

      但是big大分辨率的模拟器上设置中心界面底部空白处,再放两个item条目都可以
    
      经常使用的这个模拟器上设置中心界面底部空白处,再放一个item条目可以
    
      small小分辨率的模拟器上设置中心界面底部空白处,一个item条目都放不下了,再增加就看不到了
    

    2.像这种再增加item条目就看不到了,去适配时是通过ScrollView控件去适配的

    在activity_setting.xml中的根布局处,用ScrollView控件来达到适配

    看下ScrollView控件,它有一个上下的箭头,表示里边的内容可以向下向上滑动,那在根标签处用ScrollView来包裹整个布局

    并且将原来根布局LinearLayout中的

      域名空间:
    
      xmlns:itcast="http://schemas.android.com/apk/res/cn.itcast.mobilesafexian02"
    
      xmlns:itcast="http://schemas.android.com/apk/res/cn.itcast.mobilesafexian02"
    
    
    
      剪切到ScrollView控件中,即:
    
    <?xml version="1.0" encoding="utf-8"?>

    <ScrollView xmlns:android=“http://schemas.android.com/apk/res/android”

          xmlns:itcast="http://schemas.android.com/apk/res/cn.itcast.mobilesafexian02"
    
          android:layout_width="match_parent"
    
          android:layout_height="match_parent" >
    

    <LinearLayout

      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:background="#8866ff00"
    
          android:gravity="center_horizontal"
    
          android:paddingBottom="10dp"
    
          android:paddingTop="10dp"
    
          android:text="设置中心"
    
          android:textSize="25sp" />
    
    
    
      <cn.itcast.mobilesafexian02.ui.SettingView
    
          android:id="@+id/sv_setting_update"
    
          android:layout_width="match_parent"
    
          android:layout_height="wrap_content"
    
          itcast:des_off="关闭提示更新"
    
          itcast:des_on="打开提示更新"
    
          itcast:title="提示更新" >
    
      </cn.itcast.mobilesafexian02.ui.SettingView>
    
    
    
      <cn.itcast.mobilesafexian02.ui.SettingView
    
          android:id="@+id/sv_setting_address"
    
          android:layout_width="match_parent"
    
          android:layout_height="wrap_content"
    
          itcast:des_off="关闭显示号码归属地"
    
          itcast:des_on="打开显示号码归属地"
    
          itcast:title="显示号码归属地" >
    
      </cn.itcast.mobilesafexian02.ui.SettingView>
    
    
    
      <cn.itcast.mobilesafexian02.ui.SettingClickView
    
          android:id="@+id/scv_setting_changedbg"
    
          android:layout_width="match_parent"
    
          android:layout_height="wrap_content" >
    
      </cn.itcast.mobilesafexian02.ui.SettingClickView>
    
    
    
      <cn.itcast.mobilesafexian02.ui.SettingClickView
    
          android:id="@+id/scv_setting_changedlocation"
    
          android:layout_width="match_parent"
    
          android:layout_height="wrap_content" >
    
      </cn.itcast.mobilesafexian02.ui.SettingClickView>
    
    
    
      <cn.itcast.mobilesafexian02.ui.SettingView
    
          android:id="@+id/sv_setting_blacknum"
    
          android:layout_width="match_parent"
    
          android:layout_height="wrap_content"
    
          itcast:des_off="关闭黑名单拦截"
    
          itcast:des_on="打开黑名单拦截"
    
          itcast:title="黑名单拦截" >
    
      </cn.itcast.mobilesafexian02.ui.SettingView>
    
      
    
       <cn.itcast.mobilesafexian02.ui.SettingView
    
          android:id="@+id/sv_setting_watchdog"
    
          android:layout_width="match_parent"
    
          android:layout_height="wrap_content"
    
          itcast:des_off="关闭软件锁"
    
          itcast:des_on="开启软件锁"
    
          itcast:title="软件锁" >
    
      </cn.itcast.mobilesafexian02.ui.SettingView>
    
      </LinearLayout>
    

    运行程序到small小分辨率的模拟器上,还和以前一样,它现在不能滑动,是现在布局还没有超出界面

    那再增加一个item,然后运行程序到small小分辨率的模拟器上就可以滑动了,这个就是ScrollView的操作

    界面显示的条目看不完时就会有滑动的效果,刚好够就不用去滑动了

    这个也是适配时经常用的一种控件

    用ScrollView控件达到适配的效果,在新闻类,评论列表比较常用

    有些新闻比较长一屏显示不完,用Scroll的话,往下滑动就可以显示下边的内容

    注意:ScrollView控件是能够让屏幕进行滚动的一个控件,但是ScrollView中只能有一个子控件不能有多个子控件

    上边的布局ScrollView中就只包了一个LinearLayout,这点大家要注意了

    这里简单提了下屏幕适配,后边还会详细讲屏幕适配

    我们这里就给大家讲了两种常见的屏幕适配,总结如下:

    1.使用工具类DensityUtil.java将代码中所有需要使用px像素的地方,用dip2px方法转为px之后使用

    2.再增加item条目就看不到布局,通过ScrollView控件去适配

10.10 创建快捷方式# (重点)

金山卫士通话管理条目中有个一键呼叫,点开之后看到有:

	1.点击“添加”,加入一键呼叫的联系人

	2.进入手机桌面,点击添加的联系人立即呼叫

	3.一键呼叫联系人列表为空,快来添加

	然后底部有两个按钮,一个是“添加”,一个是“删除”



点击添加按钮,选择zhangsan,点击确定,一键呼叫联系人列表中就有zhangsan了,桌面上有一个zhangsan的快键图标(头像+名称)

这就是快键方式,点击快键方式就直接给张三拨打电话了



打开快键方式总结如下:

	1.可以给软件创建快捷方式

	2.可以修改快捷方式的名称

	3.可以修改快捷方式的图标

	4.设置快键方式执行的操作

	这4步打开快键方式的操作也是安卓中要执行的操作





	添加的快键方式直接添加到桌面上,表明桌面上应该有一个操作可以接收快键方式



		看下手机桌面源码,在Day01资料/安全卫士/packages,这里边都是安卓系统中的源码

		在packages中有个apps/launcher2,这个launcher2就是系统的桌面应用

		打开launcher2中的AndroidMinifest.xml



工作中公司可能会让你做一个launcher桌面应用,这个要注意了,别到时候说让你做一个launcher应用,你说不会,其实就是做一个app

然后在清单文件中加一些属性,这个app就是launcher桌面应用了

	如何让你的应用变成launcher桌面应用:

	在注册activity时加如下属性即可

即:

	<intent-filter>

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

		<category android:name="android.intent.category.HOME"/>

		<category android:name="android.intent.category.DEFAULT"/>

		<category android:name="android.intent.category.MONKEY"/>

	</intent-filter>



launcher其实就是一个应用,就是在清单文件中注册activity时,添加了如上属性,尤其是category.HOME这个属性

就把你的应用变成了一个launcher应用



接着看launcher2的清单文件,它里边的广播接收者receiver中有一个如下属性:



android:name="com.android.launcher2.InstallShorycutReceiver"



这个属性就是创建快键方式的操作,它在广播接收者receiver中,即:



<receiver

	android:name="com.android.launcher2.InstallShorycutReceiver"

	android:permission="com.android.launcher.permission.INSTALL_SHORTCUT">

	<intent-filter>

		<action android;name="com.android.launcher.action.INSTALL_SHORTCUT"

	</intent-filter>

</receiver>



在创建快键方式时,就是给桌面的这个广播接收者InstallShorycutReceiver发送了一个广播,它接收到广播之后就去创建了一个快键方式



action中的"com.android.launcher.action.INSTALL_SHORTCUT"就是广播事件





写一个示例工程“快键方式” 包名取为com.example.shortcut

在示例工程的activity_main.xml布局中,添加一个按钮并添加点击事件属性名称为shortcut



即:



	<Button

		android:id="@+id/button1"

		android:layout_width="wrap_content"

		android:layout_height="wrap_content"

		android:layout_alignLeft="@+id/textView1"

		android:layout_below="@+id/textView1"

		android:layout_marginLeft="38dp"

		android:layout_marginTop="48dp"

		android:text="添加快键方式"

		android:onClick="shortcut"

		/>



MainActivity中实现点击事件的方法shortcut,在shortcut方法里就可以去实现添加快键方式的操作了

那添加快键方式的操作就是去发送一个广播



先来new一个Intent,在Intent的参数处可以直接写一个action,把这个action就写成"com.android.launcher.action.INSTALL_SHORTCUT"



用intent去putExtra(name,value)来设置快键方式的名称

	参数name:写成intent.EXTRA_SHORTCUT_NAME

	参数value:写成"手机卫士西安2号"



用intent去putExtra(name,value)来设置快键方式的图标



	参数name:写成intent.EXTRA_SHORTCUT_ICON

	参数value:它要是Bitmap类型

		Bitmap可以通过BitmapFactory.decodeResource(res,id)来获取,

			参数res:资源,写成getResources()来获取

			参数id:图片的id,写成R.drawable.ic_launcher

		会返回一个Bitmap,把它叫做bitmap,即:



	Bitmap bitmap = BitmapFactory.decodeResource(getResources(),

					R.drawable.ic_launcher);



然后就可以把这个bitmap放入到intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap)的value参数处了



快键图标设置完之后,还可以用intent去putExtra(name,value)来设置快捷方式要执行的操作



	参数name:写成Intent.EXTRA_SHORTCUT_INTENT

	参数value:需要一个intent,再new一个Intent,名称叫做intent2,拨打电话的操作还记得不,用intent2去setAction(action)

		参数action:写成Intent.ACTION_CALL



	还要设置一个data数据,用intent2去setData(data)

		参数data:要写成Uri.parse(uriString)

			参数uriString:"tel:"+110,这个是拨打电话的操作



然后就可以把这个intent2放入到设置快捷方式要执行的操作的value参数处了



这个操作做完之后,因为手机桌面清单文件中是一个广播接收者,即:



<receiver

	android:name="com.android.launcher2.InstallShorycutReceiver"

	android:permission="com.android.launcher.permission.INSTALL_SHORTCUT">

	<intent-filter>

		<action android;name="com.android.launcher.action.INSTALL_SHORTCUT"

	</intent-filter>

</receiver>



那接下来要用sendBroadcast(intent)发送一个广播给手机桌面,注意这里参数处是intent,不是intent2



手机桌面清单文件中的receiver中还标明需要一个权限,拿到示例工程的清单文件中添加如下权限:

	android:permission="com.android.launcher.permission.INSTALL_SHORTCUT"



即:



public class MainActivity extends Activity{



	@Override

	protected void onCreate(){

		super.onCreate(savedInstanceState){

		setContentView(R.layout.activity_main);

	}



	/**
  • 创建快捷方式操作
     */
     private void shortCut() {

      	// 添加快捷方式
    
    
    
      	// 发送广播
    
      	Intent intent = new Intent(
    
      			"com.android.launcher.action.INSTALL_SHORTCUT");
    
      	// 设置快捷方式的名称
    
      	intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "手机卫士西安2号");
    
      	// 设置快捷方式的图标
    
      	Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
    
      			R.drawable.ic_launcher);
    
      	intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
    
      	
    
      	//隐示意图
    
      	Intent intent2 = new Intent();
    
      	intent2.setAction("cn.itcast.mobilesafexian02.home");
    
      	intent2.addCategory("android.intent.category.DEFAULT");
    
      	
    
      	
    
      	//设置快捷方式要执行的操作
    
      	intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent2);
    
    
    
      	// 发送广播
    
      	sendBroadcast(intent);
    
    
    
      }
    

    }

    运行示例工程“快键方式”,点击按钮“添加快键方式”,然后回到手机桌面上,有一个“拨打电话110”的快键方式

    点击它直接就拨打出去了,这个就是创建快键方式的步骤

    其实就是发送一个广播,然后在intent里边进行设置快键方式的名称,图标,拨打电话操作

    现在将这个快键方式移植到手机卫士中

    一般这个快键方式在创建时,都是在应用刚打开时去创建,所以一般都是在SplashActivity里的onCreate方法里边调用shortCut()方法,然后创建出这个方法,这个就是创建快键方式的操作,那可以直接从示例工程的MainActivity中拷贝过来

    然后将putExtra的第二个参数由“拨打电话110”改成我们应用的名称“手机卫士西安2号”,即:

      // 设置快捷方式的名称
    
      intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "手机卫士西安2号");
    

    图标不用换,拨打电话的操作不要了,点击这个快键方式,是为了打开自己的应用界面,一般都是打开自己应用的homeactivity

    那要new一个intent2,即:

      Intent intent2 = new Intent(this,HomeActivity.class);
    

    最后记得添加权限:

      	android:permission="com.android.launcher.permission.INSTALL_SHORTCUT"
    

    运行手机卫士西安02,桌面生成了“手机卫士西安02”快键方式了,点击快键方式提示说"App isn’t installed",意思是app没有安装

    原因:在创建快键方式的方法shortCut中,是直接让他跳转到HomeActivty中去,在桌面创建完快键方式之后,快键方式就已经是属于桌面了,不再属于我们的应用了,那你在桌面通过这种显示意图来打开HomeActivity,这个桌面它没有HomeActivity

    所以这里要用隐式意图,首先要来个intent2.setAction(action)

    这个action就是要打开的这个activity中的过滤条件,要在清单文件中注册HomeActivity的地方去设置一下过滤条件

    这个清单文件中设置过滤条件中的action,我们可以随便写成任何东西(只要保证和隐式意图setAction()的参数处保持一致即可)

    当然一般是写成包名“cn.itcast.mobilesafexian02”加上一个唯一标识(比如说是homeActivity,我们就写成home),即:

      	"cn.itcast.mobilesafexian02.home"
    

    一般有action,就会有category,这个是action的一个附加信息,一般我们用的时候就写成默认的(DEFAULT)就可以了

    即:

        <activity android:name=".HomeActivity" >
    
          <intent-filter>
    
              <action android:name="cn.itcast.mobilesafexian02.home" />
    
    
    
              <category android:name="android.intent.category.DEFAULT" />
    
          </intent-filter>
    
      </activity>
    

    在清单文件中给注册的HomeActivity添加完过滤intent-filter之后

    就可以回到SplashActivity中的shortCut方法中,给隐式意图intent2去setAction(action),设置要打开的这个HomeActivity中的过滤条件中的action,那把在清单文件中添加的action的值拷贝过来,即:

      intent2.setAction("cn.itcast.mobilesafexian02.home");
    

    然后紧接着还要用隐式意图intent2去addCategory(category)来设置要打开的这个HomeActivity中的过滤条件中的category,即:

      	"android.intent.category.DEFAULT"
    

    即:

    public class SplashActivity extends Activity {

      private SharedPreferences sp;
    
    
    
      @Override
    
      protected void onCreate(Bundle savedInstanceState) {
    
      	super.onCreate(savedInstanceState);
    
      	setContentView(R.layout.activity_splash);
    
      	
    
      	sp = getSharedPreferences("config", MODE_PRIVATE);
    
      	shortCut();
    
      }
    
    
    
      /**
    
  • 创建快捷方式操作
     */
     private void shortCut() {

      	if (sp.getBoolean("firstEnter", true)) {
    
    
    
      		// 添加快捷方式
    
    
    
      		// 发送广播
    
      		Intent intent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
    
      		// 设置快捷方式的名称
    
      		intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "手机卫士西安2号");
    
      		// 设置快捷方式的图标
    
      		Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
    
      				R.drawable.ic_launcher);
    
      		intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap);
    
    
    
      		//隐示意图 从桌面跳转到HomeActivity
    
      		Intent intent2 = new Intent();
    
      		intent2.setAction("cn.itcast.mobilesafexian02.home"); //HomeActivity清单文件中的action
    
      		intent2.addCategory("android.intent.category.DEFAULT");//HomeActivity清单文件中的category
    
      		
    
      		// 设置快捷方式要执行的操作
    
      		intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent2);
    
      		// 发送广播
    
      		sendBroadcast(intent);
    
      		
    
      		//更改保存的值
    
      		Editor edit = sp.edit();
    
      		edit.putBoolean("firstEnter", false);
    
      		edit.commit();
    
      	}
    
      }
    

    }

    那再运行一下,先把桌面的“手机卫士西安02”快键方式删除掉,当程序运行起来后,在桌面上又生成一个“手机卫士西安02”的快键方式

    点击它,直接跳转到了手机卫士的HomeActivity

    表明这个快键方式创建成功了,但是注意,当每次进去手机卫士西安02时,它都会在桌面上创建一个快键方式

    这是因为快键方式是可以创建多个的,比如将示例工程“快键方式”运行起来,然后多次点击“添加快键方式”按钮

    在桌面就可以添加上多个快键方式“拨打电话110”,而且随便点击哪个都是可以拨打电话给110的

    一般都是创建一个快键方式就可以了,所以在shortCut方法中,添加一个if判断,当sp.getBoolean(“firstEnter”, true))中没有firstEnter这个值,就来个true,true就要去创建一个快键方式,那创建完了之后,就要更改保存的firstEnter的值了,通过edit.putBoolean(“firstEnter”, false)去保存这个值为false,最后commit

    运行程序,先将桌面的“手机卫士西安2号”删除掉,程序运行起来后,在桌面创建了一个手机卫士西安2号的快键方式,那再进去手机卫士西安2号程序,然后再退出,桌面上还只有一个“手机卫士西安2号”的快键方式

    以上就是创建快键方式的操作

10.11 监听用户打开的应用程序# (重点)

腾讯管家中有一个隐私保护,它里边有一个软件锁的功能,点击软件锁会跳转到一个“请输入隐私保护密码”的页面,输入123

他会跳转到一个“隐私保护,未加锁,已加锁”的界面,这个和手机卫士的软件管理界面基本相似,只不过给listView的每个条目增加了一个锁图片和文字“加锁”,那点击“加锁”,这个对应的软件就消失了,切换到已加锁页面,它里边就是已加锁的软件





APIDemos加锁了,打开APIDemos,会出来一个“请输入隐私保护密码”的界面,下边是输入密码的框,右边是确定按钮

输入密码为123,然后点击确定才能打开这个APIDemos



接下来实现下这样的操作,给软件加上这样一个锁,然后再打开对应软件时,去输入一个解锁密码,输入正确才能进入软件,

一打开对应加锁过的应用就会弹出输入密码界面



其实就是来监听下我们打开的应用程序,如果是加过锁的,就显示输入密码界面,不是就不显示

像这种时时刻刻监听某些操作的行为,有一个专业名词叫做 “watch dog” 中文名“看门狗”



时时刻刻与什么有关系,和服务有关,服务可以一直在后台运行



创建示例工程“看门狗”,因为要时时刻刻监听某些行为,所以要创建一个服务WatchDogService	

然后紧接着到清单文件中去注册下这个WatchDogService,即:

	

<service android:name="cn.itcast.mobilesafexian02.service.WatchDogService" >

    </service>



在WatchDogService里边先重写onCreate方法,在onCreate方法中就可以实现时时刻刻监听用户打开的程序了



前边在讲的时候,说时时刻刻是不是用的timer,注意这里换另外一种方式

	new一个子线程,在run方法中来一个while(true)循环

这种方式也可以实现时时刻刻的效果,注意,在run方法中要来一个while(true)

在这个while(true)里边就可以去执行时时刻刻监听用户打开哪个应用程序的操作了

先在Thread外边获取一个进程的管理者ActivityManager

进程的管理者am里边有一个getRunningTasks(maxNum),先给ActivityManager去final一下,不然在while中用am时,会报错



这个getRunningTasks中的Task就是一个任务栈



	任务栈是用来管理activity的,一般一个应用会有一个任务栈,打开的所有activity都存放在任务栈中



	比如打开APIDemos应用程序,就相当于创建了一个任务栈,在输入密码界面(这个界面是属于手机卫士的)输入密码123

	然后点击确定就跳转到了APIDemos的主界面,那相当于在APIDemos的任务栈中创建了一个activity



am.getRunningTasks(maxNum)就相当于获取正在运行的任务栈,参数maxNum就是获取前几个正在运行的任务栈,将参数写成3

就代表获取前3个正在运行的任务栈,写成1就是获取当前正在运行的任务栈,它会返回一个List<RunningTaskInfo>,我们把它叫做runningTasks

然后接下来增强for循环一下,在runningTaskInfo中有一个baseActivity,这一步叫做获取栈底的activity,返回一个ComponentName,我们把它叫做baseactivity

既然可以获取栈底的activity,那相应的也可以获取栈顶的activity

这里只需要获取栈底的activity,所以将获取到的栈顶的activity注释掉



	什么是栈底activity,什么是栈顶activity?



	画一个图,长方形是一个任务栈,首先打开的activity在栈底,再打开的activity在上边依次堆加上去

	当前看到的这个activity在最上边,最上边就是栈顶activity,最下边就是栈底activity

	不管是栈底的还是栈顶的,随便获取一个,在它里边都有getPackageName()方法,返回一个String类型的包名,把它叫做packagename,这样就获取到应用程序的包名了,先输出一下这个应用程序的包名



注意这个服务WatchDogService还没有开启,来到示例工程“看门狗”的MainActivity中的onCreate方法中,先new一个intent

然后通过startService来开启服务WatchDogService



即:

	public class MainActivity extends Activity {



		@Override

		protected void onCreate(Bundle savedInstanceState){

				super.onCreate(savedInstanceState);

				setContentView(R.layout.activity_main);

				

				Intent intent = new Intent(this,WatchDogService.class);

				startService(intent);			

		}

	}



像这种am.getRunningTasks(1)来获取正在运行的任务栈的操作,需要加权限

在示例工程“看门狗”的清单文件中,添加获取任务栈GET_TASKS的权限:

			android.permission.GET_TASKS 



运行程序,发现在Log中打印出特别多的packageName包名,即:



	System.out  com.example.watchdog

	System.out  com.example.watchdog

	System.out  com.example.watchdog

	......



可以让new出来的线程多睡一会儿,睡个500毫秒



运行程序,WatchDogService每隔500毫秒会去打印一条watchdog应用程序的包名,即:



	com.example.watchdog

	com.example.watchdog

	com.example.watchdog

	......



现在退出watchdog应用程序到桌面,它又开始打印桌面这个应用程序的包名了,桌面这个应用程序就叫launcher,即:

	

	com.example.watchdog

	com.example.watchdog

	com.android.launcher

	......



再打开手机卫士,它又开始打印mobilesafexian02了,即:

	com.example.watchdog

	com.android.launcher

	com.itcast.mobilesafexian02

	......



那既然你获取的永远都是我当前打开的应用程序的包名,那来个if先简单的判断一下,如果packagename.quals("")

这个双引号中的包名先来一个比较熟悉点的系统应用短信的

那打开DDMS,查看到右边模拟器中显示的短信的包名是"com.android.mms"

(如果没有显示,那可以先在模拟器的桌面打开短信应用程序,再回到DDMS中就可以查看到了)



如果当前打开的这个应用程序是短信就弹出一个输入密码界面,来个Intent

参数1写成WatchDogService.this,参数2写成MainActivity.class,然后通过StartActivity(intent)去跳转了



然后在activity_main.xml中修改布局,把以前的布局删掉,给它来个密码输入框Password控件,然后再来一个button按钮



运行示例工程“看门狗”,弹出了输入密码界面,这个有了之后现在关掉,然后再打开短信应用时,应该弹出输入密码对话框

但是我们打开短信应用时提示“Unfortunately,看门狗has stopped.”,意思是看门狗出错了已经停止运行了

看下Log中打印出来的原因,它说:

	“Activity context require the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?”



意思是少了一个FLAG_ACTIVITY_NEW_TASK,也就是需要给要跳转到的activity指明一个任务栈



以前通过intent跳转activity时不用写标签FLAG_ACTIVITY_NEW_TASK是因为在activity中跳转到activity中的

现在在服务WatchDogService中跳转到activity时必须给要跳转到的activity指明一个任务栈,这样才能跳转到activity



在activity中跳转到activity中时,还在这个任务栈中进行跳转,跳转activity时就已经有那个任务栈了



在服务中进行activity的跳转,服务不在任务栈当中,前边已经说了,任务栈是用来管理activity的,服务和广播都是没有在任务栈当中的,所以在服务中跳转activity时,必须给要跳转的activity指明一个任务栈才能去跳转



在intent中有setFlags(flags)这样一个方法,在参数处来个Intent.Flag_ACTIVITY_NEW_TASK,这个就是给要跳转到的activity指明一个任务栈



运行程序,打开短信就不会奔溃了,直接打开看门狗输入密码界面了,发现打开短信之后,过一会儿才会出来输入密码界面,有点慢

这是因为让子线程Thread睡了3000毫秒,改成500毫秒



再运行程序,打开短信,输入密码界面就出来的快了好多



现在可以弹出输入密码界面了,到这,(时时刻刻)监听用户打开的程序就讲完了



问题:在WatchDogService服务中while(true)去进行了监听,那万一服务WatchDogService关闭了怎么办

这里是开了一个子线程,这个服务WatchDogService是在主线程中,在主线程开了一个子线程

那服务关闭时,这个子线程是不会退出的,那就相当于它还在一直while(true)去进行监听



在WatchDogService中实现onDestroy()方法

在WatchDogService的成员变量处设置一个标识isTasks,在这里不设置默认值为true

进入onCreate方法,也就是服务开启时去设置默认值为true

然后将写死的while(true)改为while(isTasks),它的意思是如果是true就去开启监听,如果是false就不去开启监听

当服务WatchDogService退出时,到onDestroy方法中将isTasks改为false

那这样当服务退出时,再去子线程Thread中走时,while(isTasks)就变成false了,就不会再去再去开启监听了



即:



public class WatchDogService extends Service {



	private boolean isTasks;



	@Override

	public IBinder onBind(Intent intent) {

		return null;

	}



	@Override

	public void onCreate(){

		super.onCreate();



		isTasks = true;



		//1.获取进程的管理者

		final ActivityManager am = (ActivityManager)getSytemService(ACTIVITY_SERVICE);



		//2.开启线程时时刻刻监听用户打开的程序

		new Thread(){

			public void run(){

				while(isTasks){

					//3.获取正在运行的任务栈

					List<RunningTaskInfo>  runningTasks = am.getRunningTasks(1);

					for(RunningTaskInfo runningTaskInfo : runningTasks){

						//4.获取栈底的activity

					ComponentName baseactivity = runningTaskInfo.baseActivity;

					//runningTaskInfo.topActivity;//获取栈顶的activity

					//5.获取应用程序的包名

					String packageName = baseactivity.getPackageName();

					//6.判断获取的包名是否是打开的应用程序的包名,是,就显示密码输入界面,不是,就不管了

					if(packageName.equals("com.android.mms")){

						Intent intent = new Intent(WatchDogService.this,MainActivity.class);

						//给跳转的activity指明一个任务栈

						intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//给要跳转到的activity指明一个任务栈

						

						startActivity(intent);

					}

					}

					SystemClock.sleep(500);

				}

			};

		}.start();

	}

	@Override

	public void onDestroy(){

		super.onDestroy();

		isTasks = false;

	}

}



到这里“时时刻刻监听用户打开的程序”就做完了



总结如下:





任务栈:管理activity,一个应用就有一个任务栈,打开所有的activity都存放在任务栈



在服务中跳转activity必须给要跳转的activity指明一个任务栈,这样才能跳转

10.12 数据库的操作#

把监听用户打开的应用程序的操作移植到手机卫士里边

在移植之前先看下,判断获取包名是否是打开的应用程序时直接写死了一个短信,即“com.android.mms”



实际开发中应该要动态的去获取,应用各式各样的都有,有时候来个几百个,你内存就4G,所以应该保存到一个数据库中



创建一个关于软件锁的数据库操作,和黑名单的操作BlackNumOpenHelper.java一模一样,不想写就直接拷贝修改



新建一个WatchDogOpenHelper.java,继承自SQLiteOpenHelper,在这里边首先创建出构造函数WatchDogOpenHelper



构造函数的参数这里只需要参数Context,其余的全都去掉,然后将super中的第二个参数,数据库名称写成“watchdog.db”

super中的第三个参数工厂写成null,第三个参数版本号写成1



接下来要在Oncreate方法中创建表结构,在WatchDogService.java中的“6.判断获取包名是否是打开的应用程序的包名处”

只是对这一个包名进行处理,这里其实数据库中只保存一个包名就足够了

所以接下来不写了,直接复制BlackNumOpenHelper.java中onCreate方法中的创建数据库代码

然后它需要一个数据库表名,那将DB_NAME也拷贝过来,还要改下sql语句,这里只需要一个(不需要mode)

然后将blacknum改为packagename,20不够来个50,50还不够来个100

一般应用的包名都不会超过50个字符,超过50个字符只能说明这个公司它的名称比较长	



那将    _id integer primary key autoincrement,blacknum varchar(20),mode varchar(2)

改为:   _id integer primary key autoincrement,packagename varchar(50)



即:



public class WatchDogOpenHelper extends  SQLiteOpenHelper{



	public static final String DB_NAME="info";



	public WatchDogOpenHelper(Context context){

		super(context,“watchdog.db”,null,1);

	}



	@Override

	public void onCreate(SQLiteDatabase db){

		db.execSQL("create table "+DB_NAME+"(_id integer primary key autoincrement,packagename varchar(50))");

	}



	@Override

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



	}

}





将WatchDogOpenHelper创建成功之后,应该去测试一下,看这个数据库创建成功了没

找到测试项目,在这里边再写一个单元测试类TestWatchDog.java,继承自AndroidTestCase,在这里边写一个方法testWatchdogopenhelper,在这个方法里边,要得到WatchDogOpenHelper它的一个对象,new出来一个

紧接着就可以用这个对象直接调用getReadableDatabase()



即:



	public class TestWatchDog extends AndroidTestCase{

	

	public void testWatchdogopenhelper{

		WatchDogOpenHelper watchDogOpenHelper = new WatchDogOpenHelper(getContext());

		watchDogOpenHelper.getReadableDatabase();

	}

}



选中testWatchdogopenhelper这个方法,运行成功后来到DDMS的data/data/cn.itcast.mobilesafexian02/databases下的watchdog.db,这个watchdog.db数据库就创建成功了



接下来要开始对数据库进行操作了,这个操作很简单,复制BlackNumDao.java,改为WatchDogDao.java,并对里边的代码进行修改



即:



WatchDogDao.java



	public class WatchDogDao {

	//面试:同一时刻对数据进行读操作也进行写操作,数据库报错了,这个问题该怎么解决

	

	private WatchDogOpenHelper watchDogOpenHelper;

	byte[] b = new byte[1024];

	private Context context;

	//首先获取BlackNumOpenHelper

	public WatchDogDao(Context context){

		this.context = context;

		watchDogOpenHelper = new WatchDogOpenHelper(context);

	}

	//增删改查

	/**
  • 添加包名
     */
     public void addLockApp(String packagename){
     // synchronized (b) {
     // }

      		//1.获取数据库
    

    // blacNunOpenHelper.getReadableDatabase();//也是可以进行写入操作的,不加锁,线程不安全的操作,效率比较高,读取数据库用,对数据库进行操作不会使用

      		SQLiteDatabase database = watchDogOpenHelper.getWritableDatabase();//加锁,线程安全的,效率比较低,一般用于对数据库进行操作使用
    
      		//跟map相似
    
      		ContentValues values = new ContentValues();
    
      		values.put("packagename", packagename);
    
      		//nullColumnHack : sqlit数据库是不允许字段出现null
    
      		//values : 要添加的数据
    
      		database.insert(WatchDogOpenHelper.DB_NAME, null, values);
    
      		
    
      		//告诉内容观察者,数据已经变化,可以去更新数据 
    
      		ContentResolver resolver = context.getContentResolver();
    
      		Uri uri = Uri.parse("content://cn.itcast.mobliesafexian02.unlock.change");
    
      		//通知内容观察者,数据已经发生变化了
    
      		resolver.notifyChange(uri, null);
    
      		
    
      		database.close();
    
      }
    
      /**
    
  • 查询应用是否加锁
     /
     public boolean queryLockApp(String packagename){
     //创建一个存放拦截模式变量
     boolean isLock = false;
     //1.获取数据库
     SQLiteDatabase database = watchDogOpenHelper.getReadableDatabase();
     //2.查询应用是否加锁
     //参数1:表名
     //参数2:查询的字段,两个 new String[]{“blacknum”,“mode”},数据库中的字段
     //参数3:查询条件 where …
     //参数4:查询条件的参数 where id= …
     //参数5:groupBy : 分组
     //参数6:having :去重
     //参数7:order by : 查询的方式,升序查询、降序查询
     //cursor:保存的就是查询字段的数据
     Cursor cursor = database.query(WatchDogOpenHelper.DB_NAME, null, “packagename=?”, new String[]{packagename}, null, null, null);
     //3.解析cursor获取数据,moveToNext():是否有数据
     if (cursor.moveToNext()) {
     isLock = true;
     }
     cursor.close();
     database.close();
     return isLock;
     }
     /
    *

  • 删除包名
     */
     public void deleteLockApp(String packagename){
     //1.获取数据库
     SQLiteDatabase database = watchDogOpenHelper.getWritableDatabase();
     //2.删除操作
     database.delete(WatchDogOpenHelper.DB_NAME, “packagename=?”, new String[]{packagename});

      	//告诉内容观察者,数据已经变化,可以去更新数据 
    
      	ContentResolver resolver = context.getContentResolver();
    
      	Uri uri = Uri.parse("content://cn.itcast.mobliesafexian02.unlock.change");
    
      	//通知内容观察者,数据已经发生变化了
    
      	resolver.notifyChange(uri, null);
    
      	
    
      	//3.关闭数据库
    
      	database.close();
    
      }
    
      /**
    
  • 查询全部
     */
     public List getAllLockApp(){
     List list = new ArrayList();
     //1.获取数据库
     SQLiteDatabase database = watchDogOpenHelper.getReadableDatabase();
     //2.查询数据库
     Cursor cursor = database.query(WatchDogOpenHelper.DB_NAME, new String[]{“packagename”}, null, null, null, null, null);//desc倒序查询
     //3.解析cursor获取数据
     while(cursor.moveToNext()){
     //4.获取数据
     String packageName = cursor.getString(0);
     list.add(packageName);
     }
     cursor.close();
     database.close();
     return list;
     }

    }

Day10 13.加锁解锁的操作#

腾讯管家隐私保护中的软件锁功能,输入密码123进入了一个未加锁,已加锁的界面,这个界面和软件管理的界面非常的相似



直接在软件管理这个界面上进行操作,来到SoftManagerActivity.java中,首先来改一下条目的布局文件,找到item_softmanager.xml,在右边布局处加一个小锁可以表示,之前在讲手机防盗模块时,重新进入设置向导的右边就有一个小锁

可以把这个小锁拿过来用一下了



即:



item_softmanager.xml



	<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent" >

    <ImageView 

        android:id="@+id/iv_itemsoftmanager_icon"

        android:layout_width="50dp"

        android:layout_height="50dp"

        android:src="@drawable/ic_launcher"

        />

    <!-- layout_toRightOf : 在某个控件的右边 -->

    <TextView 

        android:id="@+id/tv_itemsoftmanager_name"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="手机卫士"

        android:textSize="18sp"

        android:textColor="#000000"

        android:layout_toRightOf="@+id/iv_itemsoftmanager_icon"

        />

    <TextView 

        android:id="@+id/tv_itemsoftmanager_issd"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="手机内存"

        android:textSize="16sp"

        android:textColor="#880033"

        android:layout_toRightOf="@+id/iv_itemsoftmanager_icon"

        android:layout_below="@+id/tv_itemsoftmanager_name"

        />

    <!-- ems : 设置显示的长度 -->

    <TextView 

        android:id="@+id/tv_itemsoftmanager_versionname"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="3.1.3"

        android:layout_toRightOf="@+id/tv_itemsoftmanager_issd"

        android:layout_below="@+id/tv_itemsoftmanager_name"

        android:layout_marginLeft="5dp"

        android:textColor="#ff0033"

        android:layout_marginTop="2dp"

        android:singleLine="true"

        android:ellipsize="end"

        android:ems="3"

        />

    <ImageView 

        android:id="@+id/iv_itemsoftmanager_islock"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:src="@drawable/lock"

        android:layout_alignParentRight="true"

        android:layout_marginRight="10dp"

        />

    

</RelativeLayout>





布局文件中添加好小锁之后,还需要到SoftManagerActivity的ViewHolder中给它添加这个小锁的id为iv_itemsoftmanager_islock,即:





static class ViewHolder{

	ImageView iv_itemsoftmanager_icon,iv_itemsoftmanager_islock;

	TextView tv_itemsoftmanager_name,tv_itemsoftmanager_issd,tv_itemsoftmanager_versionname;

}



然后到getView方法中初始化小锁,即:



viewHolder.iv_itemsoftmanager_islock = (ImageView) view.findViewById(R.id.iv_itemsoftmanager_islock);	



然后去getView方法中的“设置版本号”代码的下边根据包名去显示这个应用是否是已经加锁的操作



在WacthDogDao.java中有个查询应用是否加锁的方法queryLockApp,它的参数String packagename,意思是要传一个应用的包名过去



在if判断中用watchDogDao.queryLockApp(appInfo.getPackageName())



此时发现watchDogDao报红,原因是在SoftManagerActivity页面中还没有watchDogDao这个对象

有些同学说在WatchDogDao.中给它添加static即可,但是我不敢用static,因为用static会出现一些不必要的麻烦

比如你在这里进行数据库操作时,在其他地方再操作,会出现一些重复数据库出现问题

所以这个要注意,当然你加上static也是没有问题的

还是在SoftManagerActivity的onCreate方法中new出来这个对象,并声明成成员变量方便下边在使用watchDogDao时不再会报红



即:





private WatchDogDao watchDogDao;



@Override

protected void onCreate(Bundle savedInstanceState) {

	super.onCreate(savedInstanceState);

	setContentView(R.layout.activity_softmanager);



	watchDogDao = new WatchDogDao(getApplicationContext());



	}





//根据包名去显示应用是否是已经加锁的操作

if (watchDogDao.queryLockApp(appInfo.getPackageName())) {

	//显示加锁状态

	viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.lock);

}else{

	//显示解锁状态

	viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.unlock);

}



运行程序,查看效果,进入软件管理模块,都是解锁的状态,因为还没有设置加锁的操作



那接下来要设置加锁的操作,一般是在点击软件管理中的某个应用的条目时要进行加锁的操作,但是现在点击之后已经显示了popwindow,



那接下来就要给大家使用另外一种方式:



	长按点击事件



在SoftManagerActivity.java的onCreate方法中调用listviewitemLongClick()方法,然后创建这个方法



找到listview的id为lv_softmanager_applications,给它设置长按点击事件setOnItemLongClickListener



它这个onItemLongClick方法的参数和onItemClick方法的参数一样,看下这个onItemLongClick的注释,返回true时表示被消费了,也就是执行了,false表示被拦截了,所以说要想被执行,就返回true	



在onItemLongClick这个方法中执行的操作和下边的onItemClick方法中执行的操作其实是一模一样的

首先要屏蔽掉用户程序和系统程序弹出的多少个的这个气泡的textview



紧接着还要去获取一下软件信息,因为要去获取包名,所以还是要去获取一下软件信息



接下来就可以执行加锁解锁的操作,如果watchDogDao.queryLockApp(appInfo.getPackageName),即如果查询出你这个app的包名如果是true表示原先是加锁的,那这个时候应该解锁,反之加锁,这个操作和以前自定义控件的操作一模一样



解锁的操作就是从数据库里边将包名给删除掉就可以了,即:



	watchDogDao.deleteLockApp(appinfo.getPackageName());

那这个解锁的操作完成之后



加锁的操作就是给数据库中添加一个对应的包名,即:

	watchDogDao.addLockApp(appInfo.getPackgeName());



自己不能给自己加锁,所以这里还要判断一下,如果appInfo.getPackageName(),不equals(getPackageName)就可以进行加锁的操作,反之不让你进行加锁操作



接下来要更新界面,原先更新界面时,可以拿着单个条目去刷新,这里也可以,但是这个比较特别一点,这里做了一个判断,那首先拿着view去getTag()一下,得到一个viewholder



接下来如果你是解锁操作,应该用解锁的图片,反之应该是加锁的图片



即:



/**
  • listview的条目长按点击事件
     */
     private void listviewitemLongClick() {
     lv_softmanager_applications.setOnItemLongClickListener(new OnItemLongClickListener() {

      	@Override
    
      	public boolean onItemLongClick(AdapterView<?> parent, View view,
    
      			int position, long id) {
    
      		//1.屏蔽用户程序多少个和系统程序多少个
    
      		if(position == 0 || position == userAppInfos.size()+1){
    
      			return true;
    
      		}
    
      		//2.获取软件信息
    
      		//获取条目对应的信息
    
      		//判断用户程序是否展示完
    
      		if(position <= userAppInfos.size()){
    
      			//获取用户程序
    
      			appInfo = userAppInfos.get(position-1);
    
      		}else{
    
      			//系统程序
    
      			appInfo = systemAppInfos.get(position - userAppInfos.size()-2);
    
      		}
    
    
    
      		ViewHolder viewHolder = (ViewHolder)view.getTag();
    
    
    
      		//加锁解锁的操作,原先加锁,再点击就是解锁,原先解锁,在点击就是加锁
    
      		if (watchDogDao.queryLockApp(appInfo.getPackageName())) {
    
      			//解锁
    
      			watchDogDao.deleteLockApp(appInfo.getPackageName());
    
      			//更新界面
    
      			viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.unlock);
    
      		}else{
    
      			//加锁
    
      			//自己不能给自己加锁
    
      			if (!appInfo.getPackageName().equals(getPackageName())) {
    
      				watchDogDao.addLockApp(appInfo.getPackageName());
    
      				//更新界面
    
      				viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.lock);
    
      			}
    
      		}
    
    
    
      			return true;
    
      		}
    
      });
    

    }

    这个就是更新界面的操作,运行程序,长按软件管理中的某个条目就加锁了

    将DDMS中data/data/cn.itcast.mobilesafexian02下的watchdog.db导出到桌面

    用sqliteExpert工具打开,看到info表中有一条数据: cn.itcast.mobilesafexian02.test

    说明加锁的操作也完成了

    加锁解锁的操作总结如下:

    1.在getview的item的布局文件中增加小锁的图标

    2.在getview中根据数据库中保存的是否有条目的包名来显示相应的图标

      	//根据包名去显示应用是否是已经加锁的操作
    
      	if (watchDogDao.queryLockApp(appInfo.getPackageName())) {
    
      		//显示加锁状态
    
      		viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.lock);
    
      	}else{
    
      		//显示解锁状态
    
      		viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.unlock);
    
      	}
    

    3.给listview增加长按点击事件

      	/**
    
  • listview的条目长按点击事件
     */
     private void listviewitemLongClick() {
     lv_softmanager_applications.setOnItemLongClickListener(new OnItemLongClickListener() {

      			@Override
    
      			public boolean onItemLongClick(AdapterView<?> parent, View view,
    
      					int position, long id) {
    
      				//1.屏蔽用户程序多少个和系统程序多少个
    
      				if (position == 0 || position == userAppInfos.size()+1) {
    
      					return true;
    
      				}
    
      				//2.获取软件信息
    
      				//获取条目对应的信息
    
      				//判断用户程序是否展示完
    
      				if (position <= userAppInfos.size()) {
    
      					//获取用户程序
    
      					appInfo = userAppInfos.get(position-1);
    
      				}else{
    
      					//系统程序
    
      					appInfo = systemAppInfos.get(position - userAppInfos.size()-2);
    
      				}
    
      				ViewHolder viewHolder = (ViewHolder) view.getTag();
    
      				//加锁解锁的操作,原先加锁,再点击就是解锁,原先解锁,在点击就是加锁
    
      				if (watchDogDao.queryLockApp(appInfo.getPackageName())) {
    
      					//解锁
    
      					watchDogDao.deleteLockApp(appInfo.getPackageName());
    
      					//更新界面
    
      					viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.unlock);
    
      				}else{
    
      					//加锁
    
      					//自己不能给自己加锁
    
      					if (!appInfo.getPackageName().equals(getPackageName())) {
    
      						watchDogDao.addLockApp(appInfo.getPackageName());
    
      						//更新界面
    
      						viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.lock);
    
      					}
    
      				}
    
      				//true if the callback consumed the long click, false otherwise
    
      				//true:被消费了,执行了,false:表示被拦截了
    
      				return true;
    
      			}
    
      		});
    
      	}
    

Day10 14.将监听用户打开程序的操作移植到手机卫士中 #

将示例程序“看门狗”中的WatchDogService.java服务拷贝,粘贴到mobilesafexian02的service包下,然后看哪里报错就改一下



	即:

	

	WatchDogService.java



	public class WatchDogService extends Service {

		private boolean isTasks;

		private WatchDogDao watchDogDao;

		private UnlockReceiver unlockReceiver;

		private String unlockPackageName;

		private ScreenOffReceiver screenOffReceiver;

		private List<String> lockApps;

		@Override

		public IBinder onBind(Intent intent) {

			return null;

		}

		private class UnlockReceiver extends BroadcastReceiver{

	

			@Override

			public void onReceive(Context context, Intent intent) {

				//获取传递过来的包名

				unlockPackageName = intent.getStringExtra("packagename");

			}

		}

		public class ScreenOffReceiver extends BroadcastReceiver{

	

			@Override

			public void onReceive(Context context, Intent intent) {

				unlockPackageName = null;

			}

		}

		@Override

		public void onCreate() {

			super.onCreate();

			watchDogDao = new WatchDogDao(getApplicationContext());

			

			//注册广播接受者

			unlockReceiver = new UnlockReceiver();

			//设置过滤条件

			IntentFilter intentFilter = new IntentFilter();

			//设置接受的广播事件

			intentFilter.addAction("content://cn.itcast.mobliesafexian02.unlock");

			registerReceiver(unlockReceiver, intentFilter);

			

			

			//注册锁屏的广播接受者

			screenOffReceiver = new ScreenOffReceiver();

			//设置过滤条件

			IntentFilter screenIntentFilter = new IntentFilter();

			screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);

			registerReceiver(screenOffReceiver, screenIntentFilter);

			

			

			isTasks = true;

			//1.进程的管理者

			final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);

			//2.开启线程时时刻刻监听用户打开的应用程序

			new Thread(){

				public void run() {

					//加锁或者解锁的时候要更新集合,内容观察者,查看到数据变化的时候会调用onChange方法

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

					//更新操作

					getContentResolver().registerContentObserver(

							uri, true, new ContentObserver(null) {

								public void onChange(boolean selfChange) {

									//更新数据

									lockApps = watchDogDao.getAllLockApp();

								};

						});

					//将数据库中的所有数据存放到集合中,也就是存放到内存中

					lockApps = watchDogDao.getAllLockApp();

					while(isTasks){

						//3.获取正在的运行任务栈

						//maxNum : 获取前几个正在运行的任务栈

						List<RunningTaskInfo> runningTasks = am.getRunningTasks(1);

						for (RunningTaskInfo runningTaskInfo : runningTasks) {

							//4.获取栈底的activity

							ComponentName baseactivity = runningTaskInfo.baseActivity;

	//						runningTaskInfo.topActivity;//获取栈顶的activity

							//5.获取应用程序的包名

							String packageName = baseactivity.getPackageName();

							//查看list集合中是否包含包名

							boolean islock = lockApps.contains(packageName);

							//6.判断包名是否在数据库中

							if (islock) {

								if (!packageName.equals(unlockPackageName)) {

									//跳转到密码输入界面

									Intent intent = new Intent(WatchDogService.this,WatchDogActivity.class);

									//给跳转到的activity指定一个任务栈

									intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

									//将包名传递给密码输入界面,用于显示图标和名称

									intent.putExtra("packagename", packageName);

									startActivity(intent);

								}

							}

							System.out.println(packageName);

						}

						SystemClock.sleep(500);

					}

				};

			}.start();

		}

		@Override

		public void onDestroy() {

			super.onDestroy();

			isTasks = false;

			if (unlockReceiver != null) {

				unregisterReceiver(unlockReceiver);

				unlockReceiver = null;

			}

			if (screenOffReceiver != null) {

				unregisterReceiver(screenOffReceiver);

				screenOffReceiver=null;

			}

		}

	}

	

到清单文件中注册这个服务:



	 <service android:name="cn.itcast.mobilesafexian02.service.WatchDogService" >

    </service>



WatchDogService有了之后,不要忘了还有一个权限也要添加下:



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



接着来到WatchDogService.java中,在这里边要去监听用户打开的应用程序,获取了一个运行的任务栈getRunningTasks(1)

然后获取了下栈底的activity,然后通过activity拿到了包名,紧接着在这里判断了一下包名是不是打开的应用程序

紧接着判断包名是否在数据库中



那这个比较简单了,首先要创建一个watchDogDao对象,并申明成成员变量,紧接着再判断包名是否在数据库中时,就可以查询下包名

如果是ture就表明要跳转到密码输入界面WatchDogActivity



即:





private WatchDogDao watchDogDao;



@Override

public void onCreate() {

	super.onCreate();



	watchDogDao = new WatchDogDao(getApplicationContext());

	

	//注册广播接受者

	unlockReceiver = new UnlockReceiver();

	//设置过滤条件

	IntentFilter intentFilter = new IntentFilter();

	//设置接受的广播事件

	intentFilter.addAction("content://cn.itcast.mobliesafexian02.unlock");

	registerReceiver(unlockReceiver, intentFilter);

	

	

	//注册锁屏的广播接受者

	screenOffReceiver = new ScreenOffReceiver();

	//设置过滤条件

	IntentFilter screenIntentFilter = new IntentFilter();

	screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);

	registerReceiver(screenOffReceiver, screenIntentFilter);

	

	

	isTasks = true;

	//1.进程的管理者

	final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);

	//2.开启线程时时刻刻监听用户打开的应用程序

	new Thread(){

		public void run() {

			//加锁或者解锁的时候要更新集合,内容观察者,查看到数据变化的时候会调用onChange方法

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

			//更新操作

			getContentResolver().registerContentObserver(

					uri, true, new ContentObserver(null) {

						public void onChange(boolean selfChange) {

							//更新数据

							lockApps = watchDogDao.getAllLockApp();

						};

				});

			//将数据库中的所有数据存放到集合中,也就是存放到内存中

			lockApps = watchDogDao.getAllLockApp();

			while(isTasks){

				//3.获取正在的运行任务栈

				//maxNum : 获取前几个正在运行的任务栈

				List<RunningTaskInfo> runningTasks = am.getRunningTasks(1);

				for (RunningTaskInfo runningTaskInfo : runningTasks) {

					//4.获取栈底的activity

					ComponentName baseactivity = runningTaskInfo.baseActivity;

// runningTaskInfo.topActivity;//获取栈顶的activity

					//5.获取应用程序的包名

					String packageName = baseactivity.getPackageName();

					//查看list集合中是否包含包名

					boolean islock = lockApps.contains(packageName);





					//6.判断包名是否在数据库中

					if (islock) {

						if (!packageName.equals(unlockPackageName)) {

							//跳转到密码输入界面

							Intent intent = new Intent(WatchDogService.this,WatchDogActivity.class);

							//给跳转到的activity指定一个任务栈

							intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

							//将包名传递给密码输入界面,用于显示图标和名称

							intent.putExtra("packagename", packageName);

							startActivity(intent);

						}

					}





					System.out.println(packageName);

				}

				SystemClock.sleep(500);

			}

		};

	}.start();

}





那上边需要“密码输入界面WatchDogActivity”,把这个界面创建出来,紧接着到清单文件中注册一下,紧接着在WatchDogActivity中重写它的onCreate方法,通过setContentView加载它的布局文件activity_watchdog.xml,即:



public class WatchDogActivity extends Activity {

	

	@Override

	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_watchdog);

		

	}

}

	

紧接着创建这个布局文件 activity_watchdog.xml



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



    <ImageView 

        android:id="@+id/iv_watchdog_icon"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:src="@drawable/ic_launcher"

        android:layout_gravity="center_horizontal"

        />

    <TextView 

        android:id="@+id/tv_watchdog_name"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="手机卫士"

        android:layout_gravity="center_horizontal"

        />

    <EditText

        android:id="@+id/ed_watchdog_password"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:ems="10"

        android:inputType="textPassword" >



        <requestFocus />

    </EditText>



    <Button

        android:id="@+id/button1"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="解锁" 

        android:onClick="unlock"

        />



</LinearLayout>



这个WatchDogActivity有了之后,就可以到WatchDogService.java中进行跳转了:



	//6.判断包名是否在数据库中

	if (islock) {

		if (!packageName.equals(unlockPackageName)) {

			//跳转到密码输入界面

			Intent intent = new Intent(WatchDogService.this,WatchDogActivity.class);

			//给跳转到的activity指定一个任务栈

			intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

			//将包名传递给密码输入界面,用于显示图标和名称

			intent.putExtra("packagename", packageName);

			startActivity(intent);

		}

	}



这个服务我们已经做完了,但是注意,服务还有一个开启

这个软件锁开启时,也可以让用户自己去选择是开启还是关闭,用户开启那就监听这个WatchDogService,如果关闭那就不管了



到activity_setting.xml布局中,直接模仿黑名单操作,添加如下布局:



	<cn.itcast.mobilesafexian02.ui.SettingView

        android:id="@+id/sv_setting_watchdog"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        itcast:des_off="关闭软件锁"

        itcast:des_on="开启软件锁"

        itcast:title="软件锁" >

    </cn.itcast.mobilesafexian02.ui.SettingView>



到SettingActivity的onCreate方法中,初始化这个sv_setting_watchdog:



	private SettingView sv_setting_watchdog;



	@Override

	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_setting);



		sv_setting_watchdog = (SettingView) findViewById(R.id.sv_setting_watchdog);

	

	}



它和黑名单的操作一模一样,到SettingActivity的onStart方法中,调用watchdog()方法:



		// 界面可见的调用

		@Override

		protected void onStart() {

			super.onStart();

			address();

			blacknum();



			watchdog();

		}



然后创建这个watchdog方法,并将黑名单blacknum方法中的代码拷贝过来,修改修改:



	/**
  • 软件锁
     */
     private void watchdog() {
     // 动态的获取服务是否开启
     if (AddressUtils.isRunningService(
     “cn.itcast.mobilesafexian02.service.WatchDogService”, this)) {
     sv_setting_watchdog.setChecked(true);
     } else {
     sv_setting_watchdog.setChecked(false);
     }
     sv_setting_watchdog.setOnClickListener(new OnClickListener() {

      		@Override
    
      		public void onClick(View v) {
    
      			Intent intent = new Intent(SettingActivity.this,
    
      					WatchDogService.class);
    
      			// isChecked()获取的是原先checkbox,原先是true表明原先服务是打开的,那再次点击就是表示我们要执行的是关闭的服务的操作
    
      			if (sv_setting_watchdog.isChecked()) {
    
      				// 关闭服务
    
      				stopService(intent);
    
      				// 给checkbox设置新的状态
    
      				sv_setting_watchdog.setChecked(false);
    
      			} else {
    
      				// 打开服务
    
      				startService(intent);
    
      				sv_setting_watchdog.setChecked(true);
    
      			}
    
      		}
    
      	});
    
      }
    

    运行程序,手机卫士的设置中心界面有个软件锁条目,点击之后开启了软件锁,那退出手机卫士

    来到模拟器的settings/apps/Running/手机卫士西安2号中,看下服务是否开启了,看到有这个服务WatchDogService

    还有一个WidgetsService服务开启着,加上手机卫士西安2号这个进程,那就是一个进程,2个服务

    进入手机卫士西安2号的软件管理界面,找到APIDemos应用,长按给它加上软件锁,再找到小火箭应用,长按也给它加一个软件锁

    退出手机卫士西安2号,找到APIDemos这个应用,点击之后,不会直接进入这个应用界面,而是弹出一个解锁的界面,输入密码点击解锁就可以进入应用了,说明软件锁已经可以监听到用户打开的应用程序了

    问题:

    在输入密码解锁的界面,点击返回键退出,又进入输入密码解锁界面了,不管你怎么点击返回键,它一直会进入密码解锁界面

    这个就是前边所说的bug,用另外一个模拟器看下腾讯管家是怎么干的,打开APIDemos应用,点击返回键直接退出到桌面上了

    前边给大家说过,android中每个界面跳转都有log,打开logcat查看下,它打开了一个APIDemos,紧接着又打开了一个腾讯的东西

    在输入密码解锁界面中,点击返回键,打印了一段日志,即:

      Starting:Intent
    
      {
    
      act=android.intent.action.MAIN
    
      cat=[android.intent.category.HOME]
    
      cmp=com.android.launcher、com.android.launcher2.launcher
    
      } from pid 207
    

    act就是action,看到它就是一个MAIN,cat就是category,也就是他来了一个action,来了一个category,就跳转到了主界面去了

    既然是在输入密码界面点击了返回键,那就要到WatchDogActivity.java中重写它的onKeyDown方法

    判断如果keyCode恒等于KeyEvent.KEYCODE_BACK,就要跳转到手机主界面

    即:

    @Override

    public boolean onKeyDown(int keyCode, KeyEvent event) {

      if (keyCode == KeyEvent.KEYCODE_BACK) {
    
      	//跳转到主界面
    
      	/**
    
  • act=android.intent.action.MAIN action
     cat=[android.intent.category.HOME] category
     cmp=com.android.launcher/com.android.launcher2.Launcher 
     */
     Intent intent = new Intent();
     intent.setAction(“android.intent.action.MAIN”);
     intent.addCategory(“android.intent.category.HOME”);
     startActivity(intent);
     }
     return super.onKeyDown(keyCode, event);
     }

    运行程序,在输入密码界面中点击返回键,就直接退出到手机桌面了

    这块比较繁琐,监听用户打开程序移植到手机卫士总结如下:

    1.将demo看门狗中watchdogservice操作移植到手机卫士中

    2.修改判断用户打开的应用程序是否应该显示输入密码界面的操作

     //6.判断包名是否在数据库中
    
     if (watchDogDao.queryLockApp(packageName)) {
    
     		//跳转到密码输入界面
    
     		Intent intent = new Intent(WatchDogService.this,WatchDogActivity.class);
    
     		//给跳转到的activity指定一个任务栈
    
     		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    
     		startActivity(intent);
    
     }
    

    3.在设置中心模仿(关闭/打开)黑名单拦截操作,添加一个(关闭/打开)软件锁条目

    4.解决进入输入密码界面,点击返回键(之后应该跳转到手机桌面),又进入输入密码界面的bug

     解决:在输入密码界面重写onkeydown方法
    
    
    
     	@Override
    
     	public boolean onKeyDown(int keyCode, KeyEvent event) {
    
    
    
     		if (keyCode == KeyEvent.KEYCODE_BACK) {
    
    
    
     			//跳转到主界面
    
     			/**
    
  • act=android.intent.action.MAIN action
     cat=[android.intent.category.HOME] category
     cmp=com.android.launcher/com.android.launcher2.Launcher 
     */
     Intent intent = new Intent();
     intent.setAction(“android.intent.action.MAIN”);
     intent.addCategory(“android.intent.category.HOME”);
     startActivity(intent);
     }
     return super.onKeyDown(keyCode, event);

     	}
    

10.15 修改输入密码界面 #

输入密码界面弄的很粗糙,就简单的来了一个输入密码框和解锁的按钮,比较难看来改一下



点开加锁的应用后,进入输入密码界面,就让显示你这个应用的图标和名称,让用户知道他现在进入的是哪一个应用



到activity_watchdog.xml布局文件给界面增加图片和名称



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

	

	    <ImageView 

	        android:id="@+id/iv_watchdog_icon"

	        android:layout_width="wrap_content"

	        android:layout_height="wrap_content"

	        android:src="@drawable/ic_launcher"

	        android:layout_gravity="center_horizontal"

	        />

	    <TextView 

	        android:id="@+id/tv_watchdog_name"

	        android:layout_width="wrap_content"

	        android:layout_height="wrap_content"

	        android:text="手机卫士"

	        android:layout_gravity="center_horizontal"

	        />

	    <EditText

	        android:id="@+id/ed_watchdog_password"

	        android:layout_width="match_parent"

	        android:layout_height="wrap_content"

	        android:ems="10"

	        android:inputType="textPassword" >

	

	        <requestFocus />

	    </EditText>

	

	    <Button

	        android:id="@+id/button1"

	        android:layout_width="wrap_content"

	        android:layout_height="wrap_content"

	        android:text="解锁" 

	        android:onClick="unlock"

	        />

	

	</LinearLayout>	



到WatchDogActivity中的onCreate中初始化这两个id,并将它们声明成成员变量



	private ImageView iv_watchdog_icon;

	private TextView tv_watchdog_name;



	@Override

	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_watchdog);



		iv_watchdog_icon = (ImageView) findViewById(R.id.iv_watchdog_icon);

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

		

	}



接下来就要动态显示打开的应用的图标和名称,应用的图标和名称在清单文件的application中有icon和label

那需要一个packageManager去获取application的信息



在WatchDogService中跳转到WatchDogActivity界面中时,将包名传递给WatchDogActivity密码输入界面用于显示图标和名称



	即:



		WatchDogService.java

	

		//6.判断包名是否在数据库中

		if (islock) {

			if (!packageName.equals(unlockPackageName)) {

				//跳转到密码输入界面

				Intent intent = new Intent(WatchDogService.this,WatchDogActivity.class);

				//给跳转到的activity指定一个任务栈

				intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

	

				//将包名传递给密码输入界面,用于显示图标和名称

				intent.putExtra("packagename", packageName);

				

				startActivity(intent);

			}

		}



在WatchDogService中传递之后,在WatchDogActivity中的onCreate方法中可以通过getIntent接收

得到一个intent之后,通过intent去getStringExtra(name)



这里可以根据名称去获取值,那这个名称name就是packagename



接下来根据包名获取图标和名称,那通过getPacakgeManger可以得到一个PackageManger,即:

	PackageManager pm = getPackageManager();



接下来通过这个pm去getApplicationinfo(packageName,flags)

那参数packageName就是上边接收到的包名packagename,参数flags表示需要获取哪些信息,写成0表示要获取所有信息,会得到一个ApplicationInfo



在applicationInfo中有个loadIcon(pm),需要传入一个pm参数,返回一个Drawable类型的icon

此时pm.getApplicationInfo(pacakgename,0)会报异常,try catch捕获下异常



获取完图标之后,在applicationInfo中还有一个loadLabel(pm),返回一个接口CharSequence,toString()一下,就可以得到一个String类型的name



然后紧接着就可以给控件设置数据了,那首先设置图标,然后在设置下名称



	即:



	private ImageView iv_watchdog_icon;

	private TextView tv_watchdog_name;



	@Override

	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_watchdog);



		iv_watchdog_icon = (ImageView) findViewById(R.id.iv_watchdog_icon);

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

		

		//显示打开应用的图标和名称

		Intent intent = getIntent();

		//获取传递过来的包名

		String packagename = intent.getStringExtra("pacakgename");

		//根据包名获取图标和名称

		PackageManager pm = getPacakgeManager();

		

		try{

			//根据包名得到ApplicationInfo的信息

			ApplicationInfo applicationInfo = pm.getApplicationInfo(pacakgename,0);

			//获取应用图标

			Drawable icon = applicationInfo.loadIcon(pm);

			//获取应用名称

			String name = applicationInfo.loadLabel(pm).toString();



			//设置显示数据

			iv_watchdog_icon.setImageDrawable(icon);

			tv_watchdog_name.setText(name);



		} catch (NameNotFoundException e){

			e.printStackTrace();

		}

	}



这样就获取并将应用的图标和名称显示在了界面中



运行手机卫士西安02并将设置中心中的软件锁打开,然后退出到手机桌面,重新打开APIDemos,输入密码界面中就有APIDemos这个图标和文字了



修改输入密码界面总结如下:





1.修改布局文件activity_watchdog.xml,增加图标和名称



2.在WatchdogService中跳转界面时,将包名传递过去



	//6.判断包名是否在数据库中

	if (watchDogDao.queryLockApp(packageName)) {

			//跳转到密码输入界面

			Intent intent = new Intent(WatchDogService.this,WatchDogActivity.class);

			//给跳转到的activity指定一个任务栈

			intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

			//将包名传递给密码输入界面,用于显示图标和名称

			intent.putExtra("packagename", packageName);

			startActivity(intent);

	}



3.在watchDogActivity中根据包名获取打开应用程序的图标和名称,在oncreate方法执行

	

	//显示打开应用的图标和名称

	Intent intent = getIntent();

	//1.获取传递过来的包名

	String packagename = intent.getStringExtra("packagename");

	//2.根据包名获取图标和名称

	PackageManager pm = getPackageManager();

	try {

		//3.根据包名得到ApplicationInfo的信息

		ApplicationInfo applicationInfo = pm.getApplicationInfo(packagename, 0);

		//4.获取图标

		Drawable icon = applicationInfo.loadIcon(pm);

		//5.获取名称

		String name = applicationInfo.loadLabel(pm).toString();

		//6.设置显示数据

		iv_watchdog_icon.setImageDrawable(icon);

		tv_watchdog_name.setText(name);

	} catch (NameNotFoundException e) {

		// TODO Auto-generated catch block

		e.printStackTrace();

	}

Day16 总结##

  • 15
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值