手机卫士-10
课1
窗口小部件widget
widget是什么? 阅读文档doc develop———>API Guides————>App Widgets linux命令ps:查看进程 kill+进程pid:杀掉进程 窗口小部件widget使用方法
创建widget测试 依照文档实现窗口小部件widget
创建widget窗口小部件的步骤
1、创建一个新的实现类:父类是AppWidgetProvider
//1.写一个类继承 AppWidgetProvider
//因为这个类是一个广播接收者 所以在清单文件中配置
public class MyWidget 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("ondelete");
}
@Override
public void onEnabled(Context context) {
super.onEnabled(context);
System.out.println("onenable");
}
@Override
public void onDisabled(Context context) {
System.out.println("onDisabled");
super.onDisabled(context);
}
}
2、看其源码,发现其实是一个特殊的广播接受者
3、所以需要在清单文件添加receiver节点
<receiver android:name="com.itheima.widget.MyWidget" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGETUPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/exampleappwidget_info" />4、但是意图过滤器是什么?
看文档
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
5、指定widget的元数据:在工程里创建一个新的文件夹XML--->有一个资源(根据meta-data节点知道的)exampleappwidgetinfo.xml,在该xml文件里绑定要显示的布局文件 android:initialLayout="@layout/example_appwidget"(小部件布局文件)
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />
xml-->exampleappwidgetinfo.xml
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/test"
android:minHeight="72dp"
android:minWidth="294dp"
android:updatePeriodMillis="1800000" >
</appwidget-provider>
layout-->test.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Large Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
<Button
android:id="@+id/button1"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
以上这些步骤完成后,widget小部件也就完成了
需求,给手机卫士创建一个窗口小部件widget
模仿金山的窗口小部件
解密别人的布局文件,逆向助手。反编译别人的apk,可以看到别人的xml 怎么寻找别人的widget,我们观察别人的清单文件的广播接受者所特定的意图过滤器,同时也就可以发现他的widget布局文件是什么名字了。我们就可以观察别人的widget怎么设计了,呵呵 复制别人的widget资源文件拷贝过来就可以了。 如何反编译寻找别人的widget资源,根据我们创建widget小部件的逻辑可以知道:首先我们从别人的清单文件里找到了<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />该节点,然后通过该节点就可以找到<meta-data>里指定的对应文件,然后在对应文件里发现该文件应用里一个布局xml文件,这就是我们需要找的资源布局文件。
继续定义一个继承AppWidgetProvider
MyWidget.java
public class MyWidget extends AppWidgetProvider {
@Override
public void onEnabled(Context context) {
Intent intent = new Intent(context,UpdateWidgetService.class);
//在widget开启生命周期时开启服务
context.startService(intent);
super.onEnabled(context);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
Intent intent = new Intent(context,UpdateWidgetService.class);
context.startService(intent);
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
@Override
public void onDisabled(Context context) {
Intent intent = new Intent(context,UpdateWidgetService.class);
//在widget结束生命周期的时候关闭服务
context.stopService(intent);
super.onDisabled(context);
}
}
清单文件配置广播接受者
<receiver android:name="com.itheima.mobile47.receiver.MyWidget" >
<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>
指定widget的元数据:复制金山的widget的元数据
XML:exampleappwidgetinfo.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/process_widget"
android:minHeight="72.0dip"
android:minWidth="294.0dip"
android:updatePeriodMillis="0" />
layout: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:orientation="horizontal"
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:text="正在运行软件:17个"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10.0dip"
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:text="可用内存:398.00MB"
android:id="@+id/process_memory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10.0dip"
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/main_icon" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="黑马小小卫士"
android:textColor="#ffffff" />
</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/function_greenbutton_selector"
android:text="一键清理"
android:textColor="#ffffff" />
</LinearLayout>
</LinearLayout>
需求:实现widget里的功能
widget的生命周期 重写父类的的方法观察其生命周期:onReceiver、onUpdate、onDeleted、onEnabled、onDisabled 在桌面上创建widget时
当在桌面再创建一个:
再创建一个:
删除一个时:
删一个,和上一样 把最后一个删除:
总结:
课2
但是从课一的生命周期知道,widget小部件的最小刷新时间是半小时,原因是即使在布局XML文件里的配置是 android:updatePeriodMillis="0" ,但是安卓工程师因为系统效率的考虑,还是设置安卓的小部件最低刷新时间默认是半小时。但是需求是我们的手机卫士widget小部件也要每隔几秒更新一次,就要在代码里增加计时器来刷新桌面的widget:Timer类 那么就新建一个UpdateWidgetService.java
UpdateWidgetService.java
public class UpdateWidgetService extends Service {
添加清单文件
<!-- 自动定期更新widget的服务 -->
<service android:name="com.itheima.mobile47.service.UpdateWidgetService"></service>
给服务增加计时器模板:
那么服务什么时候开启?当桌面出现了widget了时候就开启服务 在MyWidgt里的onEnabled里开启
@Override
public void onEnabled(Context context) {
Intent intent = new Intent(context,UpdateWidgetService.class);
//在widget开启生命周期时开启服务
context.startService(intent);
super.onEnabled(context);
}
在MyWidgt里的onDisabled里关闭
@Override
public void onDisabled(Context context) {
Intent intent = new Intent(context,UpdateWidgetService.class);
//在widget结束生命周期的时候关闭服务
context.stopService(intent);
super.onDisabled(context);
}
测试: 如果在系统里把widget所控制的服务关闭,那么widget里的刷新也就停止了,那么我们怎么复活服务?流氓做法:就是注册其他系统级别的广播来绑定该计时器服务,那么就可以重启服务。
在服务里计数器的框架里更新widget的内容。
涉及了进程间通信(为什么?) ipc(进程间通信:inter process communication) aidl
需求:在手机卫士的应用程序里面更新桌面应用,小控件的数据 谷歌给我们提供了一个AppWidgetManager类,方便我们的应用和widget的通信 UpdateWidgetService.java进行进程间通信 RemoteView来进行进程间通信(属于AppWidgetManager),他们内部其实是使用反射来实现进程间的数据传递 远程view对象的引用。
UpdateWidgetService.java
@Override
public void onCreate() {
// 得到widget更新的服务的引用
awm = AppWidgetManager.getInstance(this);
timer = new Timer();
task = new TimerTask() {
@Override
public void run() {
System.out.println("每隔4秒代码更新下widget");
// ipc 进程间通讯 aidl
// 在手机卫士的应用程序里面更新桌面应用 小控件的数据
ComponentName provider = new ComponentName(
getApplicationContext(), MyWidget.class);
// 创建一个远程的view对象,让另外一个应用程序显示
RemoteViews views = new RemoteViews(getPackageName(),
R.layout.process_widget);
views.setTextViewText(
R.id.process_count,
"正在运行软件:"
+ SystemInfoUtils
.getRunningProcessCount(getApplicationContext()));
views.setTextViewText(
R.id.process_memory,
"可用内存:"
+ Formatter
.formatFileSize(
getApplicationContext(),
SystemInfoUtils
.getAvailRamSize(getApplicationContext())));
//描述一个动作,交给另外的一个应用程序执行。
//利用延期的意图发送一个自定义的广播
Intent intent = new Intent();
intent.setAction("com.ithiema.killbackgroudprocess");
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.btn_clear, pendingIntent);
awm.updateAppWidget(provider, views);
}
};
timer.schedule(task, 0, 4000);
super.onCreate();
}
实现widget里一键清理的功能
继续自定义一个广播接受者接收widget发送出来的广播 KillBackgroundProcessReceiver.java 清单文件注册广播:还有action,把发送过来的action复制过来。 在广播里面进行逻辑编写:清理掉后台的进程。模板代码
子线程吐不出土司,可以使用线程合并技术吐出来
UpdateWidgetService.java
//描述一个动作,交给另外的一个应用程序执行。
//利用延期的意图发送一个自定义的广播
Intent intent = new Intent();
intent.setAction("com.ithiema.killbackgroudprocess");
PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.btn_clear, pendingIntent);
awm.updateAppWidget(provider, views);
自定义广播的清单文件
<!-- 自定义的广播接受者,用来杀死后台进程 -->
<receiver android:name="com.itheima.mobile47.receiver.KillBackgroundProcessReceiver">
<intent-filter >
<action android:name="com.ithiema.killbackgroudprocess"/>
</intent-filter>
</receiver>
KillBackgroundProcessReceiver.java
public class KillBackgroundProcessReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("收到了自定义的广播");
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> infos = am.getRunningAppProcesses();
for(RunningAppProcessInfo info: infos){
am.killBackgroundProcesses(info.processName);
}
Toast.makeText(context, "清理完毕",0).show();
}
}
课3
widget的小细节:在清单文件头部的installLocation="preferExternal"
widget就找不到了,所以app要装在内存中 (重点)介绍一个新的窗口小部件:
新建工程小火箭
拿到腾讯管家的apk,拿到两个火箭的动画 帧动画怎么写呢?
App Resources--->animation 新建rocket.xml,把资源放进xml文件里
drawable:rocket.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/desktop_rocket_launch_1" android:duration="100" />
<item android:drawable="@drawable/desktop_rocket_launch_2" android:duration="100" />
</animation-list>
新建服务TestService,配清单
<service android:name="com.itheima.rocket.TestService"></service>
打开toast的源代码
TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
}
使用WindowManager把自定义的窗体view对象(帧动画)加进来。
开启帧动画
TestService.java
public class TestService extends Service {
private WindowManager wm;
private ImageView view;
private int screenWidth;
private int screenHeight;
@Override
public IBinder onBind(Intent intent) {
return null;
}
WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
@Override
public void onCreate() {
//以下这段代码是从Toast源码里找到的,因为Toast也是使用窗口管理器来创建Toast的
/**
* TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
}
*/
wm = (WindowManager) getSystemService(WINDOW_SERVICE);
//获取系统默认窗口的长和宽
screenWidth = wm.getDefaultDisplay().getWidth();
screenHeight = wm.getDefaultDisplay().getHeight();
//以下的mParams是窗口管理器提供出来的管理自定义窗口的长和宽的控制变量
//WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;//包括体内容
mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;//包括体内容
mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE//
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;//Window flag: as long as this window is visible to the user, keep the device's screen turned on and bright.
mParams.format = PixelFormat.TRANSLUCENT;
mParams.gravity = Gravity.LEFT+Gravity.TOP;//让自定义窗体:小火箭在左上方初始化
mParams.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
//加载帧动画
view = new ImageView(getApplicationContext());
view.setBackgroundResource(R.drawable.rocket);
AnimationDrawable rocketAnimation = (AnimationDrawable) view
.getBackground();
rocketAnimation.start();
//把帧动画加载到系统窗口管理器里(WindowManager wm;)
wm.addView(view, mParams);
最后在服务关闭了就把WindowManager关闭
@Override
public void onDestroy() {
super.onDestroy();
wm.removeView(view);
view = null;
}
在关闭MainActivity.java时开启火箭
@Override
protected void onDestroy() {
Intent intent = new Intent(this,TestService.class);
startService(intent);
super.onDestroy();
}
MainActivity的逻辑就是一打开后就初始化火箭的动画,然后马上开始显示火箭发射的动画
MainActivity.java
public class MainActivity extends Activity {
//该MainActivity的逻辑就是一打开后就初始化火箭的动画,然后马上开始显示火箭发射的动画
private ImageView rocket;
private ImageView iv_bottom;
private ImageView iv_top;
private LayoutParams params;
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
if(msg.what==1){
iv_bottom.setVisibility(View.VISIBLE);
}
if(msg.what==4){
iv_top.setVisibility(View.VISIBLE);
}
//子线程从1到10都发送信息到handler来进行刷新火箭的位置
rocket.setLayoutParams(params);
if(msg.what==9){
//关闭Activity
finish();
}
};
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//低烟
iv_bottom = (ImageView) findViewById(R.id.iv_bottom);
//高烟
iv_top = (ImageView) findViewById(R.id.iv_top);
//火箭
rocket = (ImageView) findViewById(R.id.rocket);
rocket.setBackgroundResource(R.drawable.rocket);
//给火箭显示帧动画
AnimationDrawable rocketAnimation = (AnimationDrawable) rocket
.getBackground();
rocketAnimation.start();
//获取MainActivity所绑定的布局资源的RelativeLayout的宽高变量
params = (LayoutParams) rocket.getLayoutParams();
//让线程跑
new Thread(){
public void run() {
for(int i=0;i<10;i++){
params.topMargin-=i*30;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = Message.obtain();
//i作为信息发给handler,让handler去判断每个位置,而给某个位置去做操作
msg.what = i;
handler.sendMessage(msg);
}
};
}.start();
}
@Override
protected void onDestroy() {
Intent intent = new Intent(this,TestService.class);
startService(intent);
super.onDestroy();
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<ImageView
android:id="@+id/rocket"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="320dip"
/>
<ImageView
android:visibility="invisible"
android:id="@+id/iv_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:src="@drawable/desktop_smoke_m" />
<ImageView
android:visibility="invisible"
android:id="@+id/iv_top"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/iv_bottom"
android:src="@drawable/desktop_smoke_t" />
</RelativeLayout>
继续让火箭移动TestService.java 给view对象加入动作监听事件。setOnTouchListener
TestService.java
//给动画加上监听器
view.setOnTouchListener(new OnTouchListener() {
int startX;
int startY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int newX = (int) event.getRawX();
int newY = (int) event.getRawY();
int dx = newX - startX;
int dy = newY - startY;
mParams.x+=dx;
mParams.y+=dy;
wm.updateViewLayout(view, mParams);
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP://手指离开火箭
System.out.println(mParams.y);
if(mParams.x > screenWidth/3 && mParams.x < screenWidth/3*2&& mParams.y> screenHeight/3*2){
Toast.makeText(getApplicationContext(), "发射火箭", 0).show();
stopSelf();
//MainActivity就是让火箭从最低发射到最高的动画显示
Intent intent = new Intent(TestService.this,MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
break;
}
return true;
}
});
当手指按下ACTIONDOWN就记录下开始的坐标。 当手指移动ACTIONMOVE就记录结束的坐标。(用endx-starty) 下一步就是更新小火箭的位置。
wm.updateViewLayout(view, mParams); 不用限制小火箭是否出界的判断。
使用触摸的监听事件,需要加权限:
权限是<uses-permission android:name="android.permission.SYSTEMALERTWINDOW"/>
继续的需求就是火箭从最下发送升空(手机最顶),随着升空激活进程清空的操作 继续在手指离开的监听操作ACTION_UP 获得屏幕的宽高: 注意对齐:
然后操作: 继续实现火箭发射的烟雾 把烟雾资源拿来 在activity_main.xml里进行设置。 在清单文件设置
在MainActivity里进行火箭发射的逻辑编写。 在MainActivity里实现handler,在子线程里发送消息来处理烟雾,而把之前的线程和并所处理火箭上天的逻辑取消,把其逻辑放进handler里进行处理。 当火箭上升完后烟雾消失。 把这些动画和开启服务和停掉服务的逻辑结合 TestService+MainActivity,配合activity的关闭的生命周期来开启服务:整个小火箭的工程逻辑就是,当打开MainActivity.java时就首先开启演示一次火箭发射的动画,然后在MainActivity.java里有个handler,当动画播完后的一个节点让handler去关闭MainActivity.java,在MainActivity.java的onDestroy方法会启动TestService,该服务会初始化一个可拖拽的自定义窗口,该窗口的原理是Toast里的源代码,然后给窗口加上点击事件,在拖拽到某个位置的时候,就使用意图去再次打开MainActivity.java,然后就播放一次火箭发射的动画。这就是整个火箭发射的流程。
TestService.java
public class TestService extends Service {
private WindowManager wm;
private ImageView view;
private int screenWidth;
private int screenHeight;
@Override
public IBinder onBind(Intent intent) {
return null;
}
WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
@Override
public void onCreate() {
//以下这段代码是从Toast源码里找到的,因为Toast也是使用窗口管理器来创建Toast的
/**
* TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
}
*/
wm = (WindowManager) getSystemService(WINDOW_SERVICE);
//获取系统默认窗口的长和宽
screenWidth = wm.getDefaultDisplay().getWidth();
screenHeight = wm.getDefaultDisplay().getHeight();
//以下的mParams是窗口管理器提供出来的管理自定义窗口的长和宽的控制变量
//WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;//包括体内容
mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;//包括体内容
mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE//
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;//Window flag: as long as this window is visible to the user, keep the device's screen turned on and bright.
mParams.format = PixelFormat.TRANSLUCENT;
mParams.gravity = Gravity.LEFT+Gravity.TOP;//让自定义窗体:小火箭在左上方初始化
mParams.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
//加载帧动画
view = new ImageView(getApplicationContext());
view.setBackgroundResource(R.drawable.rocket);
AnimationDrawable rocketAnimation = (AnimationDrawable) view
.getBackground();
rocketAnimation.start();
//把帧动画加载到系统窗口管理器里(WindowManager wm;)
wm.addView(view, mParams);
//给动画加上监听器
view.setOnTouchListener(new OnTouchListener() {
int startX;
int startY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int newX = (int) event.getRawX();
int newY = (int) event.getRawY();
int dx = newX - startX;
int dy = newY - startY;
mParams.x+=dx;
mParams.y+=dy;
wm.updateViewLayout(view, mParams);
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP://手指离开火箭
System.out.println(mParams.y);
if(mParams.x > screenWidth/3 && mParams.x < screenWidth/3*2&& mParams.y> screenHeight/3*2){
Toast.makeText(getApplicationContext(), "发射火箭", 0).show();
stopSelf();
//MainActivity就是让火箭从最低发射到最高的动画显示
Intent intent = new Intent(TestService.this,MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
break;
}
return true;
}
});
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
wm.removeView(view);
view = null;
}
}
腾讯火箭升空时实现一键清理的原理
在小火箭的工程代码里加入一键清理的模板代码就ok
MainActivity.java
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
if(msg.what==1){
iv_bottom.setVisibility(View.VISIBLE);
}
if(msg.what==4){
iv_top.setVisibility(View.VISIBLE);
}
//子线程从1到10都发送信息到handler来进行刷新火箭的位置
rocket.setLayoutParams(params);
if(msg.what==9){
//在此加入一键清理的模板代码即可:清理完毕后可以加入土司来告知用户
//关闭Activity
finish();
}
课4
实现程序锁
在高级工具里activity_tools.xml里增加软件锁功能
activity_tools.xml
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_selector"
android:clickable="true"
android:drawableLeft="@android:drawable/star_big_on"
android:enabled="true"
android:focusable="true"
android:onClick="enterAppLock"
android:text="软件锁"
android:textSize="24sp" />
ToolsActivity--->enterAppLock(进入程序锁界面)
ToolsActivity.java
/**
* 进入程序锁界面
*
* @param view
*/
public void enterAppLock(View view){
Intent intent = new Intent(this,ApplockActivity.class);
startActivity(intent);
}
新建ApplockActivity.java配置清单文件
<!-- 程序锁的界面 -->
<activity android:name="com.itheima.mobile47.ApplockActivity"></activity>
实现布局文件:
可以使用Fragment,也可以使用listView,两个ListView,一个可视,一个不可视 载入布局文件要使用的素材。
在同一个布局中实现两套listview的切换显示方法
布局文件中的下面的空间放入两个listView嵌套在一起。使用frameLayout嵌套他们 activity_applock.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" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal" >
<TextView
android:id="@+id/tv_unlock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tab_left_pressed"
android:gravity="center"
android:text="未加锁"
android:textColor="#ffffff" />
<TextView
android:id="@+id/tv_locked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tab_right_default"
android:gravity="center"
android:text="已加锁"
android:textColor="#ffffff" />
</LinearLayout>
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:id="@+id/ll_unlock"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_unlock_count"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="未加锁应用xx个" />
<ListView
android:id="@+id/lv_unlock"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</ListView>
</LinearLayout>
<LinearLayout
android:visibility="gone"
android:id="@+id/ll_locked"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_locked_count"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="已加锁应用xx个" />
<ListView
android:id="@+id/lv_locked"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</ListView>
</LinearLayout>
</FrameLayout>
</LinearLayout>
回到ApplockActivity.java初始化控件 ApplockActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_applock);
ApplockActivity.java 两个listview为加锁和已加锁 创建数据适配器:定义当前适配器的类型
//true和false是一个标识符,用于控制listView的切换显示
unlockAdapter = new InnerAdapter(true);
lockedAdapter = new InnerAdapter(false);
//listview内容初始化
lv_unlock.setAdapter(unlockAdapter);
lv_locked.setAdapter(lockedAdapter);
//在InnerAdapter里对listview进行切换
private class InnerAdapter extends BaseAdapter{
/**
* 定义当前适配器的类型,
* true 未加锁
* false 已加速
*/
//控制加锁或未加锁listview内容的显示
private boolean showUnlock;
public InnerAdapter(boolean showUnlock) {
this.showUnlock = showUnlock;
}
设计listview为加锁里条目的布局 item_applock.xml
item_applock.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/iv_applock_icon"
android:layout_width="45dp"
android:layout_height="45dp"
android:src="@drawable/ic_launcher" />
<TextView
android:textSize="18sp"
android:layout_centerVertical="true"
android:id="@+id/tv_applock_name"
android:layout_toRightOf="@id/iv_applock_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="程序名称" />
<ImageView
android:layout_alignParentRight="true"
android:id="@+id/iv_applock_change"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/list_button_lock_pressed"
/>
</RelativeLayout>
ApplockActivity.java设计getView方法
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View view;
ViewHolder holder;
if(convertView!=null && convertView instanceof RelativeLayout){
view = convertView;
holder = (ViewHolder) view.getTag();
}else{
holder = new ViewHolder();
view = View.inflate(getApplicationContext(), R.layout.item_applock, null);
holder.iv_icon = (ImageView) view.findViewById(R.id.iv_applock_icon);
holder.iv_change = (ImageView) view.findViewById(R.id.iv_applock_change);
holder.tv_name = (TextView) view.findViewById(R.id.tv_applock_name);
view.setTag(holder);
}
final AppInfo appinfo;
//如果是未加锁界面,就在未加锁的app集合中取出应用对象,然后给listview所加载的view填充控件对象
if(showUnlock){
//未加锁界面
appinfo = unlockAppInfos.get(position);
//给listview条目中的锁提供点击事件
holder.iv_change.setImageResource(R.drawable.list_button_lock_pressed);
holder.iv_change.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//给锁加上动画
TranslateAnimation ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1.0f,
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
ta.setDuration(200);
//在点击时就执行动画效果
view.startAnimation(ta);//通知动画开始播放了。
//给动画加上监听事件
ta.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
//在监听事件里:当动画结束马上执行
@Override
public void onAnimationEnd(Animation animation) {
//把这个包名加入到程序锁数据库
//这是新建的ApplockDao类,设计黑名单的数据库applock.db的接口,提供操作存储需要加锁的应用的增删查该的操作
dao.add(appinfo.getPackageName());
//操作加锁和未加锁应用集合
unlockAppInfos.remove(appinfo);
lockedAppInfos.add(appinfo);
//通知界面更新
unlockAdapter.notifyDataSetChanged();
lockedAdapter.notifyDataSetChanged();
}
});
}
});
}else{
appinfo = lockedAppInfos.get(position);
//已加锁界面
holder.iv_change.setImageResource(R.drawable.list_button_unlock_pressed);
holder.iv_change.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
TranslateAnimation ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, -1.0f,
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
ta.setDuration(200);
view.startAnimation(ta);//通知动画开始播放了。
ta.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
//把这个包名从程序锁数据库删除
dao.delete(appinfo.getPackageName());
lockedAppInfos.remove(appinfo);
unlockAppInfos.add(appinfo);
//通知界面更新
unlockAdapter.notifyDataSetChanged();
lockedAdapter.notifyDataSetChanged();
}
});
}
});
}
创建数据库ApplockDBHelper,用来存储锁定的应用程序的信息
ApplockDBHelper.java
/**
* ============================================================
* 描 述 :
*
* 创建黑名单的数据库
* ============================================================
**/
public class ApplockDBHelper extends SQLiteOpenHelper{
public ApplockDBHelper(Context context) {
super(context, "applock.db", null, 1);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table lockinfo (_id integer primary key autoincrement,packname varchar(20)) ");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
}
}
创建ApplockDao,用来操作数据库 add(String packName) \ delete(String packname)\find 实现APPlockDao;
ApplockDao.java
/**
* 程序锁增删改查的业务类
*
*/
public class ApplockDao {
private ApplockDBHelper helper;
public ApplockDao(Context context) {
helper = new ApplockDBHelper(context);
}
/**
* 添加一条锁定的应用程序
* @param packname 包名
*/
public void add(String packname){
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("packname", packname);
db.insert("lockinfo", null, values);
db.close();
}
/**
* 删除一条锁定的应用程序
* @param packname 包名
*/
public void delete(String packname){
SQLiteDatabase db = helper.getWritableDatabase();
db.delete("lockinfo", "packname=?", new String[]{packname});
db.close();
}
/**
* 查询一条锁定的应用程序
* @param packname 包名
* @return true 被锁定 false 没有锁定
*/
public boolean find(String packname){
boolean result = false;
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor = db.query("lockinfo", null, "packname=?", new String[]{packname}, null, null, null);
if(cursor.moveToNext()){
result = true;
}
cursor.close();
db.close();
return result;
}
}
ApplockActivity.java,在里面使用ApplickDao - 在点击条目时就把app包名加入了数据库的。 - 编写数据适配器的逻辑 - 把展示在listView里的list集合进行分类。然后对ApplockActivity.java进行修改。
课5
实现腾讯程序锁条目加上锁和锁上锁的左右滑动效果
ApplockActivity里加上TransLateAnimation效果
ApplockActivity.java
@Override
public void onClick(View v) {
//给锁加上动画
TranslateAnimation ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
Animation.RELATIVE_TO_SELF, 1.0f,
Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
ta.setDuration(200);
//在点击时就执行动画效果
view.startAnimation(ta);//通知动画开始播放了。
给动画加上监听器,让动画结束时再通知listview更新
但是一路点下来程序就垮了,
数据的变化和界面的通知没法同步,所以把代码都放在onAnimationEnd里
ApplockActivity.java
//给动画加上监听事件
ta.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
//在监听事件里:当动画结束马上执行
@Override
public void onAnimationEnd(Animation animation) {
//把这个包名加入到程序锁数据库
//这是新建的ApplockDao类,设计黑名单的数据库applock.db的接口,提供操作存储需要加锁的应用的增删查该的操作
dao.add(appinfo.getPackageName());
//操作加锁和未加锁应用集合
unlockAppInfos.remove(appinfo);
lockedAppInfos.add(appinfo);
//通知界面更新
unlockAdapter.notifyDataSetChanged();
lockedAdapter.notifyDataSetChanged();
}
});
实现程序锁的具体功能
怎么实现? 现实生活中:看门狗
新建WatchDogService.java
配置清单文件
<!-- 程序锁看门狗服务 -->
<service android:name="com.itheima.mobile47.service.WatchDogService"></service>
服务:监视当前用户操作的应用程序,如果这个应用程序需要保护,看门狗就蹦出来,让用户输入密码 在设置中心界面StrringCenterAtivity.java的布局文件activitysettingcenter.xml加一个看门狗的选项
activitysettingcenter.xml
<com.itheima.mobile47.view.SettingView
android:id="@+id/setting_view_watchdog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
itheima:desc="看门狗已经开启#看门狗已经关闭"
itheima:title="看门狗设置" >
</com.itheima.mobile47.view.SettingView>
然后在设置中心写代码。
SettingCenterActivity.java
//看门狗设置
setting_view_watchdog= (SettingView) findViewById(R.id.setting_view_watchdog);
watchDogIntent = new Intent(this, WatchDogService.class);
setting_view_watchdog.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(setting_view_watchdog.isChecked()){
setting_view_watchdog.setChecked(false);
stopService(watchDogIntent);
}else{
setting_view_watchdog.setChecked(true);
startService(watchDogIntent);
}
}
});
然后给看门狗加入功能代码: WatchDogService.java 使用ApplockDao 看dao.find来判断应用程序是否要锁定。 WatchDogService.java
//对比获得的最新加载的app的名字是否存在于加锁app数据库中:当在加锁数据库中找到该packname,那么再比较该packname的app有没有通过密码校验
if (dao.find(packname)) {
//有通过密码校验
// 检查主人是否发过临时停止保护的指令
if (tempStopPacknames.contains(packname)) {
// 什么事情都不做,即不再弹出程序锁界面
//没有通过密码校验
} else {
// 这个应用程序需要被锁定
// 跳出来看门狗,让用户输入密码。
Intent intent = new Intent(WatchDogService.this,
WatchDogEnterPwdActivity.class);
//给该WatchDogEnterPwdActivity.class设置一个开启任务的模式
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//用意图开启界面,并加上应用的信息
intent.putExtra("packname", packname);
startActivity(intent);
}
}
为服务新建一个输入密码的界面 WatchDogEnterPwdActivity.java和清单文件 WatchDogEnterPwdActivity.java
public class WatchDogEnterPwdActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_watchdog_pwd);
实现界面布局: activitywatchdogpwd.xml
activitywatchdogpwd.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:gravity="center_horizontal"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_lockapp_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" />
<ImageView
android:src="@drawable/ic_launcher"
android:id="@+id/iv_lockapp_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/et_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:inputType="textPassword" >
</EditText>
<Button
android:onClick="enterApp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="确定" />
</LinearLayout>
课6
继续设置看门狗布局:activitywatchdogpwd.xml WatchDogEnterPwdActivity.java逻辑 声明控件:初始化 设置WatchDogService.java的内置广播,为其添加意图
WatchDogService.java
//内部广播类:该广播类主要是操作tempStopPacknames集合,即操作从程序锁界面传来的应用的名字和操作锁屏的状态中集合的清空,把集合提供给service去控制应用的加锁效果
//专门获取程序锁界面中传来的广播信号,通知WatchDogService去解除程序锁界面
private class InnerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//广播一直接收所有的频道信息
String action = intent.getAction();
//获取WatchDogEnterPwdActivity传来的StringExtra
if ("com.itheima.doggoaway".equals(action)) {
//StringExtra的key
String packname = intent.getStringExtra("packname");
// 看门狗把主人要求临时停止保护的包名给记录下来,即把WatchDogEnterPwdActivity传来的StringExtra:packname,把其app加入临时停止保护的集合中
tempStopPacknames.add(packname);
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
//当接收的广播频道是锁屏,就把临时停止保护的应用集合清零,即让下次打开要保护的应用再次输入密码
tempStopPacknames.clear();
}
}
}
//在打开服务时就动态注册广播,看门狗
receiver = new InnerReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.itheima.doggoaway");
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(receiver, filter);
在WatchDogEnterPwdActivity.java获取意图,意图中有包名等信息
WatchDogEnterPwdActivity.java
public void enterApp(View view){
String pwd = et_pwd.getText().toString().trim();
if("123".equals(pwd)){
//密码正确,关闭输入密码的界面.
//给看门狗发一个消息, 让看门狗 临时的停止对这个应用程序的保护。
//发送一个自定义的广播
Intent intent = new Intent();
intent.setAction("com.itheima.doggoaway");
intent.putExtra("packname", packname);
//如果密码正确了就执行这段代码,给com.itheima.doggoaway广播返回应用名
sendBroadcast(intent);
//关闭拦截界面
finish();
}else{
//如果密码不正确就执行动画效果
Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
iv_lockapp_icon.startAnimation(shake);
}
}
禁用返回键:onBackPressed里的源码:
重写父类方法:
但腾讯的返回时直接返回到桌面,但是不会看见要锁的app界面 我们重写返回键的方法,让其激活home键,看源码Lancher2 找其清单文件Lancher2.Lancher
//重写返回键
@Override
public void onBackPressed() {
//在源码里找到home键的清单文件配置
// <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"/>
//如果取消返回键的效果对用户的体验效果有些不好,所以我们把返回键模拟成home键的功能。
Intent intent = new Intent();
intent.setAction("android.intent.action.MAIN");
intent.addCategory("android.intent.category.HOME");
intent.addCategory("android.intent.category.DEFAULT");
intent.addCategory("android.intent.category.MONKEY");
startActivity(intent);
//关闭拦截页面
finish();
}
看ApiDemo软件里看到很多有趣的样式,我们可以看到了就去翻源码,抄过来实现。 通过示例界面中的文字作为线索,然后找源码 给WatchDogEnterPwdActivity.java加动画
//如果密码不正确就执行动画效果
Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
iv_lockapp_icon.startAnimation(shake);
WatchDogEnterPwdActivity.java发送一个自定义广播,然后在WatchDogService.java加上广播接收者接收消息,暂时放行app 在注册锁屏的广播接受者去给看门狗这是超时操作时间,当锁屏了就重新使用、
//内部广播类:该广播类主要是操作tempStopPacknames集合,即操作从程序锁界面传来的应用的名字和操作锁屏的状态中集合的清空,把集合提供给service去控制应用的加锁效果 //专门获取程序锁界面中传来的广播信号,通知WatchDogService去解除程序锁界面
private class InnerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//广播一直接收所有的频道信息
String action = intent.getAction();
//获取WatchDogEnterPwdActivity传来的StringExtra
if ("com.itheima.doggoaway".equals(action)) {
//StringExtra的key
String packname = intent.getStringExtra("packname");
// 看门狗把主人要求临时停止保护的包名给记录下来,即把WatchDogEnterPwdActivity传来的StringExtra:packname,把其app加入临时停止保护的集合中
tempStopPacknames.add(packname);
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
//当接收的广播频道是锁屏,就把临时停止保护的应用集合清零,即让下次打开要保护的应用再次输入密码
tempStopPacknames.clear();
}
}
}
看门狗的完整逻辑(加了完整注释)
WatchDogService.java
/**
* 监视当前用户操作的应用程序,如果这个应用程序需要保护,看门狗就蹦出来,让用户输入密码
* //其实该service所做的事件就是创建一个死循环,不断查看tempStopPacknames集合里的数据和新打开的app,从而进行比较操作
* @author Administrator
*
*/
public class WatchDogService extends Service {
//应用管理器
private ActivityManager am;
private boolean flag;
// 程序锁的数据库dao
private ApplockDao dao;
//内部广播类
private InnerReceiver receiver;
/**
* 临时停止保护的包名集合
*/
private List<String> tempStopPacknames;
@Override
public IBinder onBind(Intent intent) {
return null;
}
//内部广播类:该广播类主要是操作tempStopPacknames集合,即操作从程序锁界面传来的应用的名字和操作锁屏的状态中集合的清空,把集合提供给service去控制应用的加锁效果
//专门获取程序锁界面中传来的广播信号,通知WatchDogService去解除程序锁界面
private class InnerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//广播一直接收所有的频道信息
String action = intent.getAction();
//获取WatchDogEnterPwdActivity传来的StringExtra
if ("com.itheima.doggoaway".equals(action)) {
//StringExtra的key
String packname = intent.getStringExtra("packname");
// 看门狗把主人要求临时停止保护的包名给记录下来,即把WatchDogEnterPwdActivity传来的StringExtra:packname,把其app加入临时停止保护的集合中
tempStopPacknames.add(packname);
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
//当接收的广播频道是锁屏,就把临时停止保护的应用集合清零,即让下次打开要保护的应用再次输入密码
tempStopPacknames.clear();
}
}
}
@Override
public void onCreate() {
//初始化临时取消保护的app的集合
tempStopPacknames = new ArrayList<String>();
//获取操作加锁app的数据库的接口
dao = new ApplockDao(this);
//在打开服务时就动态注册广播,看门狗
receiver = new InnerReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.itheima.doggoaway");
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(receiver, filter);
// 不停的刷新 监视当前的正在运行的应用程序信息
//获取应用管理者
am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
//该标志符用来控制是否不断刷新
flag = true;
new Thread() {
//其实该service所做的事件就是创建一个死循环,不断查看tempStopPacknames集合里的数据和新打开的app,从而进行比较操作
public void run() {
//如果为true就进入死循环
while (flag) {
// 获取用户的正在运行的应用程序任务栈的列表,最近使用的在集合的最前面
List<RunningTaskInfo> taskinfos = am.getRunningTasks(100);
String packname = taskinfos.get(0).topActivity
.getPackageName();
System.out.println(packname);
//对比获得的最新加载的app的名字是否存在于加锁app数据库中:当在加锁数据库中找到该packname,那么再比较该packname的app有没有通过密码校验
if (dao.find(packname)) {
//有通过密码校验
// 检查主人是否发过临时停止保护的指令
if (tempStopPacknames.contains(packname)) {
// 什么事情都不做,即不再弹出程序锁界面
//没有通过密码校验
} else {
// 这个应用程序需要被锁定
// 跳出来看门狗,让用户输入密码。
Intent intent = new Intent(WatchDogService.this,
WatchDogEnterPwdActivity.class);
//给该WatchDogEnterPwdActivity.class设置一个开启任务的模式
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//用意图开启界面,并加上应用的信息
intent.putExtra("packname", packname);
startActivity(intent);
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}.start();
super.onCreate();
}
//在关闭服务的时候也就关闭关闭的监控
@Override
public void onDestroy() {
flag = false;
//动态注销广播
unregisterReceiver(receiver);
receiver = null;
super.onDestroy();
}
}
程序锁界面activity的完整逻辑
WatchDogEnterPwdActivity.java
public class WatchDogEnterPwdActivity extends Activity {
private TextView tv_lockapp_name;
private ImageView iv_lockapp_icon;
private EditText et_pwd;
private String packname;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_watchdog_pwd);
iv_lockapp_icon = (ImageView) findViewById(R.id.iv_lockapp_icon);
tv_lockapp_name = (TextView) findViewById(R.id.tv_lockapp_name);
et_pwd = (EditText) findViewById(R.id.et_pwd);
//获取激活当前输入密码界面的意图
Intent intent = getIntent();
packname = intent.getStringExtra("packname");
PackageManager pm = getPackageManager();
try {
iv_lockapp_icon.setImageDrawable(pm.getPackageInfo(packname, 0).applicationInfo.loadIcon(pm));
tv_lockapp_name.setText(pm.getPackageInfo(packname, 0).applicationInfo.loadLabel(pm).toString());
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
public void enterApp(View view){
String pwd = et_pwd.getText().toString().trim();
if("123".equals(pwd)){
//密码正确,关闭输入密码的界面.
//给看门狗发一个消息, 让看门狗 临时的停止对这个应用程序的保护。
//发送一个自定义的广播
Intent intent = new Intent();
intent.setAction("com.itheima.doggoaway");
intent.putExtra("packname", packname);
//如果密码正确了就执行这段代码,给com.itheima.doggoaway广播返回应用名
sendBroadcast(intent);
//关闭拦截界面
finish();
}else{
//如果密码不正确就执行动画效果
Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
iv_lockapp_icon.startAnimation(shake);
}
}
//重写返回键
@Override
public void onBackPressed() {
//在源码里找到home键的清单文件配置
// <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"/>
//如果取消返回键的效果对用户的体验效果有些不好,所以我们把返回键模拟成home键的功能。
Intent intent = new Intent();
intent.setAction("android.intent.action.MAIN");
intent.addCategory("android.intent.category.HOME");
intent.addCategory("android.intent.category.DEFAULT");
intent.addCategory("android.intent.category.MONKEY");
startActivity(intent);
//关闭拦截页面
finish();
}
}
技术总结 两个前后覆盖的listview如何通过点击按钮切换
activity_applock.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/startpage"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal" >
<TextView
android:id="@+id/tv_unlock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tab_left_pressed"
android:gravity="center"
android:text="未加锁"
android:textColor="#ffffff" />
<TextView
android:id="@+id/tv_locked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tab_right_default"
android:gravity="center"
android:text="已加锁"
android:textColor="#ffffff" />
</LinearLayout>
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<LinearLayout
android:id="@+id/ll_unlock"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_unlock_count"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="未加锁应用xx个"
android:textColor="#E2DED8" />
<ListView
android:id="@+id/lv_unlock"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</ListView>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_locked"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:visibility="gone" >
<TextView
android:id="@+id/tv_locked_count"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="已加锁应用xx个"
android:textColor="#E2DED8" />
<ListView
android:id="@+id/lv_locked"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</ListView>
</LinearLayout>
</FrameLayout>
</LinearLayout>
ApplockActivity.java
public class ApplockActivity extends Activity implements OnClickListener{
private TextView tv_unlock;
private TextView tv_locked;
private LinearLayout ll_unlock;
private LinearLayout ll_locked;
//两个listView未加锁和已加锁
private ListView lv_unlock;
private ListView lv_locked;
private TextView tv_locked_count;
private TextView tv_unlock_count;
/**
* 存放手机里面的所有的应用程序信息
*
*/
private List<AppInfo> allAppInfos;
/**
* 未加锁应用程序信息
*/
private List<AppInfo> unlockAppInfos;
/**
* 已加锁应用程序信息
*/
private List<AppInfo> lockedAppInfos;
private ApplockDao dao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_applock);
dao = new ApplockDao(this);
tv_unlock = (TextView) findViewById(R.id.tv_unlock);
tv_locked = (TextView) findViewById(R.id.tv_locked);
ll_unlock = (LinearLayout) findViewById(R.id.ll_unlock);
ll_locked = (LinearLayout) findViewById(R.id.ll_locked);
lv_unlock = (ListView) findViewById(R.id.lv_unlock);
lv_locked = (ListView) findViewById(R.id.lv_locked);
tv_unlock_count = (TextView) findViewById(R.id.tv_unlock_count);
tv_locked_count = (TextView) findViewById(R.id.tv_locked_count);
//获取全部的应用程序信息,TODO 最好写在子线程
allAppInfos = AppInfoparser.getAppInfo(this);
unlockAppInfos = new ArrayList<AppInfo>();
lockedAppInfos = new ArrayList<AppInfo>();
for(AppInfo info : allAppInfos){
if(dao.find(info.getPackageName())){
lockedAppInfos.add(info);
}else{
unlockAppInfos.add(info);
}
}
//初始化未加锁和已加锁的TextView按钮样式
tv_locked.setOnClickListener(this);
tv_unlock.setOnClickListener(this);
}
//未加锁和已加锁的TextView按钮点击事件的状态切换:
/**
* private LinearLayout ll_unlock;
private LinearLayout ll_locked;
*/
@Override
public void onClick(View v) {
switch(v.getId()){
case R.id.tv_unlock:
tv_locked.setBackgroundResource(R.drawable.tab_right_default);
tv_unlock.setBackgroundResource(R.drawable.tab_left_pressed);
//两个listview都是有内容显示好的了,但是就是根据linearLayout的属性来操作两个嵌套在linearLayout里的listview的显示
ll_unlock.setVisibility(View.VISIBLE);
ll_locked.setVisibility(View.GONE);
break;
case R.id.tv_locked:
tv_locked.setBackgroundResource(R.drawable.tab_right_pressed);
tv_unlock.setBackgroundResource(R.drawable.tab_left_default);
//两个listview都是有内容显示好的了,但是就是根据linearLayout的属性来操作两个嵌套在linearLayout里的listview的显示
ll_unlock.setVisibility(View.GONE);
ll_locked.setVisibility(View.VISIBLE);
break;
}
}
}