//添加动画效果,动画默认是非阻塞的,所以执行动画的同时,动画以下的代码也会执行
animationView.startAnimation(mTranslateAnimation);//500毫秒
//对动画执行过程做事件监听,监听到动画执行完成后,再去移除集合中的数据,操作数据库,刷新界面
mTranslateAnimation.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
//动画开始的是调用方法
}
@Override
public void onAnimationRepeat(Animation animation) {
//动画重复时候调用方法
}
//动画执行结束后调用方法
@Override
public void onAnimationEnd(Animation animation) {
if(isLock){
//已加锁------>未加锁过程
//1.已加锁集合删除一个,未加锁集合添加一个,对象就是getItem方法获取的对象
mLockList.remove(appInfo);
mUnLockList.add(appInfo);
//2.从已加锁的数据库中删除一条数据
mDao.delete(appInfo.packageName);
//3.刷新数据适配器
mLockAdapter.notifyDataSetChanged();
}else{
//未加锁------>已加锁过程
//1.已加锁集合添加一个,未加锁集合移除一个,对象就是getItem方法获取的对象
mLockList.add(appInfo);
mUnLockList.remove(appInfo);
//2.从已加锁的数据库中插入一条数据
mDao.insert(appInfo.packageName);
//3.刷新数据适配器
mUnLockAdapter.notifyDataSetChanged();
}
}
});
2.程序锁必须在服务中去维护
1.判断当前手机正在开启的应用(现在手机可见任务栈)
2.如果开启的应用在已加锁的列表中,弹出拦截界面
3.看门狗服务,一直(死循环(子线程,可控))对开启的应用做监听
问题:
1,栈
2,过滤已经解锁应用(发送广播)
3,开启服务后,再去添加加锁应用,不能生效
内容观察者,观察数据库变化,一旦数据库变化,则放置包名的集合需要重新获取数据
4,挂起输入密码(拦截)界面的时候,不需要去显示手机卫士的图标
5,回退按钮处理,在输入密码的activity中点击回退按钮需要回到桌面()
## Day13 ##
- 新建工程,获取缓存大小
- 布局文件开发
<LinearLayout 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"
android:orientation="vertical" >
<EditText
android:id="@+id/et_package"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入包名" >
</EditText>
<Button
android:id="@+id/btn_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确定" />
<TextView
android:id="@+id/tv_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="查询结果" />
</LinearLayout>
- 查看系统设置源码, 查看清理缓存逻辑
1. 导入Setting源码
2. 查找清除缓存的逻辑
Clear cache->clear_cache_btn_text->installed_app_details->InstalledAppDetails->cache_size_text-> mAppEntry.cacheSize->stats.cacheSize->stats->mStatsObserver->getPackageSizeInfo->查看PackageManager源码,跟踪方法getPackageSizeInfo,发现改方法隐藏
3. 通过反射方式,调用PackageManager的方法
public Method[] getMethods()返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法。
public Method[] getDeclaredMethods()对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。
//需要权限:android.permission.GET_PACKAGE_SIZE
btnOk.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String packageName = etPackage.getText().toString().trim();
if (!TextUtils.isEmpty(packageName)) {
PackageManager pm = getPackageManager();
try {
Method method = pm.getClass().getMethod(
"getPackageSizeInfo", String.class,
IPackageStatsObserver.class);
method.invoke(pm, packageName, new MyObserver());
} catch (Exception e) {
e.printStackTrace();
}
} else {
Toast.makeText(getApplicationContext(), "输入内容不能为空!",
Toast.LENGTH_SHORT).show();
}
}
});
-------------------------------------
class MyObserver extends IPackageStatsObserver.Stub {
// 在子线程运行
@Override
public void onGetStatsCompleted(PackageStats pStats, boolean succeeded)
throws RemoteException {
long cacheSize = pStats.cacheSize;
long dataSize = pStats.dataSize;
long codeSize = pStats.codeSize;
String result = "缓存:"
+ Formatter.formatFileSize(getApplicationContext(),
cacheSize)
+ "\n"
+ "数据:"
+ Formatter.formatFileSize(getApplicationContext(),
dataSize)
+ "\n"
+ "代码:"
+ Formatter.formatFileSize(getApplicationContext(),
codeSize);
System.out.println(result);
Message msg = Message.obtain();
msg.obj = result;
mHandler.sendMessage(msg);
}
}
- 缓存清理模块开发
- 新建页面CleanCacheActivity
- 布局文件开发
<?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" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#8866ff00" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:text="缓存清理"
android:textColor="#000"
android:textSize="22sp" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:onClick="cleanCache"
android:text="立即清理" />
</RelativeLayout>
<ProgressBar
android:id="@+id/pb_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:progressDrawable="@drawable/custom_progress" />
<TextView
android:id="@+id/tv_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="正在扫描:" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</ScrollView>
</LinearLayout>
- 缓存页面逻辑
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case SCANNING:
String name = (String) msg.obj;
tvStatus.setText("正在扫描:" + name);
break;
case SHOW_CACHE_INFO:
CacheInfo info = (CacheInfo) msg.obj;
View itemView = View.inflate(getApplicationContext(),
R.layout.list_cacheinfo_item, null);
TextView tvName = (TextView) itemView
.findViewById(R.id.tv_name);
ImageView ivIcon = (ImageView) itemView
.findViewById(R.id.iv_icon);
TextView tvCache = (TextView) itemView
.findViewById(R.id.tv_cache_size);
ImageView ivDelete = (ImageView) itemView
.findViewById(R.id.iv_delete);
tvName.setText(info.name);
ivIcon.setImageDrawable(info.icon);
tvCache.setText("缓存大小:"
+ Formatter.formatFileSize(getApplicationContext(),
info.cacheSize));
llContainer.addView(itemView);
break;
case SCANNING_FINISHED:
tvStatus.setText("扫描完成");
break;
default:
break;
}
};
};
/**
* 开始扫描
*/
private void startScan() {
new Thread() {
@Override
public void run() {
List<PackageInfo> packages = mPM
.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);
pbProgress.setMax(packages.size());// 设置进度条最大值为安装包的数量
int progress = 0;
for (PackageInfo packageInfo : packages) {
try {
Method method = mPM.getClass().getMethod(
"getPackageSizeInfo", String.class,
IPackageStatsObserver.class);
method.invoke(mPM, packageInfo.packageName,
new MyObserver());
} catch (Exception e) {
e.printStackTrace();
}
progress++;
pbProgress.setProgress(progress);
// 发送更新进度的消息
Message msg = Message.obtain();
msg.what = SCANNING;
msg.obj = packageInfo.applicationInfo.loadLabel(mPM)
.toString();
mHandler.sendMessage(msg);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 发送扫描结束的消息
mHandler.sendEmptyMessage(SCANNING_FINISHED);
}
}.start();
}
class MyObserver extends IPackageStatsObserver.Stub {
// 在子线程运行
@Override
public void onGetStatsCompleted(PackageStats pStats, boolean succeeded)
throws RemoteException {
long cacheSize = pStats.cacheSize;// 获取缓存大小
if (cacheSize > 0) {
try {
CacheInfo info = new CacheInfo();
String packageName = pStats.packageName;
info.packageName = packageName;
ApplicationInfo applicationInfo = mPM.getApplicationInfo(
packageName, 0);
info.name = applicationInfo.loadLabel(mPM).toString();
info.icon = applicationInfo.loadIcon(mPM);
info.cacheSize = cacheSize;
// 扫描到缓存应用时发送消息
Message msg = Message.obtain();
msg.what = SHOW_CACHE_INFO;
msg.obj = info;
mHandler.sendMessage(msg);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
}
}
// 缓存对象的封装
class CacheInfo {
public String name;
public String packageName;
public Drawable icon;
public long cacheSize;
}
-------------------------
list_cacheinfo_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp" >
<ImageView
android:id="@+id/iv_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginLeft="20dp"
android:layout_toRightOf="@+id/iv_icon"
android:text="应用名称"
android:textColor="#000"
android:textSize="16sp" />
<TextView
android:id="@+id/tv_cache_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tv_name"
android:layout_below="@id/tv_name"
android:layout_marginTop="5dp"
android:text="缓存大小:"
android:textColor="#000"
android:textSize="16sp" />
<ImageView
android:id="@+id/iv_delete"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:src="@drawable/btn_black_number_delete_selector" />
</RelativeLayout>
- 一键清理缓存
这是用的Android的一个BUG,就是你得程序去申请很大的内存,比如直接申请10G,但是你得内存总共才1G,这时候系统为了满足你得要求,会去全盘清理缓存,清理完了发现还是达不到你得要求,那么就返回失败!!!! 但是,我们的目的已经达成,就是要让他去清理全盘缓存
/**
* 一键清理
*
* @param view
*/
public void cleanAllCache(View view) {
try {
// 通过反射调用freeStorageAndNotify方法, 向系统申请内存
Method method = mPM.getClass().getMethod(
"freeStorageAndNotify", long.class,
IPackageDataObserver.class);
// 参数传Long最大值, 这样可以保证系统将所有app缓存清理掉
method.invoke(mPM, Long.MAX_VALUE, new IPackageDataObserver.Stub() {
@Override
public void onRemoveCompleted(String packageName,
boolean succeeded) throws RemoteException {
System.out.println("flag==" + succeeded);
System.out.println("packageName=" + packageName);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
-清理特定app缓存
查看Setting源码,分析清除缓存按钮的逻辑
实现代码:
/**
* 删除单个文件的缓存 需要权限:<uses-permission
* android:name="android.permission.DELETE_CACHE_FILES"/>
*
* @param packageName
*/
private void deleteCache(String packageName) {
try {
Method method = mPM.getClass().getMethod(
"deleteApplicationCacheFiles", String.class,
IPackageDataObserver.class);
method.invoke(mPM, packageName, new IPackageDataObserver.Stub() {
@Override
public void onRemoveCompleted(String packageName,
boolean succeeded) throws RemoteException {
System.out.println("succeeded" + succeeded);
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
注意加权限: <uses-permission android:name="android.permission.DELETE_CACHE_FILES"/>
知识拓展:
加上权限后仍然报错
- 跳转到某个系统应用界面清除缓存
1. 看一下腾讯管家跳转到系统应用界面时的日志,并且对于Settings源代码说明意图;
2. 代码实现:
//启动到某个系统应用页面
Intent intent = new Intent();
intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
intent.addCategory(Intent.CATEGORY_DEFAULT);//有无没影响
intent.setData(Uri.parse("package:"+cacheInfo.packName));
startActivity(intent);
- 流量统计
- 流量统计介绍, pc网络连接流量展示(已发送,已接受)
- 连接真机,查看文件proc/uid_stat,发现该目录下有很多以uid命名的文件夹
- 用户id是安装应用程序的时候 操作系统赋给应用程序的
- 获取uid方式
1. 进入AppInfoProvider,
String name = packInfo.applicationInfo.loadLabel(pm).toString()+ packInfo.applicationInfo.uid;
2. 将应用名称和uid拼成一个字符串输出,真机查看主流应用(微信,QQ)的uid
3. 例如 QQ:10083, 进入QQ(10083)目录命令:cd 10083
4. 进入QQ10083:cd 10110
下载:168251
上传:23544
tcp_rcv :代码下载的数据
tcp_snd:代表上传的数据
- 手机安装360, 验证流量准确性
- 创建TrafficeManagerActivity
- 流量统计的api介绍
TrafficStats.getMobileRxBytes();// 3g/2g下载总流量
TrafficStats.getMobileTxBytes();// 3g/2g上传总流量
TrafficStats.getTotalRxBytes();// wifi+手机下载流量
TrafficStats.getTotalTxBytes();// wifi+手机上传总流量
TrafficStats.getUidRxBytes(10085);// 某个应用下载流量
TrafficStats.getUidTxBytes(10085);// 某个应用上传流量
这里需要注意的是,通过 TrafficStats 获取的数据在手机重启的时候会被清空,所以,如果要对流量进行持续的统计需要将数据保存到数据库中,在手机重启时将数据读出进行累加即可
- 流量报警原理简介
流量校准的工作原理就给运营商发短信
A:开启超额提醒
B:设置每月流量套餐300MB
C:自动校准流量-流量短信设置
D:演示发短信给运营商;
- 联网防火墙简介
在linux上有一款强大的防火墙软件iptable
360就是把这款软件内置了
如果手机有root权限,把防火墙软件装到手机的内部,并且开启起来。
以后就可以拦截某个应用程序的联网了。
如果允许某个软件上网就什么也不做。如果不允许某个软件上网,就把这个软件的所有的联网操作都定向到本地,这时就不会产生流量了。
- Android下的开源防火墙项目droidwall
登录code.google.com,搜索droidwall
svn地址: https://droidwall.googlecode.com/svn/
svn检出, 需要翻墙
常用开源代码网站: github.com, code.google.com
- 抽屉效果 SlidingDrawer
- 基本实现
<SlidingDrawer
android:id="@+id/slidingDrawer1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:content="@+id/content"
android:handle="@+id/handle" >
//指定抽屉把手
<ImageView
android:id="@id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/lock" />
//指定抽屉内容
<LinearLayout
android:id="@id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#9e9e9e"
android:gravity="center" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是小抽屉" />
</LinearLayout>
</SlidingDrawer>
- 把抽屉做成从右向左拉
android:orientation="horizontal"
- 实现腾讯抽屉竖直方向显示一小半功能
只需在抽屉上方增加一个空view
<View
android:layout_width="match_parent"
android:layout_height="200dp" />
- 水平方向显示一小半
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<View
android:layout_width="100dp"
android:layout_height="match_parent" />
<SlidingDrawer
android:id="@+id/slidingDrawer1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:content="@+id/content"
android:handle="@+id/handle" >
<ImageView
android:id="@id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/lock" />
<LinearLayout
android:id="@id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#9e9e9e"
android:gravity="center" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是小抽屉" />
</LinearLayout>
</SlidingDrawer>
</LinearLayout>
- 小锁图片显示上面
<LinearLayout
android:id="@id/handle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/lock" />
</LinearLayout>
- TabActivity的使用(知识拓展)
- 创建CleanActivity, 继承TabActivity
- 编写布局文件
<?xml version="1.0" encoding="utf-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
//内容体
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
</FrameLayout>
//标签体
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</TabWidget>
</LinearLayout>
</TabHost>
------------------------------------
/**
* 缓存清理主页面
*
* @author Kevin
*
*/
public class CleanActivity extends TabActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_clean_base);
TabHost host = getTabHost();
TabSpec tab1 = host.newTabSpec("缓存清理").setIndicator("缓存清理");
TabSpec tab2 = host.newTabSpec("SD卡清理").setIndicator("SD卡清理");
tab1.setContent(new Intent(this, CleanCacheActivity.class));
tab2.setContent(new Intent(this, CleanSdcardActivity.class));
host.addTab(tab1);
host.addTab(tab2);
}
}
- sdcard清理
- 查看金山缓存文件夹数据库clearpath.db
- 原理介绍
1. 查询数据库中的所有缓存文件目录
2. 如果文件夹存在, 执行删除操作
- 自定义Application
- 写一个demo
/**
* 自定义全局Application 应用全局的初始化逻辑可以放在此处运行
* 这是一个单例类, 整个应用只有一个
* @author Kevin
*/
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
System.out.println("MyApplication onCreate");
}
public void doSomething() {
System.out.println("doSomething....");
}
}
----------------------
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println("MainActivity onCreate");
MyApplication app = (MyApplication) getApplication();// 获取自定义的Application
app.doSomething();
}
}
----------------------
清单文件中配置
<application
android:name=".MyApplication"
- 手机卫士自定义Application
MobileSafeApplication
- 全局捕获异常
- 模拟异常, 比如int i = 1/0, 演示崩溃情况
- 代码实现
/**
* 自定义全局Application
*
* @author Kevin
*
*/
public class MobileSafeApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 设置未捕获异常处理器
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
}
class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
// 未捕获的异常都会走到此方法中
// Throwable是Exception和Error的父类
@Override
public void uncaughtException(Thread thread, Throwable ex) {
System.out.println("产生了一个未处理的异常, 但是被哥捕获了...");
// 将异常日志输入到本地文件中, 找机会上传到服务器,供技术人员分析
File file = new File(Environment.getExternalStorageDirectory(),
"error.log");
try {
PrintWriter writer = new PrintWriter(file);
ex.printStackTrace(writer);
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
// 结束当前进程
android.os.Process.killProcess(android.os.Process.myPid());
}
}
}
- 代码混淆
- 代码未混淆的前提下,打包,并进行反编译, 发现源码都可以看到, 很不安全
- 找到项目根目录下的文件project.properties, 打开混淆注释
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
- 分析文件proguard-android.txt
- 将proguard-android.txt文件拷贝到项目根目录,方便以后修改
proguard.config=proguard-android.txt:proguard-project.txt
- 重新打包并反编译,查看效果
- 结论: 混淆后,会将类名,方法名编译成a,b,c,d等混乱的字母, 提高代码阅读成本,增强安全性
- 嵌入广告
- 分析app, 广告公司, 广告平台的关系
广告平台相当于中间商, 是app和广告公司的媒介, 抽成盈利
- 盈利方式
- 展示次数, 1000次 1毛5左右 , 1分钟展示3条广告
- 点击次数, 1次 1毛5左右
- 有效点击, 1次 1元左右
- 广告公司
有米, 百度, 360, 万普, panda
- 国外广告公司
StartApp