svn#
svn checkout
svn update
svn commit
svn add
student
123
http://192.168.20.87/svn/Mobliesafe75
spalsh界面的作用#
1 卖弄 logo界面 展示次数 品牌效应
2 项目初始化 数据库copy
3 提醒用户更新版本
4 广告的展示
小步快跑 一个月做出beta版app,抢占市场,一个星期更新一个版本,10版本积攒人气,11版本盈利
代码包结构划分方式#
目的:方便后期维护和更新的项目
1.按照代码类型划分
2.按照业务类型划分
业务模块比较独立
黑马 办公软件 www.itheima.com
教学模块 com.itheima.teach
招生模块 com.itheima.student
财务模块 com.itheima.money
spalsh界面搭建# (重点)
1.textview的阴影效果
<!-- layout_centerInParent:在父控件的中间位置
shadow
shadowColor : 设置阴影颜色
shadowDx : 关于x轴的偏移量
shadowRadius : 偏移的角度
-->
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="版本号:1.0"
android:shadowColor="#ffff00"
android:shadowDx="1"
android:shadowDy="1"
android:shadowRadius="2"
/>
2.去除标题栏
1.在values目录的styles.xml文件中增加属性
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<!-- 去除标题栏 -->
<item name="android:windowNoTitle">true</item>
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>
2.在清单文件中的application中设置theme属性是我们工程的AppTheme
android:theme="@style/AppTheme"
获取版本号# (重点)
id : 驼峰式命名: 控件类型_控件的所在位置_控件表示的逻辑内容
/**
* 获取当前应用程序的版本号
* @return
*/
private String getVersionName(){
//包的管理者,获取清单文件中的所有信息
PackageManager pm = getPackageManager();
try {
//根据包名获取清单文件中的信息,其实就是返回一个保存有清单文件信息的javabean
//packageName :应用程序的包名
//flags : 指定信息的标签,0:获取基础的信息,比如包名,版本号,要想获取权限等等信息,必须通过标签来指定,才能去获取
//GET_PERMISSIONS : 标签的含义:处理获取基础信息之外,还会额外获取权限的信息
//getPackageName() : 获取当前应用程序的包名
PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);
//获取版本号名称
String versionName = packageInfo.versionName;
return versionName;
} catch (NameNotFoundException e) {
//包名找不到的异常
e.printStackTrace();
}
return null;
}
连接服务器#
/**
* 提醒用户更新版本
*/
private void update() {
//1.连接服务器,查看是否有最新版本, 联网操作,耗时操作,4.0以后不允许在主线程中执行的,放到子线程中执行
new Thread(){
public void run() {
try {
//1.1连接服务器
//1.1.1设置连接路径
//spec:连接路径
URL url = new URL("xxxxx");
//1.1.2获取连接操作
HttpURLConnection conn = (HttpURLConnection) url.openConnection();//http协议,httpClient
//1.1.3设置超时时间
conn.setConnectTimeout(5000);//设置连接超时时间
//conn.setReadTimeout(5000);//设置读取超时时间
//1.1.4设置请求方式
conn.setRequestMethod("GET");//post
//1.1.5获取服务器返回的状态码,200,404,500
int responseCode = conn.getResponseCode();
if (responseCode == 200) {
//连接成功
}else{
//连接失败
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
};
}.start();
}
权限:<uses-permission android:name="android.permission.INTERNET"/>
数据封装形式# (重点)
1.xml
2.json
json封装形式:{"code":"2.0","apkurl":"xxxxx","des":"新版本上线了,快来下载吧!!!!"}
json : utf-8无bom格式 Notepad++
editplus : utf-8 (选择) utf-8+bom
解析json数据# (重点)
1.获取服务器返回的流信息
//获取服务器返回的流信息
InputStream in = conn.getInputStream();
2.将流信息转化成字符串
public class StreamUtil {
/**
* 将流信息转化成字符串
* @return
* @throws IOException
*/
public static String parserStreamUtil(InputStream in) throws IOException{
//字符流,读取流
BufferedReader br = new BufferedReader(new InputStreamReader(in));
//写入流
StringWriter sw = new StringWriter();
//读写操作
//数据缓冲区
String str = null;
while((str = br.readLine()) !=null){
//写入操作
sw.write(str);
}
//关流
sw.close();
br.close();
return sw.toString();
}
}
3.解析json数据
//将获取到的流信息转化成字符串
String json = StreamUtil.parserStreamUtil(in);
//解析json数据
JSONObject jsonObject = new JSONObject(json);
4.获取数据
//获取数据
code = jsonObject.getString("code");
apkurl = jsonObject.getString("apkurl");
des = jsonObject.getString("des");
弹出对话框#
1.因为在子线程不能更新主线程ui所以给handler发送一个,在handler中弹出对话框
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_UPDATE_DIALOG:
//弹出对话框
showdialog();
break;
}
};
};
2.弹出对话框
/**
* 弹出对话框
*/
protected void showdialog() {
AlertDialog.Builder builder = new Builder(this);
//设置对话框不能消失
builder.setCancelable(false);
//设置对话框的标题
builder.setTitle("新版本:"+code);
//设置对话框的图标
builder.setIcon(R.drawable.ic_launcher);
//设置对话框的描述信息
builder.setMessage(des);
//设置升级取消按钮
builder.setPositiveButton("升级", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
//显示对话框
//builder.create().show();//两种方式效果一样
builder.show();
}
对话框细节的处理#
1.实现了取消按钮的操作
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//1.隐藏对话框
dialog.dismiss();
//2.跳转到主界面
enterHome();
}
});
2.处理了在跳转到主界面,从主界面点击返回键返回的时候,返回splash界面的问题,解决:在跳转的是将splash界面移出
/**
* 跳转到主界面
*/
protected void enterHome() {
Intent intent = new Intent(this,HomeActivity.class);
startActivity(intent);
//移出splash界面
finish();
}
3.处理对话框延迟显示的问题
//处理连接外网连接时间的问题
//在连接成功之后在去获取一个时间
int endTime = (int) System.currentTimeMillis();
//比较两个时间的时间差,如果小于两秒,睡两秒,大于两秒,不睡
int dTime = endTime-startTime;
if (dTime<2000) {
//睡两秒钟
SystemClock.sleep(2000-dTime);//始终都是睡两秒钟的时间
}
下载操作# (重点)
/**
* 3.下载最新版本
*/
protected void download() {
HttpUtils httpUtils = new HttpUtils();
//判断SD卡是否挂载
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//url : 新版本下载的路径 apkurl
//target : 保存新版本的目录
//callback : RequestCallBack
httpUtils.download(apkurl, "/mnt/sdcard/mobliesafe75_2.apk", new RequestCallBack<File>() {
//现在成功调用的方法
@Override
public void onSuccess(ResponseInfo<File> arg0) {
// TODO Auto-generated method stub
}
//下载失败调用的方法
@Override
public void onFailure(HttpException arg0, String arg1) {
// TODO Auto-generated method stub
}
//显示当前下载进度操作
//total : 下载总进度
//current : 下载的当前进度
//isUploading : 是否支持断点续传
@Override
public void onLoading(long total, long current, boolean isUploading) {
super.onLoading(total, current, isUploading);
//设置显示下载进度的textview可见,同时设置相应的下载进度
tv_spalsh_plan.setVisibility(View.VISIBLE);//设置控件是否可见
tv_spalsh_plan.setText(current+"/"+total);//110/200
}
});
}
}
注意问题:
1.修改apkurl地址
2.增加sd操作权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
3.判断sd是否挂载
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {}
4.生成2.0版本的apk
工程的bin目录下去找
a.将清单文件中的版本号改成2.0
b.重新运行
c.拷贝bin目录下的文件
安装新的apk# (重点)
/**
* 4.安装最新的版本
*/
protected void installAPK() {
/**
* <intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="content" /> //content : 从内容提供者中获取数据 content://
<data android:scheme="file" /> // file : 从文件中获取数据
<data android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
*/
Intent intent = new Intent();
intent.setAction("android.intent.action.VIEW");
intent.addCategory("android.intent.category.DEFAULT");
//单独设置会相互覆盖
/*intent.setType("application/vnd.android.package-archive");
intent.setData(Uri.fromFile(new File("/mnt/sdcard/mobliesafe75_2.apk")));*/
intent.setDataAndType(Uri.fromFile(new File("/mnt/sdcard/mobliesafe75_2.apk")), "application/vnd.android.package-archive");
//在当前activity退出的时候,会调用之前的activity的onActivityResult方法
//requestCode : 请求码,用来标示是从哪个activity跳转过来
//ABC a -> c b-> c ,c区分intent是从哪个activity传递过来的,这时候就要用到请求码
startActivityForResult(intent, 0);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
enterHome();
}
异常处理#
根据不同的异常,采用不同方式处理,方便项目后期定位异常,便于解决维护异常
两种上下文的区别#
1.getApplicationContext() 返回Context
2.activity.this 代表的就是当前的activity,继承context,父类当中有的方法子类中一定有,子类中有的方法父类中不一定有,在用getApplicationContext()一定能使用activity.this,但是能用activity.this不一定能使用getApplicationContext()
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application 没有添加窗口,
在使用对话框的时候,必须告诉对话框,要挂载在那个activity,也是告诉对话框应用在那个activity中显示
在使用跟对话框相关的操作的时候必须使用activity.this不能使用getApplicationContext()
getContext() : 返回Context ,一般用在单元测试中或者是自定义控件,其实和getApplicationContext()一样操作
打包签名# (重点!!!!!)
只有打包签名过的应用才能上传应用市场上去
应用升级条件
1.包名一致
2.签名一致
微信 com.weixin
流氓程序 com.weixin
注意:签名千万不能丢的,密码也不能忘,丢失用户
解决办法:将原应用下架,重新打包签名上架, 应用市场会有一模一样的应用, 丢失一部分用户
Day02##
Gridview使用#
1.布局文件中用法,其实跟listview相似
<!-- numColumns : 设置每行显示的个数
verticalSpacing : 设置每行之间的距离
-->
<GridView
android:id="@+id/gv_home_gridview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="3"
android:verticalSpacing="10dp"
android:layout_marginTop="20dp"
></GridView>
2.代码中的用法,其实跟listview相似
a.初始化控件
gv_home_gridview = (GridView) findViewById(R.id.gv_home_gridview);
gv_home_gridview.setAdapter(new Myadapter());
b.adapter
// 设置条目的样式
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//position代表是条目的位置,从0开始 0-8
//将布局文件转化成view对象
View view = View.inflate(getApplicationContext(), R.layout.item_home, null);
//每个条目的样式都不一样,初始化控件,去设置控件的值
//view.findViewById是从item_home布局文件中找控件,findViewById是从activity_home中找控件
ImageView iv_itemhome_icon = (ImageView)view.findViewById(R.id.iv_itemhome_icon);
TextView tv_itemhome_text = (TextView) view.findViewById(R.id.tv_itemhome_text);
//设置控件的值
iv_itemhome_icon.setImageResource(imageId[position]);//给imageview设置图片,根据条目的位置从图片数组中获取相应的图片
tv_itemhome_text.setText(names[position]);
return view;
}
Textview滚动效果#
1.第一种方式
<!--
singleLine : 一行显示
ellipsize
none :省略后面文字
start : 隐藏前面的文字
middle : 隐藏中间的文字
end : 隐藏后面的文字
marquee : 滚动
focusableInTouchMode : 触摸获取焦点
TextView天生是没有点击事件和获取焦点的事件
focusable : 是否获取焦点操作,true:可以 false:不可以
marqueeRepeatLimit : 设置滚动次数,marquee_forever : -1 一直滚动
-->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="手机卫士,真64杀毒引擎,超神速度,打开7次可以召唤神龙,辅助杀毒!!!"
android:singleLine="true"
android:ellipsize="marquee"
android:focusableInTouchMode="true"
android:focusable="true"
android:marqueeRepeatLimit="marquee_forever"
/>
2.第二种方式,自定义一个textview,让textview自动获取焦点
a.创建自定义控件,继承textview
public class HomeTextView extends TextView {
//在代码中使用的调用
public HomeTextView(Context context) {
super(context);
}
//在布局文件中使用的时候调用,比两个参数多了样式
public HomeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
//在布局文件中使用的时候调用
//布局文件中的控件都是可以用代码来表示
//AttributeSet : 保存了控件在布局文件中的所有属性
public HomeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
//设置自定义的textview自动获取焦点
//是否获取焦点
@Override
public boolean isFocused() {
//true:获取焦点,false:不获取焦点
return true;
}
}
b.再布局文件中使用
<com.itheima.mobliesafe75.ui.HomeTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="手机卫士,真64杀毒引擎,超神速度,打开7次可以召唤神龙,辅助杀毒!!!"
android:singleLine="true"
android:ellipsize="marquee"
android:focusableInTouchMode="true"
android:marqueeRepeatLimit="marquee_forever"
/>
设置中心界面#
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/tv_setting_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="提示更新"
android:layout_margin="5dp"
android:textSize="18sp"
/>
<!-- layout_below : 在某个控件的下方
layout_marginLeft : 距离左边距离
-->
<TextView
android:id="@+id/tv_setting_des"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="关闭提示更新"
android:textSize="16sp"
android:layout_below="@+id/tv_setting_title"
android:layout_marginLeft="5dp"
android:textColor="#aa000000"
/>
<CheckBox
android:id="@+id/checkBox1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="17dp"
/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="#77000000"
android:layout_below="@+id/tv_setting_des"
android:layout_marginTop="5dp"
/>
</RelativeLayout>
自定义组合控件#
1.创建自定义控件继承RelativeLayout
public class SettingView extends RelativeLayout {
2.创建一个init方法,并在每个构造函数中调用
/**
* 添加控件
*/
private void init(){
//添加布局文件
// TextView textView = new TextView(getContext());
// textView.setText("我是自定义组合控件的textview");
//第一种方式
//将布局文件转化成view对象
// View view = View.inflate(getContext(), R.layout.settingview, null);//爹有了,去找孩子,亲生
// //添加操作
// this.addView(view);//在自定义组合控件中添加一个textview
//第二种方式
//获取view对象,同时给veiw对象设置父控件,相当于先创建一个view对象,在把控件放到自定义控件中
View.inflate(getContext(), R.layout.settingview, this);//孩子有了,去找爹,喜当爹
}
屏蔽自动更新#
1.将自定义组合控件移植到手机卫士中
2.为了方便修改自定义控件中的控件的值,所以在自定义控件中创建一些方法
//需要添加一些方法,使程序员能方便的去改变自定义控件中的控件的值
/**
* 设置标题的方法
*/
public void setTitle(String title){
tv_setting_title.setText(title);
}
/**
* 设置描述信息的方法
*/
public void setDes(String des){
tv_setting_des.setText(des);
}
/**
* 设置checkbox状态
*/
public void setChecked(boolean isChecked){
//设置checkbox的状态
cb_setting_update.setChecked(isChecked);
}
/**
* 获取checkbox的状态
*/
public boolean isChecked(){
return cb_setting_update.isChecked();//获取checkbox的状态
}
3.到activity中去使用这些方法
sv_setting_update = (SettingView) findViewById(R.id.sv_setting_update);
//初始化自定义控件中各个控件的值
sv_setting_update.setTitle("提示更新");
sv_setting_update.setDes("打开提示更新");
sv_setting_update.setChecked(true);
//设置自定义组合控件的点击事件
//问题1:点击checkbox发现描述信息没有改变,原因:因为checkbox天生是有点击事件和获取焦点事件,当点击checkbox,这个checkbox就会执行他的点击事件
//而不会执行条目的点击事件
//问题2:没有保存用户点击操作
sv_setting_update.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//更改状态
//根据checkbox之前的状态来改变checkbox的状态
if (sv_setting_update.isChecked()) {
//关闭提示更新
sv_setting_update.setDes("关闭提示更新");
sv_setting_update.setChecked(false);
}else{
//打开提示更新
sv_setting_update.setDes("打开提示更新");
sv_setting_update.setChecked(true);
}
}
});
4.解决两个问题
问题1:点击checkbox发现描述信息没有改变,原因:因为checkbox天生是有点击事件和获取焦点事件,当点击checkbox,这个checkbox就会执行他的点击事件
<!--
focusable : 是否可以获取焦点,true:可以,false:不可以
clickable : 是否可以点击 true:可以,false:不可以 -->
<CheckBox
android:id="@+id/cb_setting_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="17dp"
android:focusable="false"
android:clickable="false"
/>
问题2:没有保存用户点击操作
//name : 保存信息的文件的名称
//mode : 权限
sp = getSharedPreferences("config", MODE_PRIVATE);
sv_setting_update = (SettingView) findViewById(R.id.sv_setting_update);
//初始化自定义控件中各个控件的值
sv_setting_update.setTitle("提示更新");
//defValue : 缺省的值
if (sp.getBoolean("update", true)) {
sv_setting_update.setDes("打开提示更新");
sv_setting_update.setChecked(true);
}else{
sv_setting_update.setDes("关闭提示更新");
sv_setting_update.setChecked(false);
}
//设置自定义组合控件的点击事件
//问题1:点击checkbox发现描述信息没有改变,原因:因为checkbox天生是有点击事件和获取焦点事件,当点击checkbox,这个checkbox就会执行他的点击事件
//而不会执行条目的点击事件
//问题2:没有保存用户点击操作
sv_setting_update.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Editor edit = sp.edit();
//更改状态
//根据checkbox之前的状态来改变checkbox的状态
if (sv_setting_update.isChecked()) {
//关闭提示更新
sv_setting_update.setDes("关闭提示更新");
sv_setting_update.setChecked(false);
//保存状态
edit.putBoolean("update", false);
//edit.apply();//保存到文件中,但是仅限于9版本之上,9版本之下保存到内存中的
}else{
//打开提示更新
sv_setting_update.setDes("打开提示更新");
sv_setting_update.setChecked(true);
//保存状态
edit.putBoolean("update", true);
}
edit.commit();
}
});
}
5.屏蔽自动更新
if (sp.getBoolean("update", true)) {
update();
}else{
//跳转到主界面
//不能让主线程去睡两秒钟
//主线程是有渲染界面的操作,主线程睡两秒钟就没有办法去渲染界面
new Thread(){
public void run() {
SystemClock.sleep(2000);
enterHome();
};
}.start();
}
Day03##
自定义属性#
1.在values->attrs.xml
<resources>
<declare-styleable name="com.itheima.mobliesafe75.ui.SettingView">
<attr name="title" format="string" /><!-- name:属性的名称,format:类型 -->
<attr name="des_on" format="string" />
<attr name="des_off" format="string" />
</declare-styleable>
</resources>
2.在布局中使用
a.命名空间
xmlns:itheima="http://schemas.android.com/apk/res/com.itheima.mobliesafe75"
b.控件中使用
<com.itheima.mobliesafe75.ui.SettingView
android:id="@+id/sv_setting_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
itheima:title="提示更新"
itheima:des_on="打开提示更新"
itheima:des_off="关闭提示更新"
></com.itheima.mobliesafe75.ui.SettingView>
3.代码中使用
String title = attrs.getAttributeValue("http://schemas.android.com/apk/res/com.itheima.mobliesafe75", "title");
des_on = attrs.getAttributeValue("http://schemas.android.com/apk/res/com.itheima.mobliesafe75", "des_on");
des_off = attrs.getAttributeValue("http://schemas.android.com/apk/res/com.itheima.mobliesafe75", "des_off");
//给自定义组合控件的控件设置相应的值
//初始化控件的值
tv_setting_title.setText(title);
if (isChecked()) {
tv_setting_des.setText(des_on);
}else{
tv_setting_des.setText(des_off);
}
/**
* 设置checkbox状态
*/
public void setChecked(boolean isChecked){
//设置checkbox的状态
cb_setting_update.setChecked(isChecked);
//其实就是把sv_setting_update.setDes("打开提示更新");封装到了setChecked方法中
if (isChecked()) {
tv_setting_des.setText(des_on);
}else{
tv_setting_des.setText(des_off);
}
}
4.修改settingactivity中的操作
//sv_setting_update.setTitle("提示更新");
//defValue : 缺省的值
if (sp.getBoolean("update", true)) {
//sv_setting_update.setDes("打开提示更新");
sv_setting_update.setChecked(true);
}else{
//sv_setting_update.setDes("关闭提示更新");
sv_setting_update.setChecked(false);
}
设置密码对话框布局# (重点)
/**
* 设置密码对话框
*/
protected void showSetPassWordDialog() {
AlertDialog.Builder builder = new Builder(this);
//设置对话框不能消息
builder.setCancelable(false);
//将布局文件转化成view对象
View view = View.inflate(getApplicationContext(), R.layout.dialog_setpassword, null);
EditText et_setpassword_password = (EditText) view.findViewById(R.id.et_setpassword_password);
EditText et_setpassword_confrim = (EditText) view.findViewById(R.id.et_setpassword_confrim);
Button btn_ok = (Button) view.findViewById(R.id.btn_ok);
Button btn_cancle = (Button) view.findViewById(R.id.btn_cancle);
//设置确定,取消按钮的点击事件
btn_cancle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//隐藏对话框
dialog.dismiss();
}
});
builder.setView(view);
//显示对话框
//builder.show();
dialog = builder.create();
dialog.show();
}
设置密码操作#
btn_ok.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//设置密码
//1.获取密码输入框输入的内容
String password = et_setpassword_password.getText().toString().trim();
//2.判断密码是否为空
if (TextUtils.isEmpty(password)) {//null :没有内存 "":有内存地址但是没有内容
Toast.makeText(getApplicationContext(), "请输入密码", 0).show();
return;
}
//3.获取确认密码
String confrim_password = et_setpassword_confrim.getText().toString().trim();
//4.判断两次密码是否一致
if (password.equals(confrim_password)) {
//保存密码,sp
Editor edit = sp.edit();
edit.putString("password", password);
edit.commit();
//隐藏对话框
dialog.dismiss();
//提醒用户
Toast.makeText(getApplicationContext(), "密码设置成功", 0).show();
}else{
Toast.makeText(getApplicationContext(), "两次密码输入不一致", 0).show();
}
}
});
输入密码对话框#
/**
* 输入密码对话框
*/
protected void showEnterPasswordDialog() {
//第一步:复制布局
AlertDialog.Builder builder = new Builder(this);
//设置对话框不能消息
builder.setCancelable(false);
//将布局文件转化成view对象
View view = View.inflate(getApplicationContext(), R.layout.dialog_enterpassword, null);
//第三步:复制初始化控件及功能实现
final EditText et_setpassword_password = (EditText) view.findViewById(R.id.et_setpassword_password);
Button btn_ok = (Button) view.findViewById(R.id.btn_ok);
Button btn_cancle = (Button) view.findViewById(R.id.btn_cancle);
btn_ok.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//判断密码输入是否正确
//1.获取输入的密码
String password = et_setpassword_password.getText().toString().trim();
//2.判断密码是否为空
if (TextUtils.isEmpty(password)) {
Toast.makeText(getApplicationContext(), "请输入密码", 0).show();
return;
}
//3.获取保存的密码
String sp_password = sp.getString("password", "");
//4.判断两个密码是否一致
if (password.equals(sp_password)) {
//跳转到到手机防盗界面
//隐藏对话框
dialog.dismiss();
//提醒用户
Toast.makeText(getApplicationContext(), "密码正确", 0).show();
}else{
Toast.makeText(getApplicationContext(), "密码错误", 0).show();
}
}
});
btn_cancle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//隐藏对话框
dialog.dismiss();
}
});
//第二步:复制显示
builder.setView(view);
//显示对话框
//builder.show();
dialog = builder.create();
dialog.show();
}
兼容低版本#
根据具体显示风格,去进行修改
MD5加密# (重点)
md5加密:明文转化成密文之后,密文是不能转化成明文
/**
* MD5加密
* @return
*/
public static String passwordMD5(String password){
StringBuilder sb = new StringBuilder();
try {
//1.获取数据摘要器
//arg0 : 加密的方式
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
//2.将一个byte数组进行加密,返回的是一个加密过的byte数组,二进制的哈希计算,md5加密的第一步
byte[] digest = messageDigest.digest(password.getBytes());
//3.遍历byte数组
for (int i = 0; i < digest.length; i++) {
//4.MD5加密
//byte值 -128-127
int result = digest[i] & 0xff;
//将得到int类型转化成16进制字符串
//String hexString = Integer.toHexString(result)+1;//不规则加密,加盐
String hexString = Integer.toHexString(result);
if (hexString.length() < 2) {
// System.out.print("0");
sb.append("0");
}
//System.out.println(hexString);
//e10adc3949ba59abbe56e057f20f883e
sb.append(hexString);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
//找不到加密方式的异常
e.printStackTrace();
}
return null;
}
隐藏显示密码效果#
1.添加隐藏显示按钮
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<!-- layout_weight : 设置渲染优先级别,值越大优先级越低,优先级低的后渲染
inputType : 设置输入框输入类型
-->
<EditText
android:id="@+id/et_setpassword_password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:inputType="textPassword"
android:textColor="#000000"
android:hint="请输入密码"
>
</EditText>
<ImageView
android:id="@+id/iv_enterpassword_hide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/btn_circle_pressed"
android:layout_gravity="center_vertical"
/>
</LinearLayout>
2.代码中进行操作
int count=0;
ImageView iv_enterpassword_hide = (ImageView) view.findViewById(R.id.iv_enterpassword_hide);
iv_enterpassword_hide.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//隐藏显示密码
if (count%2 == 0) {
//显示密码
et_setpassword_password.setInputType(0);
}else{
//隐藏密码
et_setpassword_password.setInputType(129);//代码设置输入框输入类型
}
count++;
}
});
界面跳转逻辑# (重点:逻辑)
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sp = getSharedPreferences("config", MODE_PRIVATE);
//分为两部分1.显示设置过的手机防盗功能,2.设置手机防盗功能
//判断用户是否是第一次进入手机防盗模块,是,跳转到设置向导界面,不是,跳转防盗功能显示界面
if (sp.getBoolean("first", true)) {
//第一次进入,跳转到手机防盗设置向导界面
Intent intent = new Intent(this,SetUp1Activity.class);
startActivity(intent);
//移出lostfindActivity
finish();
}else{
//手机防盗显示界面
setContentView(R.layout.activity_lostfind);
}
}
设置向导第一个界面#
<!-- layout_margin : 距离上下左右的距离 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="您的手机防盗卫士:"
android:layout_margin="5dp"
android:textSize="18sp"
/>
<!-- drawableLeft : 是在文本的左边设置一张图片
@android:drawable/star_big_on : 调用系统的图片,使用系统图片可以节省我们应用的体积
-->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="SIM卡变更报警"
android:textSize="18sp"
android:drawableLeft="@android:drawable/star_big_on"
android:gravity="center_vertical"
android:layout_marginLeft="5dp"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="GPS追踪"
android:textSize="18sp"
android:drawableLeft="@android:drawable/star_big_on"
android:gravity="center_vertical"
android:layout_marginLeft="5dp"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="远程数据销毁"
android:textSize="18sp"
android:drawableLeft="@android:drawable/star_big_on"
android:gravity="center_vertical"
android:layout_marginLeft="5dp"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="远程锁屏"
android:textSize="18sp"
android:drawableLeft="@android:drawable/star_big_on"
android:gravity="center_vertical"
android:layout_marginLeft="5dp"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/presence_online"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/presence_invisible"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/presence_invisible"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/presence_invisible"
/>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!-- layout_centerInParent : 在父控件的中间位置 -->
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/setup1"
android:layout_centerInParent="true"
/>
<!-- layout_alignParentRight : 在父控件的右方
layout_alignParentBottom : 在父控件的下方
-->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下一步"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
/>
</RelativeLayout>
状态选择器# (重点)
状态选择器:特殊图片,根据不同的状态显示不同的图片,比如按下,抬起
1.res->drawable -> button.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/function_greenbutton_pressed" /> <!-- pressed :按下的时候显示图片-->
<item android:drawable="@drawable/function_greenbutton_normal" /> <!-- default :默认显示图片-->
</selector>
2.使用
android:background="@drawable/button"
设置向导第二界面&抽取样式# (重点:抽取样式)
1.在styles.xml文件中
<style name="next">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:text">下一步</item>
<item name="android:layout_alignParentRight">true</item>
<item name="android:layout_alignParentBottom">true</item>
<item name="android:background">@drawable/button</item>
<item name="android:drawableRight">@drawable/next</item>
<item name="android:padding">5dp</item>
<item name="android:onClick">next</item>
</style>
2.使用
<Button
style="@style/next"
/>
第三个界面&.9图片# (重点:.9图片)
.9图片:为了防止图片拉伸变形
sdk\tools\draw9patch.bat
第四个界面#
<!-- layout_alignParentRight : 在父控件的右方
layout_alignParentBottom : 在父控件的下方
padding : 距离控件上下左右内边框的距离
要想将样式文件中的属性的值覆盖,在控件中使用相同的属性,并设置值就可以
-->
<Button
style="@style/next"
android:text="设置完成"
android:drawableRight="@null"
/>
抽取操作# (重点!!!!!!)
第一个抽取
1.创建父类
public abstract class SetUpBaseActivity extends Activity {
2.将子类中的上一步,下一步按钮点击事件抽取到父类中
//按钮点击事件的操作
//将每个界面中的上一步,下一步按钮操作,抽取到父类中
public void pre(View v){
pre_activity();
}
public void next(View v){
next_activity();
}
3.因为父类不知道子类上一步,下一步具体实现,所以创建两个抽象方法,让子类去实现,根据自己的特性去实现响应的操作
//因为父类不知道子类上一步,下一步具体的执行操作代码,所以要创建一个抽象方法,让子类实现这个抽象方法,根据自己的特性去实现相应的操作
//下一步的操作
public abstract void next_activity();
//上一步的操作
public abstract void pre_activity();
4.子类继承父类,实现抽象方法,根据自己的特性去进行相应的实现
public class SetUp2Activity extends SetUpBaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_setup2);
}
@Override
public void next_activity() {
// 跳转到第三个界面
Intent intent = new Intent(this, SetUp3Activity.class);
startActivity(intent);
finish();
}
@Override
public void pre_activity() {
// 跳转到第一个界面
Intent intent = new Intent(this, SetUp1Activity.class);
startActivity(intent);
finish();
}
}
第二个抽取:因为在进行第二个界面的时候,点击返回键会直接回到主界面,因为每个界面中都有返回键操作,所以向上抽取到父类进行统一的处理,子类就不去单独处理返回键的操作
//监听手机物理按钮的点击事件
//keyCode : 物理按钮的标示
//event : 按键的处理事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
//判断keycode是否是返回键的标示
if (keyCode == KeyEvent.KEYCODE_BACK) {
//true:是可以屏蔽按键的事件
//return true;
pre_activity();
}
return super.onKeyDown(keyCode, event);
}
Day04##
界面切换动画效果# (重点)
平移动画
1.res -> anim -> xxx.xml (translate)
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="100%"
android:toXDelta="0"
android:duration="500"
>
<!-- fromXDelta : 表示从x轴哪里开始移动
toXDelta : 移动到哪里
duration : 持续时间
-->
</translate>
2.调用
//执行平移动画
//执行界面切换动画的操作,是在startActivity或者finish之后执行
//enterAnim : 新的界面进入的动画
//exitAnim : 旧的界面退出的动画
overridePendingTransition(R.anim.setup_enter_next, R.anim.setup_exit_next);
手势识别器# (重点)
private GestureDetector gestureDetector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1.获取手势识别器
//要想让手势是识别器生效,必须将手势识别器注册到屏幕的触摸事件中
gestureDetector = new GestureDetector(this, new MyOnGestureListener());
}
//界面的触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
gestureDetector.onTouchEvent(event);
return super.onTouchEvent(event);
}
//base simple
private class MyOnGestureListener extends SimpleOnGestureListener{
//e1 : 按下的事件,保存有按下的坐标
//e2 : 抬起的事件,保存有抬起的坐标
//velocityX : velocity 速度 在x轴上移动的速率
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
//得到按下的x坐标
float startX = e1.getRawX();
//得到抬起的x坐标
float endX = e2.getRawX();
//得到按下的Y坐标
float startY = e1.getRawY();
//得到抬起的y坐标
float endY = e2.getRawY();
//判断是否是斜滑
if ((Math.abs(startY-endY)) > 50) {
Toast.makeText(getApplicationContext(), "你小子又乱滑了,别闹了....", 0).show();
return false;
}
//下一步
if ((startX-endX) > 100) {
next_activity();
}
//上一步
if ((endX-startX) > 100) {
pre_activity();
}
//true if the event is consumed, else false
//true : 事件执行 false:拦截事件,事件不执行
return true;
}
}
第四个界面设置完成操作#
@Override
public void next_activity() {
//保存用户第一次进入手机防盗模块设置向导的状态,frist
Editor edit = sp.edit();
edit.putBoolean("first", false);
edit.commit();
// 跳转到手机防盗页面
Intent intent = new Intent(this,LostfindActivity.class);
startActivity(intent);
finish();
}
shape资源# (重点)
1.res -> drawable -> xxx.xml (shape)
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"
>
<!-- shape : 设置图片的行状态 rectangle : 矩形 oval:椭圆 line:线 ring:环形-->
<!-- corners : 弧度 -->
<corners android:radius="10dp"/>
<!-- solid : 颜色 -->
<solid android:color="#ff0000"/>
<!-- stroke : 虚线 dashWidth : 点的宽度 dashGap : 点之间的距离
<stroke android:width="3dp" android:color="#00ff00" android:dashWidth="3dp" android:dashGap="5dp"/>
-->
<!-- gradient : 渐变 angle : 渐变的角度-->
<gradient android:startColor="#ff0000" android:centerColor="#00ff00" android:endColor="#0000ff"/>
</shape>
2.调用
android:background="@drawable/shape_drawable"
绑定SIM卡#
//设置回显操作
//根据保存的SIM卡去初始化控件状态,有保存SIM卡号就是绑定SIM卡,如果没有就是没有绑定SIM卡
if (TextUtils.isEmpty(sp.getString("sim", ""))) {
//没有绑定
sv_setup2_sim.setChecked(false);
}else{
//绑定SIM卡
sv_setup2_sim.setChecked(true);
}
sv_setup2_sim.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Editor edit = sp.edit();
//绑定SIM卡
//根据checkbox的状态设置描述信息的状态
//isChecked() : 获取之前checkbox的状态
if (sv_setup2_sim.isChecked()) {
//解绑
edit.putString("sim", "");
sv_setup2_sim.setChecked(false);
}else{
//绑定SIM卡
//获取SIM卡号
//电话的管理者
TelephonyManager tel = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
//tel.getLine1Number();//获取SIM卡绑定的电话号码 line1:双卡双待.在中国不太适用,运营商一般不会将SIM卡和手机号码绑定
String sim = tel.getSimSerialNumber();//获取SIM卡序列号,唯一标示
//保存SIM卡号
edit.putString("sim", sim);
sv_setup2_sim.setChecked(true);
}
edit.commit();
}
});
监听手机重启# (重点:广播接受者)
1.监听手机重启
a.创建一个广播接受者,接受者手机重启的广播事件
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("手机重启了......");
}
}
b.清单文件中配置
<receiver android:name="com.itheima.mobliesafe75.receiver.BootCompleteReceiver">
<!-- priority : 广播接受者的优先级,值越大优先级越高,越先接收到广播 -->
<intent-filter
android:priority="1000">
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
c.权限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
2.发送短信
a.发送短信
//发送报警短信
//短信的管理者
SmsManager smsManager = SmsManager.getDefault();
//destinationAddress : 收件人
//scAddress : 短信中心的地址 一般null
//text : 短信的内容
//sentIntent : 是否发送成功
//deliveryIntent : 短信的协议 一般null
smsManager.sendTextMessage("5556", null, "da ge wo bei dao le,help me!!!!", null, null);
b.权限
<uses-permission android:name="android.permission.SEND_SMS"/>
3.检查SIM卡是否发生变化
//1.获取保存的SIM卡号
String sp_sim = sp.getString("sim", "");
//2.再次获取本地SIM卡号
TelephonyManager tel = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
//tel.getLine1Number();//获取SIM卡绑定的电话号码 line1:双卡双待.在中国不太适用,运营商一般不会将SIM卡和手机号码绑定
String sim = tel.getSimSerialNumber();//获取SIM卡序列号,唯一标示
//3.判断两个SIM卡号是否为空
if (!TextUtils.isEmpty(sp_sim) && !TextUtils.isEmpty(sim)) {
//4.判断两个SIM卡是否一致,如果一致就不发送报警短信,不一致发送报警短信
if (!sp_sim.equals(sim)) {
//发送报警短信
//短信的管理者
SmsManager smsManager = SmsManager.getDefault();
//destinationAddress : 收件人
//scAddress : 短信中心的地址 一般null
//text : 短信的内容
//sentIntent : 是否发送成功
//deliveryIntent : 短信的协议 一般null
smsManager.sendTextMessage("5556", null, "da ge wo bei dao le,help me!!!!", null, null);
}
}
保存安全号码#
1.保存安全号码
//保存输入的安全号
//1.获取输入的安全号码
String safenum = et_setup3_safenum.getText().toString().trim();
//2.判断号码是否为空
if (TextUtils.isEmpty(safenum)) {
Toast.makeText(getApplicationContext(), "请输入安全号码", 0).show();
return;
}
//3.保存输入的安全号码
Editor edit = sp.edit();
edit.putString("safenum", safenum);
edit.commit();
2.回显操作
//回显操作
et_setup3_safenum.setText(sp.getString("safenum", ""));
获取联系人操作# (重点)
/**
* 获取系统联系人
* @return
*/
public static List<HashMap<String, String>> getAllContactInfo(Context context){
ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String,String>>();
//1.获取内容解析者
ContentResolver resolver = context.getContentResolver();
//2.获取内容提供者的地址:com.android.contacts www.baidu.com/jdk
//raw_contacts表的地址 :raw_contacts
//view_data表的地址 : data
//3.生成查询地址
Uri raw_uri = Uri.parse("content://com.android.contacts/raw_contacts");//http://
Uri date_uri = Uri.parse("content://com.android.contacts/data");
//4.查询操作,先查询raw_contacts,查询contact_id
//projection : 查询的字段
Cursor cursor = resolver.query(raw_uri, new String[]{"contact_id"}, null, null, null);
//5.解析cursor
while(cursor.moveToNext()){
//6.获取查询的数据
String contact_id = cursor.getString(0);
//cursor.getString(cursor.getColumnIndex("contact_id"));//getColumnIndex : 查询字段在cursor中索引值,一般都是用在查询字段比较多的时候
//7.根据contact_id查询view_data表中的数据
//selection : 查询条件
//selectionArgs :查询条件的参数
//sortOrder : 排序
Cursor c = resolver.query(date_uri, new String[]{"data1","mimetype"}, "raw_contact_id=?", new String[]{contact_id}, null);
HashMap<String, String> map = new HashMap<String, String>();
//8.解析c
while(c.moveToNext()){
//9.获取数据
String data1 = c.getString(0);
String mimetype = c.getString(1);
//10.根据类型去判断获取的data1数据并保存
if (mimetype.equals("vnd.android.cursor.item/phone_v2")) {
//电话
map.put("phone", data1);
}else if(mimetype.equals("vnd.android.cursor.item/name")){
//姓名
map.put("name", data1);
}
}
//11.添加到集合中数据
list.add(map);
//12.关闭cursor
c.close();
}
//12.关闭cursor
cursor.close();
return list;
}
权限:<uses-permission android:name="android.permission.READ_CONTACTS"/>
获取联系人测试#
1.创建一个测试工程
2.解决contact_id为null
if (!TextUtils.isEmpty(contact_id)) {//null ""
.......
}
选择联系人界面#
1.listview使用
<ListView
android:id="@+id/lv_contact_contacts"
android:layout_width="match_parent"
android:layout_height="match_parent"
></ListView>
2.代码中使用
lv_contact_contacts = (ListView) findViewById(R.id.lv_contact_contacts);
lv_contact_contacts.setAdapter(new Myadapter());
adapter
// 设置条目的样式
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = View.inflate(getApplicationContext(), R.layout.item_contact, null);
//初始化控件
//view.findViewById 是从item_contact找控件,findViewById是从activity_contacts找控件
TextView tv_itemcontact_name = (TextView) view.findViewById(R.id.tv_itemcontact_name);
TextView tv_itemcontact_phone = (TextView) view.findViewById(R.id.tv_itemcontact_phone);
//设置控件的值
tv_itemcontact_name.setText(list.get(position).get("name"));//根据条目的位置从list集合中获取相应的数据
tv_itemcontact_phone.setText(list.get(position).get("phone"));
return view;
}
选择联系人界面设置安全号码# (重点:两个activity之间的数据传递)
两个activity之间的数据传递
1.在setup3Activity中使用startActivityForResult(intent, 0);进行跳转
/**
* 选择联系人按钮
* @param v
*/
public void selectContacts(View v){
//跳转到选择联系人界面
Intent intent = new Intent(this,ContactActivity.class);
//当现在的activity退出的时候,会调用之前activity的onActivityResult方法
startActivityForResult(intent, 0);
}
同时重写setup3Activity的onActivityResult方法
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
2.在contactActivity中给listview设置条目点击事件,并传递数据
lv_contact_contacts.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//将点击联系人的号码传递给设置安全号码界面
Intent intent = new Intent();
intent.putExtra("num", list.get(position).get("phone"));
//将数据传递给设置安全号码界面
//设置结果的方法,会将结果传递给调用当前activity的activity
setResult(RESULT_OK, intent);
//移出界面
finish();
}
});
3.在setup3Activity的onActivityResult方法中接受数据
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (data!=null) {
//接受选择联系人界面传递过来的数据,null.方法 参数为null
String num = data.getStringExtra("num");
//将获取到的号码,设置给安全号码输入框
et_setup3_safenum.setText(num);
}
}
细节处理# (重点:耗时操作,注解初始化控件)
耗时操作的处理
1.将获取联系人的操作放到子线程中执行
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
lv_contact_contacts.setAdapter(new Myadapter());
};
};
new Thread(){
public void run() {
//获取联系人
list = ContactEngine.getAllContactInfo(getApplicationContext());
//获取完联系人的时候给handler发送一个消息,在handler中去setadapter
handler.sendEmptyMessage(0);
};
}.start();
2.解决界面加载数据时,空白页面展示问题,使用进度条
使用注解的方式初始化控件
a.在类的成员变量出去声明控件,并加上注解
@ViewInject(R.id.lv_contact_contacts)
private ListView lv_contact_contacts;
b.在oncreate方法中初始化操作
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contacts);
ViewUtils.inject(this);
处理空白界面问题,使用进度条
//在加载数据之前显示进度条
loading.setVisibility(View.VISIBLE);
new Thread(){}
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
lv_contact_contacts.setAdapter(new Myadapter());
//数据显示完成,隐藏进度条
loading.setVisibility(View.INVISIBLE);
};
};
Day05##
异步加载框架# (重点:面试必问)
handler机制
1.创建异步加载框架
public abstract class MyAsycnTaks {
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
postTask();
};
};
/**
* 在子线程之前执行的方法
*/
public abstract void preTask();
/**
* 在子线程之中执行的方法
*/
public abstract void doinBack();
/**
* 在子线程之后执行的方法
*/
public abstract void postTask();
/**
* 执行
*/
public void execute(){
preTask();
new Thread(){
public void run() {
doinBack();
handler.sendEmptyMessage(0);
};
}.start();
}
}
2.使用
//异步加载框架
new MyAsycnTaks() {
@Override
public void preTask() {
//在加载数据之前显示进度条,在子线程之前执行的操作
loading.setVisibility(View.VISIBLE);
}
@Override
public void postTask() {
//在子线程之后执行操作
lv_contact_contacts.setAdapter(new Myadapter());
//数据显示完成,隐藏进度条
loading.setVisibility(View.INVISIBLE);
}
@Override
public void doinBack() {
//获取联系人,在子线程之中执行的操作
list = ContactEngine.getAllContactInfo(getApplicationContext());
}
}.execute();
修改进度条样式#
android:indeterminateOnly : 是否显示进度条 true:不显示 false:显示
1.res->drawable -> xxx.xml
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/shenmabg"
android:pivotX="50%"
android:pivotY="50%"
/>
2.使用,indeterminateDrawable属性
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminateDrawable="@drawable/progressbar_drawable"
/>
防盗保护是否开启#
1.checkbox设置点击事件
//根据保存的用户状态进行回显操作
if (sp.getBoolean("protected", false)) {
//开始防盗保护
cb_setup4_protected.setText("您已经开启了防盗保护");
cb_setup4_protected.setChecked(true);//必须要写
}else{
//关闭防盗保护
cb_setup4_protected.setText("您还没有开启防盗保护");
cb_setup4_protected.setChecked(false);//必须要写
}
//设置checkbox点击事件
//当checkbox状态改变的时候调用
cb_setup4_protected.setOnCheckedChangeListener(new OnCheckedChangeListener() {
//CompoundButton : checkbox
//isChecked : 改变之后的值,点击之后的值
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Editor edit = sp.edit();
//根据checkbox的状态设置checkbox的信息
if (isChecked) {
//开始防盗保护
cb_setup4_protected.setText("您已经开启了防盗保护");
cb_setup4_protected.setChecked(true);//程序严谨性
//保存用户选中的状态
edit.putBoolean("protected", true);
}else{
//关闭防盗保护
cb_setup4_protected.setText("您还没有开启防盗保护");
cb_setup4_protected.setChecked(false);//程序严谨性
edit.putBoolean("protected", false);
}
edit.commit();
}
});
2.根据保存的防盗保护是否开启的状态,判断是否应该监听手机重启检测SIM卡是否变化发送报警短信的操作,在bootCompleteReceiver
if (sp.getBoolean("protected", false)) {
//检查SIM卡是否发生变化
.......
}
设置手机防盗页面安全号码和防盗保护状态#
TextView tv_lostfind_safenum = (TextView) findViewById(R.id.tv_lostfind_safenum);
ImageView tv_lostfind_protected = (ImageView) findViewById(R.id.tv_lostfind_protected);
//根据保存的安全号码和防盗保护状态进行设置
//设置安全号码
tv_lostfind_safenum.setText(sp.getString("safenum", ""));
//设置防盗保护是否开启状态
//获取保存的防盗保护状态
boolean b = sp.getBoolean("protected", false);
//根据获取防盗保护状态设置相应显示图片
if (b) {
//开启防盗保护
tv_lostfind_protected.setImageResource(R.drawable.lock);
}else{
//关闭防盗保护
tv_lostfind_protected.setImageResource(R.drawable.unlock);
}
接受指令操作# (重点:广播接受者)
1.服务器端推送一个指令,客户端接受者指令,叫做消息推送,心跳连接/长连接(费流量,费电),第三方消息推送sdk,极光推送,百度推送,xmpp,局限性:依赖于网络
2.接受解析短信的操作,解析短信的内容,如果是指令的话,就执行相应的操作,局限性:费钱
1.创建一个广播接受者
public class SmsReceiver extends BroadcastReceiver {
2.清单文件配置
<!-- 要想实现拦截操作,优先级必须大于0,小于0是系统先接受到短信,大于0是我们先接受到短信 -->
<receiver android:name="com.itheima.mobliesafe75.receiver.SmsReceiver">
<intent-filter android:priority="1000">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
3.接受解析短信
@Override
public void onReceive(Context context, Intent intent) {
//接受解析短信
//70汉字一条短信,71汉字两条短信
Object[] objs = (Object[]) intent.getExtras().get("pdus");
for(Object obj:objs){
//解析成SmsMessage
SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) obj);
String body = smsMessage.getMessageBody();//获取短信的内容
String sender = smsMessage.getOriginatingAddress();//获取发件人
System.out.println("发件人:"+sender+" 短信内容:"+body);
//真机测试,加发件人判断
//判断短信是哪个指令
if ("#*location*#".equals(body)) {
//GPS追踪
System.out.println("GPS追踪");
//拦截短信
abortBroadcast();//拦截操作,原生android系统,国产深度定制系统中屏蔽,比如小米
}else if("#*alarm*#".equals(body)){
//播放报警音乐
System.out.println("播放报警音乐");
abortBroadcast();//拦截操作,原生android系统,国产深度定制系统中屏蔽,比如小米
}else if("#*wipedata*#".equals(body)){
//远程删除数据
System.out.println("远程删除数据");
abortBroadcast();//拦截操作,原生android系统,国产深度定制系统中屏蔽,比如小米
}else if("#*lockscreen*#".equals(body)){
//远程锁屏
System.out.println("远程锁屏");
abortBroadcast();//拦截操作,原生android系统,国产深度定制系统中屏蔽,比如小米
}
}
}
}
定位方式#
gps一种定位方式
1.wifi定位,IP地址,根据你的IP地址获取你的地理位置,精确度不是特别高了
2.基站定位,基站就是为电话服务,信号的强弱决定了你离基站的距离,精确度比较高,几十米--几公里,精确度取决于基站的个数
wifi定位和基站定位局限性:不能定位海拔
3.gps定位,gps定位卫星进行定位,使用最少卫星实现全球定位,去和gps定位卫星进行通讯来获取定位坐标,通过光波进行通讯,必须得到空旷地方才能进行定位,连接至少需要一分钟,耗电,精确度特别高,不需要联网,联网:agps技术,通过联网来修正获取的坐标,特别准确的
百度定位sdk gps
高德 sdk
Day06##
定位的具体代码#
android.permission.ACCESS_MOCK_LOCATION : 模拟位置的权限,模拟器中必须加的,真机可加可不加
android.permission.ACCESS_FINE_LOCATION : 精确位置的权限,真机必须添加
android.permission.ACCESS_COARSE_LOCATION : 大概位置的权限,真机必须添加
passive : 被动,基站定位
gps : gps定位
定位的步骤
1.获取位置的管理者
//1.获取位置的管理者
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
2.获取定位方式
//2.获取定位方式
//2.1获取所有的定位方式
//enabledOnly : true : 返回所有可用的定位方式
List<String> providers = locationManager.getProviders(true);
for (String string : providers) {
System.out.println(string);
}
//2.2获取最佳的定位方式
Criteria criteria = new Criteria();
criteria.setAltitudeRequired(true);//设置是否可以定位海拔,true:可以定位海拔,一定返回gps定位
//criteria : 设置定位的属性,决定使用什么定位方式的
//enabledOnly : true : 定位可用的就返回
String bestProvider = locationManager.getBestProvider(criteria, true);
System.out.println("最佳的定位方式:"+bestProvider);
3.定位操作
a.定位
//provider : 定位方式
//minTime : 定位的最小时间间隔
//minDistance : 定位的最小距离间隔
//listener : LocationListener
locationManager.requestLocationUpdates(bestProvider, 0, 0, myLocationListener);
b.LocationListener
private class MyLocationListener implements LocationListener{
//当定位位置改变的时候调用
//location : 当前的位置
@Override
public void onLocationChanged(Location location) {
double latitude = location.getLatitude();//获取纬度,平行
double longitude = location.getLongitude();//获取经度
textview.setText("longitude:"+longitude+" latitude:"+latitude);
}
//当定位状态改变的时候调用
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// TODO Auto-generated method stub
}
//当定位可用的时候调用
@Override
public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub
}
//当定位不可用的时候调用
@Override
public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub
}
}
4.关闭gps定位
@Override
protected void onDestroy() {
super.onDestroy();
//关闭gps定位,高版本中已经不能这么做了,高版本中规定关闭和开启gps必须交由用户自己去实现
locationManager.removeUpdates(myLocationListener);
}
火星坐标#
116.29028943
40.04306343
火星坐标
116.2963848402697
40.04433174242044
定位操作移植到手机卫士# (重点:服务器service)
1.创建一个服务GPSService
public class GPSService extends Service {
}
2.清单文件配置
<service android:name="com.itheima.mobliesafe75.service.GPSService"></service>
3.将定位的操作移植到服务中
复制到定位的代码到服务中
4.开启服务
//开启一个服务
Intent intent_gps = new Intent(context,GPSService.class);
context.startService(intent_gps);
5.定位成功之后,发送短信,并且关闭服务
//当定位位置改变的时候调用
//location : 当前的位置
@Override
public void onLocationChanged(Location location) {
double latitude = location.getLatitude();//获取纬度,平行
double longitude = location.getLongitude();//获取经度
//给安全号码发送一个包含经纬度坐标的短信
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(sp.getString("safenum", "5556"), null, "longitude:"+longitude+" latitude:"+latitude, null, null);
//停止服务
stopSelf();
}
播放报警音乐# (重点)
1.播放报警音乐
//在播放报警音乐之前,将系统音量设置成最大
//声音的管理者
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
//设置系统音量的大小
//streamType : 声音的类型
//index : 声音的大小 0最小 15最大
//flags : 指定信息的标签
//getStreamMaxVolume : 获取系统最大音量,streamType:声音的类型
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
//判断是否在播放报警音乐
if (mediaPlayer!=null) {
mediaPlayer.release();//释放资源
}
mediaPlayer = MediaPlayer.create(context, R.raw.ylzs);
//mediaPlayer.setVolume(1.0f, 1.0f);//设置最大音量,音量比例
//mediaPlayer.setLooping(true);
mediaPlayer.start();
2.解决同时播放两首报警音乐问题
//广播接受者在每接收到一个广播事件,重新new广播接受者
private static MediaPlayer mediaPlayer;
超级管理员权限#
Caused by: java.lang.SecurityException: No active admin owned by uid 10047 for policy #3//缺少超级管理员权限
root:Linux系统中的最高权限,android是以liunx内核进行开发,root权限也是android的最高权限
超级管理员:只是能做一些比较危险的事情,比如锁屏,删除数据,root也可以做,但是root权限能做的事情,超级管理员就不一定能做
步骤
1.创建创建一类继承DeviceAdminReceiver
public class Admin extends DeviceAdminReceiver {
}
2.清单文件配置
<receiver
android:name="com.example.locknow.Admin"
android:description="@string/sample_device_admin_description"
android:label="@string/sample_device_admin"
android:permission="android.permission.BIND_DEVICE_ADMIN" >
<meta-data
android:name="android.app.device_admin"
android:resource="@xml/device_admin_sample" />
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
3.在res->xml->device_admin_sample.xml,同时修改描述信息,标题等信息
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
<uses-policies>
<limit-password />
<watch-login />
<reset-password />
<force-lock />
<wipe-data />
<expire-password />
<encrypted-storage />
<disable-camera />
</uses-policies>
</device-admin>
4.激活 setting -> security -> device admin...
5.代码激活超级管理员
public void register(View v){
//代码激活超级管理员
//设置激活超级管理员
Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
//设置激活那个超级管理员
//mDeviceAdminSample : 超级管理员的标示
intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, componentName);
//设置描述信息
intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION,"一键锁屏,非常好用");
startActivity(intent);
}
6.代码注销超级管理员
public void delete(View v){
//注销超级管理员
//判断超级管理员是否激活
if (devicePolicyManager.isAdminActive(componentName)) {
//注销超级管理员
devicePolicyManager.removeActiveAdmin(componentName);
}
}
7.锁屏操作
public void locknow(View v){
//锁屏的操作
devicePolicyManager.lockNow();//锁屏
}
远程删除数据&远程锁屏的功能#
1.将超级管理员操作移植到手机卫士中
2.远程删除数据
if (devicePolicyManager.isAdminActive(componentName)) {
devicePolicyManager.wipeData(0);//远程删除数据
}
3.远程锁屏
//判断超级管理员是否激活
if (devicePolicyManager.isAdminActive(componentName)) {
devicePolicyManager.lockNow();
}
查询号码归属地之数据库优化#
通过号码去查询这个号码属于哪个地区 13333333333 河北秦皇岛 02年 200万
号码
第一位:1
第二位:34578
第三位:0-9
号码前三位:可以确定运营商 131联通 133电信 134移动
往后4位:可以确定号码归属地
一个号码的前7位可以确定一个号码归属地
select location,areacode from mob_location group by location,areacode
select outkey from data1 where id=1300001
select location from data2 where id=496
select location from data2 where id=(select outkey from data1 where id=1300001)
拷贝数据库# (重点)
/**
* 拷贝数据库
*/
private void copyDb() {
File file = new File(getFilesDir(), "address.db");
//判断文件是否存在
if (!file.exists()) {
//从assets目录中将数据库读取出来
//1.获取assets的管理者
AssetManager am = getAssets();
InputStream in = null;
FileOutputStream out = null;
try {
//2.读取数据库
in = am.open("address.db");
//写入流
//getCacheDir : 获取缓存的路径
//getFilesDir : 获取文件的路径
out = new FileOutputStream(file);
//3.读写操作
//设置缓冲区
byte[] b = new byte[1024];
int len = -1;
while((len=in.read(b)) != -1){
out.write(b, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}finally{
// in.close();
// out.close();
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}
}
打开数据库,查询号码归属地# (重点)
/**
* 打开数据库,查询号码归属地
* @return
*/
public static String queryAddress(String num,Context context){
String location = "";
//1.获取数据库的路径
File file = new File(context.getFilesDir(), "address.db");
//2.打开数据库
//getAbsolutePath : 获取文件的绝对路径
//factory : 游标工厂
SQLiteDatabase database = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
//3.查询号码归属地
//sql : sql语句
//selectionArgs :查询条件的参数
//substring : 包含头不包含尾
Cursor cursor = database.rawQuery("select location from data2 where id=(select outkey from data1 where id=?)", new String[]{num.substring(0, 7)});
//4.解析cursor
//因为每个号码对应一个号码归属地,所以查询出来的是一个号码归属地,没有必要用whlie,用if就可以了
if (cursor.moveToNext()) {
location = cursor.getString(0);
}
//5.关闭数据库
cursor.close();
database.close();
return location;
}
查询号码归属地界面#
1.创建高级工具activity,在其中增加按钮,点击进入查询号码归属地activity
2.创建查询号码归属地的activity,在其中增加号码输入框,查询按钮,归属地显示textview
3.查询操作
/**
* 查询号码归属地操作
* @param v
*/
public void query(View v){
//1.获取输入的号码
String phone = et_address_queryphone.getText().toString().trim();
//2.判断号码是否为空
if (TextUtils.isEmpty(phone)) {
Toast.makeText(getApplicationContext(), "请输入要查询号码", 0).show();
return;
}
//3.根据号码查询号码归属地
String queryAddress = AddressDao.queryAddress(phone, getApplicationContext());
//4.判断查询的号码归属地是否为空
if (!TextUtils.isEmpty(queryAddress)) {
tv_address_queryaddress.setText(queryAddress);
}
}
查询号码归属地细节处理# (重点:逻辑)
//使用正则表达式进行判断
//^1[34578]\d{9}$
//身份证 ^[0-9]{17}[0-9x]$
//前六位:出生地 往后8位:出生年月日 剩下4位:前两位:出生编号 剩下2位:前一位:性别 奇数男 偶数女 最后一位:前17位的校验 x
if (num.matches("^1[34578]\\d{9}$")) {
Cursor cursor = database.rawQuery("select location from data2 where id=(select outkey from data1 where id=?)", new String[]{num.substring(0, 7)});
//4.解析cursor
//因为每个号码对应一个号码归属地,所以查询出来的是一个号码归属地,没有必要用whlie,用if就可以了
if (cursor.moveToNext()) {
location = cursor.getString(0);
}
//5.关闭数据库
cursor.close();
}else{
//对特殊电话做出来
switch (num.length()) {
case 3://110 120 119 911
location = "特殊电话";
break;
case 4://5554 5556
location = "虚拟电话";
break;
case 5://10086 10010 10000
location ="客服电话";
break;
case 7://座机,本地电话
case 8:
location="本地电话";
break;
default:// 010 1234567 10位 010 12345678 11位 0372 12345678 12位
//长途电话
if (num.length() >= 10 && num.startsWith("0")) {
//根据区号查询号码归属
//1.获取号码的区号
//3位,4位
//3位
String result = num.substring(1, 3);//010 10
//2.根据区号查询号码归属地
Cursor cursor = database.rawQuery("select location from data2 where area=?", new String[]{result});
//3.解析cursor
if (cursor.moveToNext()) {
location = cursor.getString(0);
//截取数据
location = location.substring(0, location.length()-2);
cursor.close();
}else{
//3位没有查询到,直接查询4位
//获取4位的区号
result = num.substring(1, 4);//0372 372
cursor = database.rawQuery("select location from data2 where area=?", new String[]{result});
if (cursor.moveToNext()) {
location = cursor.getString(0);
location = location.substring(0, location.length()-2);
cursor.close();
}
}
}
break;
}
Day07##
监听文本变化#
//监听输入框文本变化
et_address_queryphone.addTextChangedListener(new TextWatcher() {
//当文本变化完成的的时候调用
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//1.获取输入框输入的内容
String phone = s.toString();
//2.根据号码查询号码归属地
String queryAddress = AddressDao.queryAddress(phone, getApplicationContext());
//3.判断查询的号码归属地是否为空
if (!TextUtils.isEmpty(queryAddress)) {
//将查询的号码归属地设置给textveiw显示
tv_address_queryaddress.setText(queryAddress);
}
}
//当文本变化之前调用
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
// TODO Auto-generated method stub
}
//文本变化之后调用
@Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
抖动的效果#
借鉴别人代码的技巧
1.根据布局文件中的文本,去找相应布局文件
2.根据布局文件去找java类
3.在java类根据效果触发事件,去找相应的实现代码
1.res -> anim -> shake.xml
<!-- interpolator : 动画插补器 -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromXDelta="0"
android:interpolator="@anim/cycle_7"
android:toXDelta="10" />
2.res -> anim -> cycle_7.xml
<!-- cycleInterpolator : 循环插补器 cycles : 执行的次数 -->
<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:cycles="7" />
3.使用
//实现抖动的效果
Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
/*shake.setInterpolator(new Interpolator() {
@Override
public float getInterpolation(float x) {
return 0;//根据x的值获取y的值 y=x*x y=x-k
}
});*/
et_address_queryphone.startAnimation(shake);//开启动画
振动的效果#
//1.获取振动的管理者
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
//2.振动的效果
//vibrator.vibrate(Long.MAX_VALUE);//milliseconds : 振动的持续时间,毫秒值,但是在国产定制系统只会振动一次,比如小米
//pattern : 振动平率
//repeat : 是否重复振动,-1不重复,非-1重复,如果是非-1表示从振动平率的那个元素开始振动
vibrator.vibrate(new long[]{20l,10l,20l,10l}, -1);
权限
<uses-permission android:name="android.permission.VIBRATE"/>
监听电话状态# (重点:服务,telephoneManger)
1.创建一个服务
public class AddressService extends Service {
2.通过telephoneManager监听电话的状态
//监听电话状态
//1.获取电话的管理者
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
//2.监听电话的状态
myPhoneStateListener = new MyPhoneStateListener();
//listener : 电话状态的回调监听
//events : 监听电话的事件
//LISTEN_NONE : 不做任务监听操作
//LISTEN_CALL_STATE : 监听电话状态
telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
3.创建phonesatelistener
private class MyPhoneStateListener extends PhoneStateListener{
//监听电话状态的回调方法
//state : 电话的状态
//incomingNumber : 来电电话
@Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
case TelephonyManager.CALL_STATE_IDLE://空闲 状态,挂断状态
break;
case TelephonyManager.CALL_STATE_RINGING:;//响铃的状态
//查询号码归属地并显示
String queryAddress = AddressDao.queryAddress(incomingNumber, getApplicationContext());
//判断查询归属地是否为空
if (!TextUtils.isEmpty(queryAddress)) {
//显示号码归属地
Toast.makeText(getApplicationContext(), queryAddress, 0).show();
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK://通话的状态
break;
}
super.onCallStateChanged(state, incomingNumber);
}
}
4.服务关闭的时候,取消监听
@Override
public void onDestroy() {
//当服务关闭的时候,取消监听操作
telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_NONE);
super.onDestroy();
}
自定义toast# (重点:看源码,layoutParams)
抄写系统源码实现
1.显示toast
/**
* 显示toast
*/
public void showToast(String queryAddress) {
//1.获取windowmanager
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
textView = new TextView(getApplicationContext());
textView.setText(queryAddress);
textView.setTextSize(100);
textView.setTextColor(Color.RED);
//3.设置toast的属性
//layoutparams是toast的属性,控件要添加到那个父控件中,父控件就要使用那个父控件的属性,表示控件的属性规则符合父控件的属性规则
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
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.type = WindowManager.LayoutParams.TYPE_TOAST; // 执行toast的类型
//2.将view对象添加到windowManager中
//params : layoutparams 控件的属性
//将params属性设置给view对象,并添加到windowManager中
windowManager.addView(textView, params);
}
2.隐藏toast的方法
/**
* 隐藏toast
*/
public void hideToast(){
if (windowManager != null && textView!= null) {
windowManager.removeView(textView);//移出控件
windowManager= null;
textView=null;
}
}
服务开启# (重点:动态获取正在运行服务)
alt+shift+m : 将选中的代码抽取到方法中
1.在SettingActivity中增加显示号码归属地的条目
2.给条目设置点击事件
sv_setting_address.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(SettingActivity.this,AddressService.class);
//根据checkbox的状态设置描述信息的状态
//isChecked() : 之前的状态
if (sv_setting_address.isChecked()) {
//关闭提示更新
stopService(intent);
//更新checkbox的状态
sv_setting_address.setChecked(false);
}else{
//打开提示更新
startService(intent);
sv_setting_address.setChecked(true);
}
}
});
2.回显操作了,因为我们可以在设置中心中手动关闭服务,所以要动态的去获取服务是否开启
a.创建工具类,动态获取服务是否开启
/**
* 动态获取服务是否开启
* @return
*/
public static boolean isRunningService(String className,Context context){
//进程的管理者,活动的管理者
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//获取正在运行的服务
List<RunningServiceInfo> runningServices = activityManager.getRunningServices(1000);//maxNum 返回正在运行的服务的上限个数,最多返回多少个服务
//遍历集合
for (RunningServiceInfo runningServiceInfo : runningServices) {
//获取控件的标示
ComponentName service = runningServiceInfo.service;
//获取正在运行的服务的全类名
String className2 = service.getClassName();
//将获取到的正在运行的服务的全类名和传递过来的服务的全类名比较,一直表示服务正在运行 返回true,不一致表示服务没有运行 返回false
if (className.equals(className2)) {
return true;
}
}
return false;
}
b.使用
//回显操作
//动态的获取服务是否开启
if (AdressUtils.isRunningService("com.itheima.mobliesafe75.service.AddressService", getApplicationContext())) {
//开启服务
sv_setting_address.setChecked(true);
}else{
//关闭服务
sv_setting_address.setChecked(false);
}
3.当最小化应用的时候,从设置中心关闭服务,再次打开应用,发现条目没有进行更新的操作,解决:在onstart中调用动态获取服务是否开启的操作
//activity可见的时候调用
@Override
protected void onStart() {
super.onStart();
address();
}
自定义toast样式#
1.创建布局文件设置样式
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@drawable/call_locate_blue"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"
/>
<TextView
android:id="@+id/tv_toastcustom_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="北京移动"
android:textSize="18sp"
android:textColor="#000000"
/>
</LinearLayout>
2.将布局文件转化成view对象,并添加到windowmanager
//将布局文件转化成view对象
view = View.inflate(getApplicationContext(), R.layout.toast_custom, null);
//初始化控件
//view.findViewById表示去toast_custom找控件
TextView tv_toastcustom_address = (TextView) view.findViewById(R.id.tv_toastcustom_address);
tv_toastcustom_address.setText(queryAddress);
//将params属性设置给view对象,并添加到windowManager中
windowManager.addView(view, params);
外拨电话显示号码归属地# (重点:代码注册广播接受者)
广播接受者有两种注册方式
1.清单文件中注册
只要程序安装,就会一直生效,不能注销广播接受者,不受程序员控制
2.代码注册
只有程序运行,才会生效,杀死进程,就会失效,可以通过代码注销广播接受者,受程序员控制
代码注册广播接受者步骤
1.在服务中创建广播接受者
/**
* 外拨电话的广播接受者
* @author Administrator
*
*/
private class MyOutGoingCallReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
}
}
2.设置接受的广播事件
//1.设置广播接受者
myOutGoingCallReceiver = new MyOutGoingCallReceiver();
//2.设置接受的广播事件
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.NEW_OUTGOING_CALL");//设置接受的广播事件
3.注册广播接受者
//3.注册广播接受者
registerReceiver(myOutGoingCallReceiver, intentFilter);
4.注销广播接受者
//注销外拨电话广播接受者
unregisterReceiver(myOutGoingCallReceiver);
5.在广播接受者的onReceive中进行操作
@Override
public void onReceive(Context context, Intent intent) {
//查询外拨电话的号码归属地
//1.获取外拨电话
String phone = getResultData();
//2.查询号码归属地
String queryAddress = AddressDao.queryAddress(phone, getApplicationContext());
//3.判断号码归属地是否为空
if (!TextUtils.isEmpty(queryAddress)) {
//显示toast
showToast(queryAddress);
}
}
归属地提示框风格# (重点:单选框)
1.创建settingClickview及布局文件,拷贝settingview,将checkbox及自定义属性全部去掉
2.使用
/**
* 设置归属地提示框风格
*/
private void changedbg() {
final String[] items={"半透明","活力橙","卫士蓝","金属灰","苹果绿"};
//设置标题和描述信息
scv_setting_changedbg.setTitle("归属地提示框风格");
//根据保存的选中的选项的索引值设置自定义组合控件描述信息回显操作
scv_setting_changedbg.setDes(items[sp.getInt("which", 0)]);
//设置自定义组合控件的点击事件
scv_setting_changedbg.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//弹出单选对话框
AlertDialog.Builder builder = new Builder(SettingActivity.this);
//设置图标
builder.setIcon(R.drawable.ic_launcher);
//设置标题
builder.setTitle("归属地提示框风格");
//设置单选框
//items : 选项的文本的数组
//checkedItem : 选中的选项
//listener : 点击事件
//设置单选框选中选项的回显操作
builder.setSingleChoiceItems(items, sp.getInt("which", 0), new DialogInterface.OnClickListener(){
//which : 选中的选项索引值
@Override
public void onClick(DialogInterface dialog, int which) {
Editor edit = sp.edit();
edit.putInt("which", which);
edit.commit();
//1.设置自定义组合控件描述信息文本
scv_setting_changedbg.setDes(items[which]);//根据选中选项索引值从items数组中获取出相应文本,设置给描述信息控件
//2.隐藏对话框
dialog.dismiss();
}
});
//设置取消按钮
builder.setNegativeButton("取消", null);//当点击按钮只是需要进行隐藏对话框的操作的话,参数2可以写null,表示隐藏对话框
builder.show();
}
});
}
3.修改toast背景风格
//根据归属地提示框风格中设置的风格索引值设置toast显示的背景风格
view.setBackgroundResource(bgcolor[sp.getInt("which", 0)]);
设置toast位置# (重点)
//设置toast位置
//效果冲突,以默认的效果为主
params.gravity = Gravity.LEFT | Gravity.TOP;
params.x = 120;//不是坐标,表示的距离边框的距离,根据gravity来设置的,如果gravity是left表示距离左边框的距离,如果是right表示距离有边框的距离
params.y = 100;//跟x的含义
归属地提示框位置界面#
1.在设置中心添加归属地提示框风格相似条目
2.设置标题,描述信息和点击事件
/**
* 归属地提示框位置
*/
private void location() {
scv_setting_location.setTitle("归属地提示框位置");
scv_setting_location.setDes("设置归属地提示框的显示位置");
scv_setting_location.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//跳转到设置位置的界面
Intent intent = new Intent(SettingActivity.this,DragViewActivity.class);
startActivity(intent);
}
});
}
3.设置dragviewActivity的界面
a.在清单文件中设置activity的theme属性
<activity android:name=".DragViewActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
/>
b.设置布局文件中的父控件的背景
android:background="#aa000000"
c.设置相应的控件
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/call_locate_orange"
android:gravity="center_vertical"
android:orientation="horizontal" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<TextView
android:id="@+id/tv_toastcustom_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="双击居中"
android:textColor="#ffffff"
android:textSize="18sp" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按住提示框拖到任意位置\n按手机返回键立即生效"
android:textColor="#000000"
android:layout_alignParentBottom="true"
android:textSize="18sp"
android:gravity="center_horizontal"
android:background="@drawable/call_locate_blue"
android:padding="10dp"
/>
随着手指移动而移动的操作# (重点)
给控件设置触摸监听事件
/**
* 设置触摸监听
*/
private void setTouch() {
ll_dragview_toast.setOnTouchListener(new OnTouchListener() {
private int startX;
private int startY;
//v : 当前的控件
//event : 控件执行的事件
@Override
public boolean onTouch(View v, MotionEvent event) {
//event.getAction() : 获取控制的执行的事件
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下的事件
System.out.println("按下了....");
//1.按下控件,记录开始的x和y的坐标
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//移动的事件
System.out.println("移动了....");
//2.移动到新的位置记录新的位置的x和y的坐标
int newX = (int) event.getRawX();
int newY = (int) event.getRawY();
//3.计算移动的偏移量
int dX = newX-startX;
int dY = newY-startY;
//4.移动相应的偏移量,重新绘制控件
//获取的时候原控件距离父控件左边和顶部的距离
int l = ll_dragview_toast.getLeft();
int t = ll_dragview_toast.getTop();
//获取新的控件的距离父控件左边和顶部的距离
l+=dX;
t+=dY;
int r = l+ll_dragview_toast.getWidth();
int b = t+ll_dragview_toast.getHeight();
ll_dragview_toast.layout(l, t, r, b);//重新绘制控件
//5.更新开始的坐标
startX=newX;
startY=newY;
break;
case MotionEvent.ACTION_UP:
//抬起的事件
System.out.println("抬起了....");
break;
}
//True if the listener has consumed the event, false otherwise.
//true:事件消费了,执行了,false:表示事件被拦截了
return true;
}
});
}
Day08##
设置外拨电话和来电界面toast的位置#
1.在设置归属地提示框位置界面中保存控件移动到的新的位置的坐标,在up事件中
case MotionEvent.ACTION_UP:
//抬起的事件
System.out.println("抬起了....");
//保存控件的坐标,保存的是控件的坐标不是手指坐标
//获取控件的坐标
int x = ll_dragview_toast.getLeft();
int y = ll_dragview_toast.getTop();
Editor edit = sp.edit();
edit.putInt("x", x);
edit.putInt("y", y);
edit.commit();
break;
2.在addressservice中的showToast方法中,根据保存的坐标设置toast的位置
params.x = sp.getInt("x", 100);//不是坐标,表示的距离边框的距离,根据gravity来设置的,如果gravity是left表示距离左边框的距离,如果是right表示距离有边框的距离
params.y = sp.getInt("y", 100);//跟x的含义
位置回显操作# (重点:layoutparams)
控制正常显示出来
1.测量控件的宽高
2.分配控件的位置
3.绘制控件
回显操作
//设置控件回显操作
//1.获取保存的坐标
int x = sp.getInt("x", 0);
int y = sp.getInt("y", 0);
System.out.println("x:"+x+" y:"+y);
//2.重新绘制控件
/*int width = ll_dragview_toast.getWidth();
int height = ll_dragview_toast.getHeight();
System.out.println("width:"+width+" height:"+height);
ll_dragview_toast.layout(x, y, x+width, y+height);*/
//在初始化控件之前重新设置控件的属性
//2.1获取父控件的属性规则,父控件的layoutparams
RelativeLayout.LayoutParams params = (LayoutParams) ll_dragview_toast.getLayoutParams();
//2.2设置相应的属性
//leftMargin : 距离父控件左边的距离,根据布局文件中控件中layout_marginLeft属性效果相似
params.leftMargin = x;
params.topMargin = y;
//2.3给控件设置属性
ll_dragview_toast.setLayoutParams(params);
防止控件移出屏幕的操作#
在move事件中,在绘制控件之前判断
//在绘制控件之前,判断ltrb的值是否超出屏幕的大小,如果是就不在进行绘制控件的操作
if (l < 0 || r > width || t < 0 || b > height - 25) {
break;
}
触摸事件和点击的冲突问题#
点击事件 : 一组事件的集合,按下+抬起
触摸事件 : 每个事件都是一个单独的事件,按下,移动,抬起
如果只有触摸事件:return true表示事件执行
如果点击事件和触摸事件共同存在,触摸事件的事件是会点击事件消费掉
先执行触摸 然后执行其他事件
点击 按下+抬起
return false
触摸 : 按下 执行完 -> 移动 执行完 -> 抬起
触摸+点击 : 按下(触摸) 拦截 - > 按下(点击) 执行 - > 抬起(触摸) 拦截 - > 抬起(点击) 执行 - > 输出(触摸) - > 输出(点击)
return true
触摸 : 按下 -> 移动 -> 抬起
触摸+点击 按下(触摸) 执行 -> 移动(触摸) -> 抬起(触摸)
如果只有触摸事件:return true;
如果触摸+点击事件:触摸事件return false
双击居中# (重点)
long[] mHits = new long[2];
/**
* 双击居中
*/
private void setDoubleClick() {
ll_dragview_toast.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
/**
* src the source array to copy the content. 拷贝的原数组
srcPos the starting index of the content in src. 是从源数组那个位置开始拷贝
dst the destination array to copy the data into. 拷贝的目标数组
dstPos the starting index for the copied content in dst. 是从目标数组那个位置开始去写
length the number of elements to be copied. 拷贝的长度
*/
//拷贝数组操作
System.arraycopy(mHits, 1, mHits, 0, mHits.length-1);
mHits[mHits.length-1] = SystemClock.uptimeMillis(); // 将离开机的时间设置给数组的第二个元素,离开机时间 :毫秒值,手机休眠不算
if (mHits[0] >= (SystemClock.uptimeMillis()-500)) { // 判断是否多击操作
System.out.println("双击了...");
//双击居中
int l = (width - ll_dragview_toast.getWidth())/2;
int t = (height -25- ll_dragview_toast.getHeight())/2;
ll_dragview_toast.layout(l, t, l+ll_dragview_toast.getWidth(), t+ll_dragview_toast.getHeight());
//保存控件的坐标
Editor edit = sp.edit();
edit.putInt("x", l);
edit.putInt("y", t);
edit.commit();
}
}
});
}
细节的处理#
1.在布局文件中添加两个一模一样的textview,一个在上方,一个在下方
2.在move事件根据控件距离顶部的距离判断隐藏显示
//判断textview的隐藏显示
int top = ll_dragview_toast.getTop();
if (top >= height/2) {
//隐藏下方显示上方
tv_dragview_bottom.setVisibility(View.INVISIBLE);
tv_dragview_top.setVisibility(View.VISIBLE);
}else{
//隐藏上方显示下方
tv_dragview_top.setVisibility(View.INVISIBLE);
tv_dragview_bottom.setVisibility(View.VISIBLE);
}
3.在回显操作的时候也进行相应的判断
if (y >= height/2) {
//隐藏下方显示上方
tv_dragview_bottom.setVisibility(View.INVISIBLE);
tv_dragview_top.setVisibility(View.VISIBLE);
}else{
//隐藏上方显示下方
tv_dragview_top.setVisibility(View.INVISIBLE);
tv_dragview_bottom.setVisibility(View.VISIBLE);
}
外拨电话和来电显示界面toast的拖动# (重点:触摸监听事件)
1.给toast设置触摸监听事件
/**
* toast触摸监听事件
*/
private void setTouch() {
view.setOnTouchListener(new OnTouchListener() {
private int startX;
private int startY;
//v : 当前的控件
//event : 控件执行的事件
@Override
public boolean onTouch(View v, MotionEvent event) {
//event.getAction() : 获取控制的执行的事件
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下的事件
System.out.println("按下了....");
//1.按下控件,记录开始的x和y的坐标
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//移动的事件
System.out.println("移动了....");
//2.移动到新的位置记录新的位置的x和y的坐标
int newX = (int) event.getRawX();
int newY = (int) event.getRawY();
//3.计算移动的偏移量
int dX = newX-startX;
int dY = newY-startY;
//4.移动相应的偏移量,重新绘制控件
params.x+=dX;
params.y+=dY;
//控制控件的坐标不能移出外拨电话界面
if (params.x < 0) {
params.x = 0;
}
if (params.y < 0) {
params.y=0;
}
if (params.x > width-view.getWidth()) {
params.x = width-view.getWidth();
}
if (params.y > height - view.getHeight() - 25) {
params.y = height - view.getHeight() - 25;
}
windowManager.updateViewLayout(view, params);//更新windowmanager中的控件
//5.更新开始的坐标
startX=newX;
startY=newY;
break;
case MotionEvent.ACTION_UP:
//抬起的事件
System.out.println("抬起了....");
//保存控件的坐标,保存的是控件的坐标不是手指坐标
//获取控件的坐标
int x = params.x;
int y = params.y;
Editor edit = sp.edit();
edit.putInt("x", x);
edit.putInt("y", y);
edit.commit();
break;
}
//True if the listener has consumed the event, false otherwise.
//true:事件消费了,执行了,false:表示事件被拦截了
return false;
}
});
}
2.修改相应的属性
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE // 没有焦点
|
// WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE // 不可触摸
// |
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; // 保持当前屏幕
params.format = PixelFormat.TRANSLUCENT; // 透明
params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE; // 使用TYPE_PHONE//// 执行toast的类型,toast天生是没有获取见到和触摸事件
3.权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
小火箭#
10-15 06:18:10.301: E/AndroidRuntime(15436): android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want? //没有给activity设置任务栈
1.触摸监听事件
/**
* 小火箭设置触摸监听事件
*/
private void setTouch() {
view.setOnTouchListener(new OnTouchListener() {
private int startX;
private int startY;
//v : 当前的控件
//event : 控件执行的事件
@Override
public boolean onTouch(View v, MotionEvent event) {
//event.getAction() : 获取控制的执行的事件
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下的事件
System.out.println("按下了....");
//1.按下控件,记录开始的x和y的坐标
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
//移动的事件
System.out.println("移动了....");
//2.移动到新的位置记录新的位置的x和y的坐标
int newX = (int) event.getRawX();
int newY = (int) event.getRawY();
//3.计算移动的偏移量
int dX = newX-startX;
int dY = newY-startY;
//4.移动相应的偏移量,重新绘制控件
params.x+=dX;
params.y+=dY;
//控制控件的坐标不能移出外拨电话界面
if (params.x < 0) {
params.x = 0;
}
if (params.y < 0) {
params.y=0;
}
if (params.x > width-view.getWidth()) {
params.x = width-view.getWidth();
}
if (params.y > height - view.getHeight() - 25) {
params.y = height - view.getHeight() - 25;
}
windowManager.updateViewLayout(view, params);//更新windowmanager中的控件
//5.更新开始的坐标
startX=newX;
startY=newY;
break;
case MotionEvent.ACTION_UP:
//抬起的事件
System.out.println("抬起了....");
//发射小火箭
//判断小火箭是否在屏幕底部中间的位置,在就可以发送,不在不能发送
if (params.y > 290 && params.x > 100 && params.x < 200) {
sendRocket();
//开启烟雾动画
Intent intent = new Intent(RocketService.this,BackGroundActivity.class);
//给跳转了activity设置任务栈
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
break;
}
//True if the listener has consumed the event, false otherwise.
//true:事件消费了,执行了,false:表示事件被拦截了
return true;
}
});
}
2.帧动画
a.res -> anim - > xxx.xml
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false"> <!-- oneshot : 是否执行一次 true:执行一次 false:循环执行 -->
<item android:drawable="@drawable/desktop_rocket_launch_1" android:duration="200" />
<item android:drawable="@drawable/desktop_rocket_launch_2" android:duration="200" />
</animation-list>
b.使用
AnimationDrawable animationDrawable = (AnimationDrawable) iv_rocket.getBackground();
animationDrawable.start();//开始动画
3.渐变动画
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(200);
ImageView smoke_m = (ImageView) findViewById(R.id.smoke_m);
ImageView smoke_t = (ImageView) findViewById(R.id.smoke_t);
//执行动画
smoke_m.startAnimation(alphaAnimation);
smoke_t.startAnimation(alphaAnimation);
创建数据库# (重点)
1.创建一个类继承SQLiteOpenHelper
//创建数据库
public class BlackNumOpenHlper extends SQLiteOpenHelper {
//方便后期我们在实现数据库操作的时候能方便去使用表名,同时也方便后期去修改表名
private static final String DB_NAME="info";
/**
* context : 上下文
* name : 数据库名称
* factory:游标工厂
* version : 数据库的版本号
* @param context
*/
public BlackNumOpenHlper(Context context) {
super(context, "blacknum.db", null, 1);
}
//第一次创建数据库的调用,创建表结构
@Override
public void onCreate(SQLiteDatabase db) {
//创建表结构 字段: blacknum:黑名单号码 mode:拦截类型
//参数:创建表结构sql语句
db.execSQL("create table "+DB_NAME+"(_id integer primary key autoincrement,blacknum varchar(20),mode varchar(2))");
}
//当数据库版本发生变化的时候调用
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
2.单元测试
public void testBlackNumOpenHelper(){
BlackNumOpenHlper blackNumOpenHlper = new BlackNumOpenHlper(getContext());//不能创建数据库
blackNumOpenHlper.getReadableDatabase();//创建数据库
}
添加黑名单# (重点)
/**
* 添加黑名单
* @param blacknum
* @param mode
*/
public void addBlackNum(String blacknum,int mode){
//1.获取数据库
SQLiteDatabase database = blackNumOpenHlper.getWritableDatabase();
//2.添加操作
//ContentValues : 添加的数据
ContentValues values = new ContentValues();
values.put("blacknum", blacknum);
values.put("mode", mode);
database.insert(BlackNumOpenHlper.DB_NAME, null, values);
//3.关闭数据库
database.close();
}
数据其他操作# (重点)
1.更新
/**
* 更新黑名单的拦截模式
*/
public void updateBlackNum(String blacknum,int mode){
//1.获取数据库
SQLiteDatabase database = blackNumOpenHlper.getWritableDatabase();
//2.更新操作
ContentValues values = new ContentValues();
values.put("mode", mode);
//table : 表名
//values : 要更新数据
//whereClause : 查询条件 where blacknum=blacknum
//whereArgs : 查询条件的参数
database.update(BlackNumOpenHlper.DB_NAME, values, "blacknum=?", new String[]{blacknum});
//3.关闭数据库
database.close();
}
2.查询
/**
* 通过黑名单号码,查询黑名单号码的拦截模式
*/
public int queryBlackNumMode(String blacknum){
int mode=-1;
//1.获取数据库
SQLiteDatabase database = blackNumOpenHlper.getReadableDatabase();
//2.查询数据库
//table : 表名
//columns : 查询的字段 mode
//selection : 查询条件 where xxxx = xxxx
//selectionArgs : 查询条件的参数
//groupBy : 分组
//having : 去重
//orderBy : 排序
Cursor cursor = database.query(BlackNumOpenHlper.DB_NAME, new String[]{"mode"}, "blacknum=?", new String[]{blacknum}, null, null, null);
//3.解析cursor
if (cursor.moveToNext()) {
//获取查询出来的数据
mode = cursor.getInt(0);
}
//4.关闭数据库
cursor.close();
database.close();
return mode;
}
3.删除
/**
* 根据黑名单号码,删除相应的数据
* @param blacknum
*/
public void deleteBlackNum(String blacknum){
//1.获取数据库
SQLiteDatabase database = blackNumOpenHlper.getWritableDatabase();
//2.删除
//table : 表名
//whereClause : 查询的条件
//whereArgs : 查询条件的参数
database.delete(BlackNumOpenHlper.DB_NAME, "blacknum=?", new String[]{blacknum});
//3.关闭数据库
database.close();
}
Day09##
查询全部数据#
/**
* 查询全部黑名单号码
*/
public List<BlackNumInfo> queryAllBlackNum(){
List<BlackNumInfo> list = new ArrayList<BlackNumInfo>();
//1.获取数据库
SQLiteDatabase database = blackNumOpenHlper.getReadableDatabase();
//2.查询操作
Cursor cursor = database.query(BlackNumOpenHlper.DB_NAME, new String[]{"blacknum","mode"}, null, null, null, null, null);
//3.解析cursor
while(cursor.moveToNext()){
//获取查询出来的数据]
String blacknum = cursor.getString(0);
int mode = cursor.getInt(1);
BlackNumInfo blackNumInfo = new BlackNumInfo(blacknum, mode);
list.add(blackNumInfo);
}
//4.关闭数据库
cursor.close();
database.close();
return list;
}
通讯卫士界面展示#
1.通过异步加载获取数据
/**
* 加载数据
*/
private void fillData() {
new MyAsycnTaks() {
@Override
public void preTask() {
loading.setVisibility(View.VISIBLE);
}
@Override
public void postTask() {
lv_callsmssafe_blacknums.setAdapter(new MyAdapter());
loading.setVisibility(View.INVISIBLE);
}
@Override
public void doinBack() {
list = blackNumDao.queryAllBlackNum();
}
}.execute();
}
2.通过listview展示数据
//设置条目显示的样式
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//根据条目的位置获取对应的bean对象
BlackNumInfo blackNumInfo = list.get(position);
View view = View.inflate(getApplicationContext(), R.layout.item_callsmssafe, null);
TextView tv_itemcallsmssafe_blacknum = (TextView) view.findViewById(R.id.tv_itemcallsmssafe_blacknum);
TextView tv_itemcallsmssafe_mode = (TextView) view.findViewById(R.id.tv_itemcallsmssafe_mode);
ImageView iv_itemcallsmssafe_delete = (ImageView) view.findViewById(R.id.iv_itemcallsmssafe_delete);
//设置显示数据
tv_itemcallsmssafe_blacknum.setText(blackNumInfo.getBlacknum());
int mode = blackNumInfo.getMode();
switch (mode) {
case BlackNumDao.CALL:
tv_itemcallsmssafe_mode.setText("电话拦截");
break;
case BlackNumDao.SMS:
tv_itemcallsmssafe_mode.setText("短信拦截");
break;
case BlackNumDao.ALL:
tv_itemcallsmssafe_mode.setText("全部拦截");
break;
}
return view;
}
listview复用缓存# (重点)
1.在activity创建存放控件的容器
/**
* 存放控件的容器
* @author Administrator
*
*/
class ViewHolder{
TextView tv_itemcallsmssafe_blacknum,tv_itemcallsmssafe_mode;
ImageView iv_itemcallsmssafe_delete;
}
2.在创建条目view对象的时候,将控件保存到控件的容器中,并将容器绑定到view对象中
ViewHolder viewHolder;
if (convertView == null) {
view = View.inflate(getApplicationContext(), R.layout.item_callsmssafe, null);
//创建控件的容器
viewHolder = new ViewHolder();
//把控件存放到容器中
viewHolder.tv_itemcallsmssafe_blacknum = (TextView) view.findViewById(R.id.tv_itemcallsmssafe_blacknum);
viewHolder.tv_itemcallsmssafe_mode = (TextView) view.findViewById(R.id.tv_itemcallsmssafe_mode);
viewHolder.iv_itemcallsmssafe_delete = (ImageView) view.findViewById(R.id.iv_itemcallsmssafe_delete);
//将容器和view对象绑定在一起
view.setTag(viewHolder);
3.复用缓存的时候,将控件的容器从复用的view对象中拿出来
}else{
view = convertView;
//从view对象中得到控件的容器
viewHolder = (ViewHolder) view.getTag();
}
4.使用,可以直接使用复用的控件的容器中存放已经findviewbyid过的控件,就是在使用已已经findviewbyid过的控件
//设置显示数据
viewHolder.tv_itemcallsmssafe_blacknum.setText(blackNumInfo.getBlacknum());
删除黑名单号码#
/**
* 删除黑名单
*/
viewHolder.iv_itemcallsmssafe_delete.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//删除黑名单操作
AlertDialog.Builder builder = new Builder(CallSmsSafeActivity.this);
builder.setMessage("您确认要删除黑名单号码:"+blackNumInfo.getBlacknum()+"?");
//设置确定和取消按钮
builder.setPositiveButton("确定", new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which) {
//删除黑名单操作
//1.删除数据库中的黑名单号码
blackNumDao.deleteBlackNum(blackNumInfo.getBlacknum());
//2.删除界面中已经显示黑名单号码
//2.1从存放有所有数据的list集合中删除相应的数据
list.remove(position);//删除条目对应位置的相应的数据
//2.2更新界面
myAdapter.notifyDataSetChanged();//更新界面
//3.隐藏对话框
dialog.dismiss();
}
});
builder.setNegativeButton("取消", null);
builder.show();
}
});
添加黑名单# (重点 : RadioGroup)
1.添加黑名单操作
/**
* 添加黑名单点击事件
* @param v
*/
public void addBlackNum(View v){
//弹出对话框,让用户去添加黑名单
AlertDialog.Builder builder = new Builder(this);
View view = View.inflate(getApplicationContext(), R.layout.dialog_add_blacknum, null);
//初始化控件,执行添加操作
final EditText et_addblacknum_blacknum = (EditText) view.findViewById(R.id.et_addblacknum_blacknum);
final RadioGroup rg_addblacknum_modes = (RadioGroup) view.findViewById(R.id.rg_addblacknum_modes);
Button btn_ok = (Button) view.findViewById(R.id.btn_ok);
Button btn_cancle = (Button) view.findViewById(R.id.btn_cancle);
//设置按钮的点击事件
btn_ok.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//添加黑名单号码操作
//1.获取输入的黑名单号码
String blacknum = et_addblacknum_blacknum.getText().toString().trim();
//2.判断获取的内容是否为空
if (TextUtils.isEmpty(blacknum)) {
Toast.makeText(getApplicationContext(), "请输入黑名单号码", 0).show();
return;
}
//3.获取拦截模式
int mode = -1;
int radioButtonId = rg_addblacknum_modes.getCheckedRadioButtonId();//获取选中的RadioButton的id
switch (radioButtonId) {
case R.id.rb_addblacknum_tel:
//电话拦截
mode = BlackNumDao.CALL;
break;
case R.id.rb_addblacknum_sms:
//短信拦截
mode = BlackNumDao.SMS;
break;
case R.id.rb_addblacknum_all:
//全部拦截
mode = BlackNumDao.ALL;
break;
}
//4.添加黑名单
//1.添加到数据库
blackNumDao.addBlackNum(blacknum, mode);
//2.添加到界面显示
//2.1添加到list集合中
//list.add(new BlackNumInfo(blacknum, mode));
list.add(0, new BlackNumInfo(blacknum, mode));//location : 参数2要添加到位置,参数2:添加数据
//2.2更新界面
myAdapter.notifyDataSetChanged();
//隐藏对话哭给你
dialog.dismiss();
}
});
//设置取消按钮的点击事件
btn_cancle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//隐藏对话框
dialog.dismiss();
}
});
builder.setView(view);
//builder.show();
dialog = builder.create();
dialog.show();
}
2.修改查询数据的方式为倒序查询
Cursor cursor = database.query(BlackNumOpenHlper.DB_NAME, new String[]{"blacknum","mode"}, null, null, null, null, "_id desc");//desc倒序查询,asc:正序查询,默认正序查询
分批加载# (重点)
select blacknum from info order by _id desc limit 20 offset 20
limit : 查询的总个数 offset :查询的其实位置
select blacknum from info order by _id desc limit 0,20
前面:查询的起始位置 后面:查询的总个数
1.创建加载部分数据的方法
/**
* 查询部分数据
* 查询20条数据
* MaxNum : 查询的总个数
* startindex : 查询的起始位置
* @return
*/
public List<BlackNumInfo> getPartBlackNum(int MaxNum,int startindex){
List<BlackNumInfo> list = new ArrayList<BlackNumInfo>();
//1.获取数据库
SQLiteDatabase database = blackNumOpenHlper.getReadableDatabase();
//2.查询操作
//Cursor cursor = database.query(BlackNumOpenHlper.DB_NAME, new String[]{"blacknum","mode"}, null, null, null, null, "_id desc");//desc倒序查询,asc:正序查询,默认正序查询
Cursor cursor = database.rawQuery("select blacknum,mode from info order by _id desc limit ? offset ?", new String[]{MaxNum+"",startindex+""});
//3.解析cursor
while(cursor.moveToNext()){
//获取查询出来的数据]
String blacknum = cursor.getString(0);
int mode = cursor.getInt(1);
BlackNumInfo blackNumInfo = new BlackNumInfo(blacknum, mode);
list.add(blackNumInfo);
}
//4.关闭数据库
cursor.close();
database.close();
return list;
}
2.更改加载数据方式
@Override
public void postTask() {
if (myAdapter == null) {
myAdapter = new MyAdapter();
lv_callsmssafe_blacknums.setAdapter(myAdapter);
}else{
myAdapter.notifyDataSetChanged();
}
loading.setVisibility(View.INVISIBLE);
}
@Override
public void doinBack() {
if (list == null) {
//1.2.3 4.5.6
list = blackNumDao.getPartBlackNum(MAXNUM,startIndex);
}else{
//addAll : 将一个集合整合到另一个集合
//A [1.2.3] B[4.5.6]
//A.addAll(B) A [1.2.3.4.5.6]
list.addAll(blackNumDao.getPartBlackNum(MAXNUM,startIndex));
}
}
3.实现滑动加载更多操作
//listview滑动监听事件
lv_callsmssafe_blacknums.setOnScrollListener(new OnScrollListener() {
//当滑动状态改变的时候调用的方法
//view : listview
//scrollState : 滑动状态
//SCROLL_STATE_IDLE : 空闲的状态
//SCROLL_STATE_TOUCH_SCROLL : 缓慢滑动的状态
//SCROLL_STATE_FLING : 快速滑动
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//当listview静止的时候判断界面显示的最后一个条目是否是查询数据的最后一个条目,是加载下一波数据,不是用户进行其他操作
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
//获取界面显示最后一个条目
int position = lv_callsmssafe_blacknums.getLastVisiblePosition();//获取界面显示最后一个条目,返回的时候条目的位置
//判断是否是查询数据的最后一个数据 20 0-19
if (position == list.size()-1) {
//加载下一波数据
//更新查询的其实位置 0-19 20-39
startIndex+=MAXNUM;
fillData();
}
}
}
//当滑动的时候调用的方法
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
}
});
短信拦截#
1.创建一个服务,在其中去实现拦截功能
2.在设置中心中模仿显示号码归属地添加一个条目,拷贝代码修改
3.在服务中注册广播接受者
a.创建广播接受者
private class SmsReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
}
}
b.注册广播接受者
//代码注册短信到来的广播接受者
//1.创建广播接受者
smsReceiver = new SmsReceiver();
//2.设置监听广播事件
IntentFilter intentFilter = new IntentFilter();
//广播接受者的最高优先级Integer.MAX_VALUE,优先级相同,代码注册的广播接受者先接受广播事件
intentFilter.setPriority(1000);
intentFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
//3.注册广播接受者
registerReceiver(smsReceiver, intentFilter);
c.注销广播接受者
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(smsReceiver);
}
d.实现操作
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("代码注册广播接受者接受短信");
//接受解析短信的操作
Object[] objs = (Object[]) intent.getExtras().get("pdus");
for(Object obj:objs){
//解析成SmsMessage
SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) obj);
String body = smsMessage.getMessageBody();//获取短信的内容
String sender = smsMessage.getOriginatingAddress();//获取发件人
//根据发件人号码,获取号码的拦截模式
int mode = blackNumDao.queryBlackNumMode(sender);
//判断是否是短信拦截或者是全部拦截
if (mode == BlackNumDao.SMS || mode == BlackNumDao.ALL) {
abortBroadcast();//拦截广播事件,拦截短信操作
}
}
}
电话拦截# (建议:看看反射)
1.通过telePhoneManager监听电话的状态
private class MyPhoneStateListener extends PhoneStateListener{
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
//如果是响铃状态,检测拦截模式是否是电话拦截,是挂断
if (state == TelephonyManager.CALL_STATE_RINGING) {
//获取拦截模式
int mode = blackNumDao.queryBlackNumMode(incomingNumber);
if (mode == BlackNumDao.CALL || mode == BlackNumDao.ALL) {
//挂断电话 1.5
endCall();
}
}
}
}
2.通过反射实现挂断电话
/**
* 挂断电话
*/
public void endCall() {
//通过反射进行实现
try {
//1.通过类加载器加载相应类的class文件
//Class<?> forName = Class.forName("android.os.ServiceManager");
Class<?> loadClass = BlackNumService.class.getClassLoader().loadClass("android.os.ServiceManager");
//2.获取类中相应的方法
//name : 方法名
//parameterTypes : 参数类型
Method method = loadClass.getDeclaredMethod("getService", String.class);
//3.执行方法,获取返回值
//receiver : 类的实例
//args : 具体的参数
IBinder invoke = (IBinder) method.invoke(null, Context.TELEPHONY_SERVICE);
//aidl
ITelephony iTelephony = ITelephony.Stub.asInterface(invoke);
//挂断电话
iTelephony.endCall();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
3.权限
<uses-permission android:name="android.permission.CALL_PHONE"/>
删除通话记录# (重点:内容观察者)
//删除通话记录
//1.获取内容解析者
final ContentResolver resolver = getContentResolver();
//2.获取内容提供者地址 call_log calls表的地址:calls
//3.获取执行操作路径
final Uri uri = Uri.parse("content://call_log/calls");
//4.删除操作
//resolver.delete(uri, "number=?", new String[]{incomingNumber});
//通过内容观察者观察内容提供者内容,如果变化,就去执行删除操作
//notifyForDescendents : 匹配规则,true : 精确匹配 false:模糊匹配
resolver.registerContentObserver(uri, true, new ContentObserver(new Handler()) {
//内容提供者内容变化的时候调用
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
//删除通话记录
resolver.delete(uri, "number=?", new String[]{incomingNumber});
//注销内容观察者
resolver.unregisterContentObserver(this);
}
});
获取系统应用程序信息# (重点)
/**
* 获取系统中所有应用程序的信息
*/
public static List<AppInfo> getAppInfos(Context context){
List<AppInfo> list = new ArrayList<AppInfo>();
//获取应用程序信息
//包的管理者
PackageManager pm = context.getPackageManager();
//获取系统中安装到所有软件信息
List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
for (PackageInfo packageInfo : installedPackages) {
//获取包名
String packageName = packageInfo.packageName;
//获取版本号
String versionName = packageInfo.versionName;
//获取application
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
//获取应用程序的图标
Drawable icon = applicationInfo.loadIcon(pm);
//获取应用程序的名称
String name = applicationInfo.loadLabel(pm).toString();
//是否是用户程序
//获取应用程序中相关信息,是否是系统程序和是否安装到SD卡
boolean isUser;
int flags = applicationInfo.flags;
if ((applicationInfo.FLAG_SYSTEM & flags) == applicationInfo.FLAG_SYSTEM) {
//系统程序
isUser = false;
}else{
//用户程序
isUser = true;
}
//是否安装到SD卡
boolean isSD;
if ((applicationInfo.FLAG_EXTERNAL_STORAGE & flags) == applicationInfo.FLAG_EXTERNAL_STORAGE) {
//安装到了SD卡
isSD = true;
}else{
//安装到手机中
isSD = false;
}
//添加到bean中
AppInfo appInfo = new AppInfo(name, icon, packageName, versionName, isSD, isUser);
//将bean存放到list集合
list.add(appInfo);
}
return list;
}
软件管理界面#
1.使用异步加载框架,加载数据
/**
* 加载数据
*/
private void fillData() {
new MyAsycnTaks() {
@Override
public void preTask() {
loading.setVisibility(View.VISIBLE);
}
@Override
public void postTask() {
lv_softmanager_application.setAdapter(new Myadapter());
loading.setVisibility(View.INVISIBLE);
}
@Override
public void doinBack() {
list = AppEngine.getAppInfos(getApplicationContext());
}
}.execute();
}
2.通过listview展示数据
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ViewHolder viewHolder;
if (convertView != null ) {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}else{
view = View.inflate(getApplicationContext(), R.layout.item_softmanager, null);
viewHolder = new ViewHolder();
viewHolder.iv_itemsoftmanage_icon = (ImageView) view.findViewById(R.id.iv_itemsoftmanage_icon);
viewHolder.tv_softmanager_name = (TextView) view.findViewById(R.id.tv_softmanager_name);
viewHolder.tv_softmanager_issd = (TextView) view.findViewById(R.id.tv_softmanager_issd);
viewHolder.tv_softmanager_version = (TextView) view.findViewById(R.id.tv_softmanager_version);
//将viewholer和view对象绑定
view.setTag(viewHolder);
}
//1.获取应用程序的信息
AppInfo appInfo = list.get(position);
//设置显示数据
viewHolder.iv_itemsoftmanage_icon.setImageDrawable(appInfo.getIcon());
viewHolder.tv_softmanager_name.setText(appInfo.getName());
boolean sd = appInfo.isSD();
if (sd) {
viewHolder.tv_softmanager_issd.setText("SD卡");
}else{
viewHolder.tv_softmanager_issd.setText("手机内存");
}
viewHolder.tv_softmanager_version.setText(appInfo.getVersionName());
return view;
}
static class ViewHolder{
ImageView iv_itemsoftmanage_icon;
TextView tv_softmanager_name,tv_softmanager_issd,tv_softmanager_version;
}
Day10##
系统程序和用户程序拆分#
1.将集合拆分,分别保存
@Override
public void doinBack() {
list = AppEngine.getAppInfos(getApplicationContext());
userappinfo = new ArrayList<AppInfo>();
systemappinfo = new ArrayList<AppInfo>();
for (AppInfo appinfo : list) {
//将数据分别存放到用户程序集合和系统程序集合中
if (appinfo.isUser()) {
userappinfo.add(appinfo);
}else{
systemappinfo.add(appinfo);
}
}
}
2.修改adapter的getcount返回值
@Override
public int getCount() {
// TODO Auto-generated method stub
//list.size() = userappinfo.size()+systemappinfo.size()
return userappinfo.size()+systemappinfo.size()+2;
}
3.在getview中增加条目,并获取数据
增加条目
if (position == 0) {
//添加用户程序(...个)textview
TextView textView = new TextView(getApplicationContext());
textView.setBackgroundColor(Color.GRAY);
textView.setTextColor(Color.WHITE);
textView.setText("用户程序("+userappinfo.size()+")");
return textView;
}else if(position == userappinfo.size()+1){
//添加系统程序(....个)textview
TextView textView = new TextView(getApplicationContext());
textView.setBackgroundColor(Color.GRAY);
textView.setTextColor(Color.WHITE);
textView.setText("系统程序("+systemappinfo.size()+")");
return textView;
}
获取数据
//1.获取应用程序的信息
AppInfo appInfo;
//数据就要从userappinfo和systemappinfo中获取
if (position <= userappinfo.size()) {
//用户程序
appInfo = userappinfo.get(position-1);
}else{
//系统程序
appInfo = systemappinfo.get(position - userappinfo.size() - 2);
}
判断缓存复用
if (convertView != null && convertView instanceof RelativeLayout) {
view = convertView;
viewHolder = (ViewHolder) view.getTag();
}else{
浮动显示程序个数#
/**
* listview滑动监听事件
*/
private void listviewOnscroll() {
lv_softmanager_application.setOnScrollListener(new OnScrollListener() {
//滑动状态改变的时候调用
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
//滑动的时候调用
//view : listview
//firstVisibleItem : 界面第一个显示条目
//visibleItemCount : 显示条目总个数
//totalItemCount : 条目的总个数
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//为null的原因:listview在初始化的时候就会调用onScroll方法
if (userappinfo != null && systemappinfo != null) {
if (firstVisibleItem >= userappinfo.size()+1) {
tv_softmanager_userorsystem.setText("系统程序("+systemappinfo.size()+")");
}else{
tv_softmanager_userorsystem.setText("用户程序("+userappinfo.size()+")");
}
}
}
});
}
popuwindow操作# (重点)
/**
* 条目点击事件
*/
private void listviewItemClick() {
lv_softmanager_application.setOnItemClickListener(new OnItemClickListener() {
//view : 条目的view对象
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//弹出气泡
//1.屏蔽用户程序和系统程序(...个)弹出气泡
if (position == 0 || position == userappinfo.size()+1) {
return;
}
//2.获取条目所对应的应用程序的信息
//数据就要从userappinfo和systemappinfo中获取
if (position <= userappinfo.size()) {
//用户程序
appInfo = userappinfo.get(position-1);
}else{
//系统程序
appInfo = systemappinfo.get(position - userappinfo.size() - 2);
}
//5.弹出新的气泡之前,删除旧 的气泡
hidePopuwindow();
//3.弹出气泡
/*TextView contentView = new TextView(getApplicationContext());
contentView.setText("我是popuwindow的textview控件");
contentView.setBackgroundColor(Color.RED);*/
View contentView = View.inflate(getApplicationContext(), R.layout.popu_window, null);
//contentView : 显示view对象
//width,height : view宽高
popupWindow = new PopupWindow(contentView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
//4.获取条目的位置,让气泡显示在相应的条目
int[] location = new int[2];//保存x和y坐标的数组
view.getLocationInWindow(location);//获取条目x和y的坐标,同时保存到int[]
//获取x和y的坐标
int x = location[0];
int y = location[1];
//parent : 要挂载在那个控件上
//gravity,x,y : 控制popuwindow显示的位置
popupWindow.showAtLocation(parent, Gravity.LEFT | Gravity.TOP, x+50, y);
}
});
}
隐藏气泡
/**
* 隐藏气泡
*/
private void hidePopuwindow() {
if (popupWindow != null) {
popupWindow.dismiss();//隐藏气泡
popupWindow = null;
}
}
popuwindow动画# (重点)
动画要想执行,执行的控件必须有背景,动画都是基于背景来进行一些计算,没有背景动画是无法执行,popuwindow默认是没有设置背景
//6.设置动画
//缩放动画
//前四个 : 控制控件由没有变到有 动画 0:没有 1:整个控件
//后四个:控制控件是按照自身还是父控件进行变化
//RELATIVE_TO_SELF : 以自身变化
//RELATIVE_TO_PARENT : 以父控件变化
ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setDuration(500);
//渐变动画
AlphaAnimation alphaAnimation = new AlphaAnimation(0.4f, 1.0f);//由半透明变成不透明
alphaAnimation.setDuration(500);
//组合动画
//shareInterpolator : 是否使用相同的动画插补器 true:共享 false:各自使用各自的
AnimationSet animationSet = new AnimationSet(true);
//添加动画
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(alphaAnimation);
//执行动画
contentView.startAnimation(animationSet);
给popuwindow设置背景
popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
设置控件点击事件#
1.初始化控件
//初始化控件
LinearLayout ll_popuwindow_uninstall = (LinearLayout) contentView.findViewById(R.id.ll_popuwindow_uninstall);
LinearLayout ll_popuwindow_start = (LinearLayout) contentView.findViewById(R.id.ll_popuwindow_start);
LinearLayout ll_popuwindow_share = (LinearLayout) contentView.findViewById(R.id.ll_popuwindow_share);
LinearLayout ll_popuwindow_detail = (LinearLayout) contentView.findViewById(R.id.ll_popuwindow_detail);
2.activity实现点击事件接口,并实现相应的点击事件方法,同时设置控件点击事件
public class SoftMangaerActivity extends Activity implements OnClickListener {
//给控件设置点击事件
ll_popuwindow_uninstall.setOnClickListener(SoftMangaerActivity.this);
ll_popuwindow_start.setOnClickListener(SoftMangaerActivity.this);
ll_popuwindow_share.setOnClickListener(SoftMangaerActivity.this);
ll_popuwindow_detail.setOnClickListener(SoftMangaerActivity.this);
@Override
public void onClick(View v) {
//判断点击是按个按钮
//getId() : 获取点击按钮的id
switch (v.getId()) {
case R.id.ll_popuwindow_uninstall:
System.out.println("卸载");
break;
case R.id.ll_popuwindow_start:
System.out.println("启动");
break;
case R.id.ll_popuwindow_share:
System.out.println("分享");
break;
case R.id.ll_popuwindow_detail:
System.out.println("详情");
break;
}
}
卸载#
/**
* 卸载
*/
private void uninstall() {
/**
* <intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.DELETE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="package" />
</intent-filter>
*/
//判断是否是系统程序
if (appInfo.isUser()) {
//判断是否是我们自己的应用,是不能卸载
if (!appInfo.getPackagName().equals(getPackageName())) {
Intent intent = new Intent();
intent.setAction("android.intent.action.DELETE");
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse("package:"+appInfo.getPackagName()));//tel:110
startActivityForResult(intent,0);
}else{
Toast.makeText(getApplicationContext(), "文明社会,杜绝自杀", 0).show();
}
}else{
Toast.makeText(getApplicationContext(), "要想卸载系统程序,请root先", 0).show();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
fillData();
}
启动#
/**
* 启动
*/
private void start() {
PackageManager pm = getPackageManager();
//获取应用程序的启动意图
Intent intent = pm.getLaunchIntentForPackage(appInfo.getPackagName());
if (intent!=null) {
startActivity(intent);
}else{
Toast.makeText(getApplicationContext(), "系统核心程序,无法启动", 0).show();
}
}
详情#
通过系统log查看跳转操作
/**
*详情
*/
private void detail() {
/**
* Intent
{
act=android.settings.APPLICATION_DETAILS_SETTINGS action
dat=package:com.example.android.apis data
cmp=com.android.settings/.applications.InstalledAppDetails
} from pid 228
*/
Intent intent = new Intent();
intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
intent.setData(Uri.parse("package:"+appInfo.getPackagName()));
startActivity(intent);
}
分享# (重点)
1.分享
/**
* Intent
{
act=android.intent.action.SEND
typ=text/plain
flg=0x3000000
cmp=com.android.mms/.ui.ComposeMessageActivity (has extras) intent中包含信息
} from pid 228
*/
Intent intent = new Intent();
intent.setAction("android.intent.action.SEND");
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, "发现一个很牛x软件"+appInfo.getName()+",下载地址:www.baidu.com,自己去搜");
startActivity(intent);
2.如果是应用能够进行分享操作
a.在activity中添加意图
<!-- 表名应用程序能够接受分享信息 -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
b.接受分享信息
Intent intent = getIntent();
String stringExtra = intent.getStringExtra(Intent.EXTRA_TEXT);
if (!TextUtils.isEmpty(stringExtra)) {
iv_share.setText(stringExtra);
}
shareSDK
获取可用空间#
1.apputil
/**
* 获取SD卡可用空间
*/
public static long getAvailableSD(){
//获取SD卡路径
File path = Environment.getExternalStorageDirectory();
//硬盘的API操作
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();//获取每块的大小
long totalBlocks = stat.getBlockCount();//获取总块数
long availableBlocks = stat.getAvailableBlocks();//获取可用的块数
return availableBlocks*blockSize;
}
/**
*获取内存可用空间
* @return
*/
public static long getAvailableROM(){
//获取内存路径
File path = Environment.getDataDirectory();
//硬盘的API操作
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();//获取每块的大小
long totalBlocks = stat.getBlockCount();//获取总块数
long availableBlocks = stat.getAvailableBlocks();//获取可用的块数
return availableBlocks*blockSize;
}
2.使用
//获取可用内存,获取都是kb
long availableSD = AppUtil.getAvailableSD();
long availableROM = AppUtil.getAvailableROM();
//数据转化
String sdsize = Formatter.formatFileSize(getApplicationContext(), availableSD);
String romsize = Formatter.formatFileSize(getApplicationContext(), availableROM);
//设置显示
tv_softmanager_sd.setText("SD卡可用:"+sdsize);
tv_softmanager_rom.setText("内存可用:"+romsize);
获取系统所有进程信息# (重点)
/**
* 获取系统中所有进程信息
* @return
*/
public static List<TaskInfo> getTaskAllInfo(Context context){
List<TaskInfo> list = new ArrayList<TaskInfo>();
//1.进程的管理者
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
PackageManager pm = context.getPackageManager();
//2.获取所有正在运行的进程信息
List<RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
//遍历集合
for (RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses) {
TaskInfo taskInfo = new TaskInfo();
//3.获取相应的信息
//获取进程的名称,获取包名
String packagName = runningAppProcessInfo.processName;
taskInfo.setPackageName(packagName);
//获取进程所占的内存空间,int[] pids : 输入几个进程的pid,就会返回几个进程所占的空间
MemoryInfo[] memoryInfo = activityManager.getProcessMemoryInfo(new int[]{runningAppProcessInfo.pid});
int totalPss = memoryInfo[0].getTotalPss();
long ramSize = totalPss*1024;
taskInfo.setRamSize(ramSize);
try {
//获取application信息
//packageName : 包名 flags:指定信息标签
ApplicationInfo applicationInfo = pm.getApplicationInfo(packagName, 0);
//获取图标
Drawable icon = applicationInfo.loadIcon(pm);
taskInfo.setIcon(icon);
//获取名称
String name = applicationInfo.loadLabel(pm).toString();
taskInfo.setName(name);
//获取程序的所有标签信息,是否是系统程序是以标签的形式展示
int flags = applicationInfo.flags;
boolean isUser;
//判断是否是用户程序
if ((applicationInfo.FLAG_SYSTEM & flags) == applicationInfo.FLAG_SYSTEM) {
//系统程序
isUser = false;
}else{
//用户程序
isUser = true;
}
//保存信息
taskInfo.setUser(isUser);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
//taskinfo添加到集合
list.add(taskInfo);
}
return list;
}
进程管理界面# (重点:思路)
按照软件管理思路一步步复制
checkboxBug解决# (重点)
问题:checkbox的状态会跟着控件一起复用,
解决:所以将状态保存到bean类中,根据bean中保存的状态在getview中进行动态设置
1.将状态设置到bean类中
//checkbox是否被选中
private boolean isChecked = false;
2.在getview中进行动态判断设置
//因为checkbox的状态会跟着一起复用,所以一般要动态修改的控件的状态,不会跟着去复用,而是将状态保存到bean对象,在每次复用使用控件的时候
//根据每个条目对应的bean对象保存的状态,来设置控件显示的相应的状态
if (taskinfo.isChecked()) {
viewHolder.cb_itemtaskmanager_ischecked.setChecked(true);
}else{
viewHolder.cb_itemtaskmanager_ischecked.setChecked(false);
}
3.屏蔽checkbox的获取焦点和点击事件
<CheckBox
android:id="@+id/cb_itemtaskmanager_ischecked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="15dp"
android:focusable="false"
android:clickable="false"
/>
4.屏蔽之后,发现点击条目无法更改checkbox的状态,所以设置条目点击事件进行更改
/**
* listview条目点击事件
*/
private void listviewItemClick() {
lv_taskmanager_processes.setOnItemClickListener(new OnItemClickListener() {
//view : 条目的view对象
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//动态改变checkbox状态
//1.屏蔽用户程序和系统程序(...个)弹出气泡
if (position == 0 || position == userappinfo.size()+1) {
return;
}
//2.获取条目所对应的应用程序的信息
//数据就要从userappinfo和systemappinfo中获取
if (position <= userappinfo.size()) {
//用户程序
taskInfo = userappinfo.get(position-1);
}else{
//系统程序
taskInfo = systemappinfo.get(position - userappinfo.size() - 2);
}
//3.根据之前保存的checkbox的状态设置点击之后的状态,原先选中,点击之后不选中
if (taskInfo.isChecked()) {
taskInfo.setChecked(false);
}else{
taskInfo.setChecked(true);
}
//4.更新界面
//myadapter.notifyDataSetChanged();
//只更新点击的条目
ViewHolder viewHolder = (ViewHolder) view.getTag();
viewHolder.cb_itemtaskmanager_ischecked.setChecked(taskInfo.isChecked());
}
});
}
5.单独更新单个条目
因为控件是保存在ViewHolder控件容器中,而容器又是在listview条目展示的时候通过view.settag和view对象进行绑定了,那我们就可以在条目点击事件进行使用处理,因为在条目点击事件中有view参数,这个参数代表就是点击条目的view对象,通过view.gettag获取到和view对象绑定的容器,从容器中获取控件,设置控件的状态了
全选和取消#
/**
* 全选
* @param v
*/
public void all(View v){
//用户进程
for (int i = 0; i < userappinfo.size(); i++) {
userappinfo.get(i).setChecked(true);
}
//系统进程
for (int i = 0; i < systemappinfo.size(); i++) {
systemappinfo.get(i).setChecked(true);
}
//更新界面
myadapter.notifyDataSetChanged();
}
/**
* 取消
* @param v
*/
public void cancel(View v){
//用户进程
for (int i = 0; i < userappinfo.size(); i++) {
userappinfo.get(i).setChecked(false);
}
//系统进程
for (int i = 0; i < systemappinfo.size(); i++) {
systemappinfo.get(i).setChecked(false);
}
//更新界面
myadapter.notifyDataSetChanged();
}
清理进程# (重点:五个进程)
android进程
1.前台进程 正在操作的进程,正在运行进程,不可杀死
2.可见进程 没有焦点,但是可见的进程
3.服务进程 包含服务的进程,当内存不足的时候,会被杀掉,当内存充足的时候,重新服务进程
4.后台进程 点击home键,最小化
5.空进程 退出,为了下次能够快速启动
后台进程和空进程是可以通过代码杀死
/**
* 清理
* @param v
*/
public void clear(View v){
//1.获取进程的管理者
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
//保存杀死进程信息的集合
List<TaskInfo> deleteTaskInfos = new ArrayList<TaskInfo>();
for (int i = 0; i < userappinfo.size(); i++) {
if (userappinfo.get(i).isChecked()) {
//杀死进程
//packageName : 进程的包名
//杀死后台进程
am.killBackgroundProcesses(userappinfo.get(i).getPackageName());
deleteTaskInfos.add(userappinfo.get(i));//将杀死的进程信息保存的集合中
}
}
//系统进程
for (int i = 0; i < systemappinfo.size(); i++) {
if (systemappinfo.get(i).isChecked()) {
//杀死进程
//packageName : 进程的包名
//杀死后台进程
am.killBackgroundProcesses(systemappinfo.get(i).getPackageName());
deleteTaskInfos.add(systemappinfo.get(i));//将杀死的进程信息保存的集合中
}
}
//遍历deleteTaskInfos,分别从userappinfo和systemappinfo中删除deleteTaskInfos中的数据
for (TaskInfo taskInfo : deleteTaskInfos) {
if (taskInfo.isUser()) {
userappinfo.remove(taskInfo);
}else{
systemappinfo.remove(taskInfo);
}
}
//为下次清理进程做准备
deleteTaskInfos.clear();
deleteTaskInfos=null;
//更新界面
myadapter.notifyDataSetChanged();
}
权限
<uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES"/>
清理细节处理#
1.设置清理显示
long memory=0;
//遍历deleteTaskInfos,分别从userappinfo和systemappinfo中删除deleteTaskInfos中的数据
for (TaskInfo taskInfo : deleteTaskInfos) {
if (taskInfo.isUser()) {
userappinfo.remove(taskInfo);
}else{
systemappinfo.remove(taskInfo);
}
memory+=taskInfo.getRamSize();
}
//数据转化
String deletesize = Formatter.formatFileSize(getApplicationContext(), memory);
Toast.makeText(getApplicationContext(), "共清理"+deleteTaskInfos.size()+"个进程,释放"+deletesize+"内存空间", 0).show();
2.设置当前应用不能选中
在条目中处理
//3.根据之前保存的checkbox的状态设置点击之后的状态,原先选中,点击之后不选中
if (taskInfo.isChecked()) {
taskInfo.setChecked(false);
}else{
//如果是当前应用不能设置成true
if (!taskInfo.getPackageName().equals(getPackageName())) {
taskInfo.setChecked(true);
}
}
在全选中处理
//用户进程
for (int i = 0; i < userappinfo.size(); i++) {
if (!userappinfo.get(i).getPackageName().equals(getPackageName())) {
userappinfo.get(i).setChecked(true);
}
}
3.设置当前应用的checkbox隐藏
//判断如果是我们的应用程序,就把checkbox隐藏,不是的话显示,在getview中有if必须有else
if (taskinfo.getPackageName().equals(getPackageName())) {
viewHolder.cb_itemtaskmanager_ischecked.setVisibility(View.INVISIBLE);
}else{
viewHolder.cb_itemtaskmanager_ischecked.setVisibility(View.VISIBLE);
}
Day11##
运行进程个数以及剩余总内存# (重点:sdk版本)
1.获取操作
public class TaskUtil {
/**
* 获取正在运行的进程的个数
* @return
*/
public static int getProcessCount(Context context){
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses();
return runningAppProcesses.size();
}
/**
* 获取剩余内存
* @return
*/
public static long getAvailableRam(Context context){
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//获取内存的信息,保存到memoryinfo中
MemoryInfo outInfo = new MemoryInfo();
am.getMemoryInfo(outInfo);
//获取空闲的内存
//outInfo.availMem;
// //获取总的内存
// outInfo.totalMem;
return outInfo.availMem;
}
/**
* 获取总的内存
* @return
* @deprecated
*/
public static long getTotalRam(Context context){
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//获取内存的信息,保存到memoryinfo中
MemoryInfo outInfo = new MemoryInfo();
am.getMemoryInfo(outInfo);
//获取空闲的内存
//outInfo.availMem;
// //获取总的内存
// outInfo.totalMem;
return outInfo.totalMem;//16版本之上才有,之下是没有的
}
/**
* 兼容低版本
* @return
*/
public static long getTotalRam(){
File file = new File("/proc/meminfo");
StringBuilder sb = new StringBuilder();
try {
//读取文件
BufferedReader br = new BufferedReader(new FileReader(file));
String readLine = br.readLine();
//获取数字
char[] charArray = readLine.toCharArray();
for (char c : charArray) {
if (c>='0' && c<='9') {
sb.append(c);
}
}
String string = sb.toString();
//转化成long
long parseLong = Long.parseLong(string);
return parseLong*1024;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return 0;
}
}
2.使用
tv_taskmanager_processes = (TextView) findViewById(R.id.tv_taskmanager_processes);
tv_taskmanager_freeandtotalram = (TextView) findViewById(R.id.tv_taskmanager_freeandtotalram);
// 设置显示数据
// 获取相应的数据
// 获取运行的进程个数
processCount = TaskUtil.getProcessCount(getApplicationContext());
tv_taskmanager_processes.setText("运行中进程:\n" + processCount + "个");
// 获取剩余,总内存'
long availableRam = TaskUtil.getAvailableRam(getApplicationContext());
// 数据转化
String availaRam = Formatter.formatFileSize(getApplicationContext(),
availableRam);
// 获取总内存
// 根据不同的sdk版去调用不同的方法
// 1.获取当前的sdk版本
int sdk = android.os.Build.VERSION.SDK_INT;
long totalRam;
if (sdk >= 16) {
totalRam = TaskUtil.getTotalRam(getApplicationContext());
} else {
totalRam = TaskUtil.getTotalRam();
}
// 数据转化
String totRam = Formatter.formatFileSize(getApplicationContext(),
totalRam);
tv_taskmanager_freeandtotalram.setText("剩余/总内存:\n" + availaRam + "/"
+ totRam);
3.清理操作
// 更改运行中的进程个数以及剩余总内存
processCount = processCount - deleteTaskInfos.size();
tv_taskmanager_processes.setText("运行中进程:\n" + processCount + "个");
// 更改剩余总内存,重新获取剩余总内存
// 获取剩余,总内存'
long availableRam = TaskUtil.getAvailableRam(getApplicationContext());
// 数据转化
String availaRam = Formatter.formatFileSize(getApplicationContext(),
availableRam);
// 获取总内存
// 根据不同的sdk版去调用不同的方法
// 1.获取当前的sdk版本
int sdk = android.os.Build.VERSION.SDK_INT;
long totalRam;
if (sdk >= 16) {
totalRam = TaskUtil.getTotalRam(getApplicationContext());
} else {
totalRam = TaskUtil.getTotalRam();
}
// 数据转化
String totRam = Formatter.formatFileSize(getApplicationContext(),
totalRam);
tv_taskmanager_freeandtotalram.setText("剩余/总内存:\n" + availaRam + "/"
+ totRam);
桌面小控件#
1.创建appwidgetprovider
public class MyWidget extends AppWidgetProvider {
}
2.清单文件配置
<receiver android:name="com.example.widgets.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>
3.xml -> xxx.xml
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="275dp"
android:minHeight="72dp"
android:updatePeriodMillis="0"
android:initialLayout="@layout/example_appwidget"
android:resizeMode="horizontal|vertical"
>
</appwidget-provider>
<!--
minWidth,minHeight : 最小的宽高
updatePeriodMillis : 更新时间,毫秒值,最短不能小于30分钟,设置0跟30分钟一个效果
previewImage : 预览图片,不设置默认使用应用图标
initialLayout :widget布局文件
configure : 可选数据,创建widget之后会启动那个activity
resizeMode : 设置widget显示尺寸规则,主要用来设置屏幕横竖屏切换保证widget布局不会变形
widgetCategory : 设置widget显示在什么地方 ,home_screen : 桌面 keyguard:键盘
initialKeyguardLayout : 设置锁屏的时候显示控件的布局
-->
4.带有@Remoteview注解控件是可以显示在桌面小控件上的
桌面小控件生命周期#
第一次创建
10-20 02:08:34.418: I/System.out(4570): onReceive
10-20 02:08:34.418: I/System.out(4570): onEnabled
10-20 02:08:34.418: I/System.out(4570): onReceive
10-20 02:08:34.418: I/System.out(4570): onUpdate
10-20 02:08:37.788: I/System.out(4570): onReceive
第二次创建
10-20 02:09:27.292: I/System.out(4570): onReceive
10-20 02:09:27.292: I/System.out(4570): onUpdate
10-20 02:09:31.439: I/System.out(4570): onReceive
第三次创建
10-20 02:09:56.408: I/System.out(4570): onReceive
10-20 02:09:56.408: I/System.out(4570): onUpdate
10-20 02:10:00.769: I/System.out(4570): onReceive
第一次删除
10-20 02:10:28.303: I/System.out(4570): onReceive
10-20 02:10:28.303: I/System.out(4570): onDeleted
第二次删除
10-20 02:10:45.319: I/System.out(4570): onReceive
10-20 02:10:45.319: I/System.out(4570): onDeleted
删除最后一个
10-20 02:11:09.370: I/System.out(4570): onReceive
10-20 02:11:09.370: I/System.out(4570): onDeleted
10-20 02:11:09.380: I/System.out(4570): onReceive
10-20 02:11:09.380: I/System.out(4570): onDisabled
1.第一次创建的时候调用onEnabled
2.不管什么操作都会调用onReceive
3.每次创建都会调用onUpdate
4.每次删除都会调用onDeleted
5.删除最后一个会调用onDisabled
桌面小控件移植到手机卫士# (重点:反编译+如何找资源控件)
1.反编译金山卫士apk
2.根据广播事件去清单文件中找widget
3.根据元数据找到xml -> xxx.xml
4.根据xxx.xml下initialLayout找到布局文件
5.拷贝布局文件,根据错误进行修改
更新widget中textview#
1.创建服务
2.在widget的onEnabled开启服务,在onDisabled关闭服务
3.更新
//widget的管理者
appWidgetManager = AppWidgetManager.getInstance(this);
/**
* 更新widget
*/
private void updateWidgets() {
/*new Thread(){
public void run() {
while(true){
SystemClock.sleep(2000);
}
};
}.start();*/
//计数器
Timer timer = new Timer();
//执行操作
//task : 要执行操作
//when : 延迟的时间
//period : 每次执行的间隔时间
timer.schedule(new TimerTask() {
@Override
public void run() {
//更新操作
//获取组件的标示
ComponentName provider = new ComponentName(WidgetService.this, MyWidget.class);
//获取远程布局
//packageName : 应用的包名
//layoutId :widget布局文件
RemoteViews views = new RemoteViews(getPackageName(), R.layout.process_widget);
//远程布局不能通过findviewbyid获取初始化控件
//更新布局文件中相应控件的值
//viewId :更新控件的id
//text : 更新的内容
views.setTextViewText(R.id.process_count, "正在运行软件:"+TaskUtil.getProcessCount(WidgetService.this));
views.setTextViewText(R.id.process_memory, "可用内存:"+Formatter.formatFileSize(WidgetService.this, TaskUtil.getAvailableRam(WidgetService.this)));
//更新操作
appWidgetManager.updateAppWidget(provider, views);
}
}, 2000, 2000);
}
一键清理# (重点:发送自定义广播+接受自定义广播+pendingIntente)
1.在timer中进行操作
//pendingIntent : 延迟意图 包含一个intent意图,当点击的才去执行这个意图,不点击就不执行意图
views.setOnClickPendingIntent(R.id.btn_clear, pendingIntent);
2.创建pendingIntent
//通过发送一个广播去表示要执行清理操作,通过接受发送的广播执行清理操作
//flags : 指定信息的标签
PendingIntent pendingIntent = PendingIntent.getBroadcast(WidgetService.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
3.pedingintent中需要intent,创建intent,设置发送广播事件
//按钮点击事件
Intent intent = new Intent();
intent.setAction("aa.bb.cc");//设置要发送的广播,aa.bb.cc:自定义的广播事件
//sendBroadcast(intent);
4.创建清理进程广播接受者接受广播
a.创建广播接受者
/**
* 清理进程的广播接受者
* @author Administrator
*
*/
private class WidgetReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//清理进程
killProcess();
}
}
b.注册广播接受者
//注册清理进程广播接受者
//1.广播接受者
widgetReceiver = new WidgetReceiver();
//2.设置接受的广播事件
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("aa.bb.cc");
//3.注册广播接受者
registerReceiver(widgetReceiver, intentFilter);
c.注销广播接受者
//注销清理进程的广播接受者
if (widgetReceiver != null) {
unregisterReceiver(widgetReceiver);
widgetReceiver = null;
}
d.清理进程
/**
* 清理进程
*/
public void killProcess() {
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
//获取正在运行进程
List<RunningAppProcessInfo> runningAppProcesses = am.getRunningAppProcesses();
for (RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses) {
//判断我们的应用进程不能被清理
if (!runningAppProcessInfo.processName.equals(getPackageName())) {
am.killBackgroundProcesses(runningAppProcessInfo.processName);
}
}
}
5.关闭服务的,停止更新
//停止更新widget
if (timer != null) {
timer.cancel();
timer = null;
}
隐藏显示系统进程#
1.创建一个标示
//是否显示系统进程的标示
private boolean isshowSystem = true;
2.在adapter的getCount方法根据标示设置显示的条目个数
@Override
public int getCount() {
// TODO Auto-generated method stub
// list.size() = userappinfo.size()+systemappinfo.size()
return isshowSystem==true ? userappinfo.size() + 1 + systemappinfo.size() + 1 : userappinfo.size() + 1;
}
3.在设置的点击事件中更改标示
/**
* 设置
*
* @param v
*/
public void setting(View v) {
//true 改为false false改为true
isshowSystem = !isshowSystem;
//更新界面
myadapter.notifyDataSetChanged();
}
锁屏和解锁的操作# (重点)
android锁屏和解锁广播接受者比较特别,不能在清单文件中注册,比如通过代码去注册
1.锁屏清理进程
a.代码注册锁屏的广播接受者
/**
* 锁屏的广播接受者
* @author Administrator
*
*/
private class ScreenOffReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("锁屏了.....");
}
}
b.注册
/**
* 锁屏的广播接受者
* @author Administrator
*
*/
private class ScreenOffReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("锁屏了.....");
//清理进程
killProcess();
//停止更新
stopUpdates();
}
}
c.注销
if (screenOffReceiver != null) {
unregisterReceiver(screenOffReceiver);
screenOffReceiver = null;
}
d.锁屏清理进程
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("锁屏了.....");
//清理进程
killProcess();
}
2.锁屏停止更新,解锁重新更新
a.在锁屏中停止更新
/**
* 锁屏的广播接受者
* @author Administrator
*
*/
private class ScreenOffReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
System.out.println("锁屏了.....");
//清理进程
killProcess();
//停止更新
stopUpdates();
}
}
b.注册解锁的广播接受者
//注册解锁的广播接受者
screenOnReceiver = new ScreenOnReceiver();
IntentFilter screenOnIntentFilter = new IntentFilter();
screenOnIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
registerReceiver(screenOnReceiver, screenOnIntentFilter);
c.解锁重新进行更新
/**
* 解锁的广播接受者
* @author Administrator
*
*/
private class ScreenOnReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
updateWidgets();
}
}
widgetBug解决#
将onEnabled开启服务的操作,移植onUpdate方法中,因为更新时间到了之后就会调用onUpdate方法
屏幕适配# (重点)
1.dp -> px
public class DensityUtil {
/**
* 根据手机的分辨率从 dip 的单位 转成为 px(像素)
* @return
*/
public static int dip2qx(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density; //获取屏幕的密度
return (int) (dpValue * scale + 0.5f); //+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);
}
}
2.ScrollView
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:itheima="http://schemas.android.com/apk/res/com.itheima.mobliesafe75"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- ScrollView : 只能有一个子控件 -->
创建快捷方式# (重点)
/**
* 创建快捷方式
*/
private void shortcut() {
if (sp.getBoolean("firstshortcut", true)) {
// 给桌面发送一个广播
Intent intent = new Intent(
"com.android.launcher.action.INSTALL_SHORTCUT");
// 设置属性
// 设置快捷方式名称
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "手机卫士75");
// 设置快捷方式的图标
Bitmap value = BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, value);
// 设置快捷方式执行的操作
Intent intent2 = new Intent();
intent2.setAction("com.itheima.mobliesafe75.home");
intent2.addCategory("android.intent.category.DEFAULT");
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent2);
sendBroadcast(intent);
//保存已经创建快捷方式的状态
Editor edit = sp.edit();
edit.putBoolean("firstshortcut", false);
edit.commit();
}
}
监听用户代开应用程序# (重点)
时时刻刻监听某些操作行为 , watch dog 看门狗
//时时刻刻监听用户打开的程序
//activity都是存放在任务栈中的,一个应用只有一个任务栈
final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
new Thread(){
public void run() {
while(true){
//监听操作
//监听用户打开了哪些任务栈,打开哪些应用
//获取正在运行的任务栈,如果任务栈运行,就表示应用打开过
//maxNum : 获取正在运行的任务栈的个数
//现在打开的应用的任务栈,永远在第一个,而之前(点击home最小化,没有退出)的应用的任务栈会依次往后推
List<RunningTaskInfo> runningTasks = am.getRunningTasks(1);
System.out.println("----------------------");
for (RunningTaskInfo runningTaskInfo : runningTasks) {
//获取任务栈,栈底的activity
ComponentName baseactivity = runningTaskInfo.baseActivity;
/*//获取任务栈栈顶的activity
runningTaskInfo.topActivity;*/
String packageName = baseactivity.getPackageName();
System.out.println(packageName);
if (packageName.equals("com.android.mms")) {
Intent intent = new Intent(WatchDogService.this,MainActivity.class);
//因为activity保存在任务栈中,而服务,广播没有保存在任务栈中的,所以在服务,广播中跳转activity必须给activity设置任务栈
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
System.out.println("----------------------");
SystemClock.sleep(3000);
}
};
}.start();
看门狗数据库操作# (重点)
1.创建数据库
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) {
}
}
2.操作,拷贝blacknumdao,修改
public class WatchDogDao {
private WatchDogOpenHelper watchDogOpenHelper;
private byte[] b = new byte[1024];
//在构造函数中获取BlackNumOpenHlper
public WatchDogDao(Context context){
watchDogOpenHelper = new WatchDogOpenHelper(context);
}
//增删改查
//面试:我在同一时刻对数据库既进行读操作也进行写操作,怎么避免这个两个操作同时操作数据库,同步锁+将WatchDogOpenHelper设置成单例模式
/**
* 添加应用程序包名
* @param blacknum
* @param mode
*/
public void addLockApp(String packageName){
// synchronized (b) {
//1.获取数据库
SQLiteDatabase database = watchDogOpenHelper.getWritableDatabase();
//2.添加操作
//ContentValues : 添加的数据
ContentValues values = new ContentValues();
values.put("packagename", packageName);
database.insert(WatchDogOpenHelper.DB_NAME, null, values);
//3.关闭数据库
database.close();
// }
}
Day12##
加锁解锁#
1.在item的布局文件中增加imageview
2.在getview中判断是否加锁
//判断应用程序是加锁还是解锁
if (watchDogDao.queryLockApp(appInfo.getPackagName())) {
//加锁
viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.lock);
}else{
//解锁
viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.unlock);
}
3.使用长按点击事件实现加锁解锁
/**
* 长按点击事件
*/
private void listviewItemLongClick() {
lv_softmanager_application.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
System.out.println("长按点击事件");
//true if the callback consumed the long click, false otherwise
//true:表示执行 false:拦截
//加锁解锁的操作
//屏蔽用户和系统程序(..个)不能加锁解锁操作
if (position == 0 || position == userappinfo.size()+1) {
return true;
}
//获取数据
if (position <= userappinfo.size()) {
//用户程序
appInfo = userappinfo.get(position-1);
}else{
//系统程序
appInfo = systemappinfo.get(position - userappinfo.size() - 2);
}
//加锁解锁
ViewHolder viewHolder = (ViewHolder) view.getTag();
//判断应用有没有加锁,有的解锁,没有的加锁
if (watchDogDao.queryLockApp(appInfo.getPackagName())) {
//解锁操作
watchDogDao.deleteLockApp(appInfo.getPackagName());
viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.unlock);
}else{
//加锁操作
watchDogDao.addLockApp(appInfo.getPackagName());
viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.lock);
}
//myadapter.notifyDataSetChanged();
return true;
}
});
}
监听用户打开程序#
1.创创建解锁界面
2.在服务中,监听用户打开程序的操作中,通过查询数据库的方式判断打开的应用程序是否加锁
//通过查询数据库,如果数据库中有,弹出解锁界面,没有不作处理
if (watchDogDao.queryLockApp(packageName)) {
//弹出解锁界面
Intent intent = new Intent(WatchDogService.this,WatchDogActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
3.在解锁界面点击返回键,重复弹出解锁界面
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
/**
* Starting: Intent {
act=android.intent.action.MAIN
cat=[android.intent.category.HOME
] cmp=com.android.launcher/com.android.launcher2.Launcher } from pid 208
*/
//跳转到主界面
Intent intent = new Intent();
intent.setAction("android.intent.action.MAIN");
intent.addCategory("android.intent.category.HOME");
startActivity(intent);
finish();
}
return super.onKeyDown(keyCode, event);
}
4.不能给当前应用程序加锁
//判断如果是当前应用程序,就不要加锁
if (!appInfo.getPackagName().equals(getPackageName())) {
watchDogDao.addLockApp(appInfo.getPackagName());
viewHolder.iv_itemsoftmanager_islock.setImageResource(R.drawable.lock);
}else{
Toast.makeText(getApplicationContext(), "当前的应用程序不能加锁", 0).show();
}
解锁界面优化#
1.在服务跳转activity的操作中将加锁应用程序的包名传递给解锁界面
Intent intent = new Intent(WatchDogService.this,WatchDogActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("packageName", packageName);
startActivity(intent);
2.在解锁界面获取传递的数据,并通过包名获取图片和名称
//接受获取数据
Intent intent = getIntent();
packagename = intent.getStringExtra("packageName");
//设置显示加锁的应用程序的图片和名称
PackageManager pm = getPackageManager();
try {
ApplicationInfo applicationInfo = pm.getApplicationInfo(packagename, 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();
}
3.解决,点击home最小,打开其他加锁程序,显示上一个加锁程序的图片和名称的bug
@Override
protected void onStop() {
super.onStop();
finish();
}
解锁操作# (重点:发送广播)
1.通过发送广播的方式,去给服务发送解锁信息
if ("123".equals(password)) {
//解锁
//一般通过广播的形式将信息发送给服务
Intent intent = new Intent();
intent.setAction("com.itheima.mobliesafe75.unlock");//自定义发送广播事件
intent.putExtra("packagename", packagename);
sendBroadcast(intent);
finish();
}else{
Toast.makeText(getApplicationContext(), "密码错误", 0).show();
}
2.在服务中,通过注册广播的形式实现接受信息并进行解锁操作
注册广播接受者
//注册解锁的广播接受者
//1.广播接受者
unlockCurrentReceiver = new UnlockCurrentReceiver();
//2.设置接受的广播事件
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.itheima.mobliesafe75.unlock");
接受信息
private class UnlockCurrentReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//解锁操作
unlockcurrentPackagname = intent.getStringExtra("packagename");
}
}
解锁操作
//通过查询数据库,如果数据库中有,弹出解锁界面,没有不作处理
if (watchDogDao.queryLockApp(packageName)) {
if (!packageName.equals(unlockcurrentPackagname)) {
//弹出解锁界面
Intent intent = new Intent(WatchDogService.this,WatchDogActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("packageName", packageName);
startActivity(intent);
}
}
3.加锁操作,锁屏之后重新加锁
a.注册锁屏的广播接受者
//注册锁屏的广播接受者
screenOffReceiver = new ScreenOffReceiver();
IntentFilter screenOffIntentFilter = new IntentFilter();
screenOffIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(screenOffReceiver, screenOffIntentFilter);
b.在锁屏的广播接受者中将包名表示设置为null,表示加锁操作
private class ScreenOffReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
//加锁的操作
unlockcurrentPackagname = null;
}
}
两个bug解决方案# (重点:启动模式)
1.启动模式
<activity android:name=".WatchDogActivity"
android:launchMode="singleInstance"
></activity>
<!--
standard : 标准的启动模式
singleTop : 如果activity在栈顶,直接使用
singleTask : 如果任务栈中有activity的,之前的activity删除,将activity置顶
singleInstance : 将activity单独存放一个任务栈中
-->
2.从最近列表中删除手机卫士应用
android:excludeFromRecents="true"
excludeFromRecents : 是否在最近列表中显示 true:不显示 false:显示,只有设置这个属性的activity运行了,这个属性才会生效
数据库优化# (重点:内容观察者)
1.当数据库发生变化,通知内容观察者进行更新数据,在数据库操作中,比如添加,删除
//通知内容观察者数据库变化了
ContentResolver contentResolver = context.getContentResolver();
//因为是我们自己的数据发生变化了,所以我们要自定义一个uri进行操作
Uri uri = Uri.parse("content://com.itheima.mobliesafe75.lock.changed");
//通知内容观察者数据发生变化了
contentResolver.notifyChange(uri, null);
2.使用内容观察者进行数据更新操作
//先将数据库中的数据,查询存放到内存,然后再把数据从内存中获取出来进行操作
//当数据库变化的时候重新更新内存中的数据,当数据库变化的时候通知内容观察者数据库变化了,然后在内容观察者中去更新最新的数据
Uri uri = Uri.parse("content://com.itheima.mobliesafe75.lock.changed");
//notifyForDescendents:匹配规则,true:精确匹配 false:模糊匹配
getContentResolver().registerContentObserver(uri, true, new ContentObserver(null) {
public void onChange(boolean selfChange) {
//更新数据
list = watchDogDao.querAllLockApp();
};
});
3.使用内存中的数据,使用list集合中的数据
//判断list集合中是否包含包名
boolean b = list.contains(packageName);
//通过查询数据库,如果数据库中有,弹出解锁界面,没有不作处理
if (b) {
if (!packageName.equals(unlockcurrentPackagname)) {
//弹出解锁界面
Intent intent = new Intent(WatchDogService.this,WatchDogActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("packageName", packageName);
startActivity(intent);
}
}
获取短信#
/**
* 获取短信
*/
public static void getAllSMS(Context context){
//1.获取内容解析者
ContentResolver resolver = context.getContentResolver();
//2.获取内容提供者地址 sms,sms表的地址:null 不写
//3.获取查询路径
Uri uri = Uri.parse("content://sms");
//4.查询操作
//projection : 查询的字段
//selection : 查询的条件
//selectionArgs : 查询条件的参数
//sortOrder : 排序
Cursor cursor = resolver.query(uri, new String[]{"address","date","type","body"}, null, null, null);
//5.解析cursor
while(cursor.moveToNext()){
String address = cursor.getString(0);
String date = cursor.getString(1);
String type = cursor.getString(2);
String body = cursor.getString(3);
System.out.println("address:"+address+" date:"+date+" type:"+type+" body:"+body);
}
}
权限
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
备份短信# (重点:创建xml文件)
1.数据库
2.文件 xml
/**
* 获取短信
*/
public static void getAllSMS(Context context){
//1.获取短信
//1.1获取内容解析者
ContentResolver resolver = context.getContentResolver();
//1.2获取内容提供者地址 sms,sms表的地址:null 不写
//1.3获取查询路径
Uri uri = Uri.parse("content://sms");
//1.4.查询操作
//projection : 查询的字段
//selection : 查询的条件
//selectionArgs : 查询条件的参数
//sortOrder : 排序
Cursor cursor = resolver.query(uri, new String[]{"address","date","type","body"}, null, null, null);
//2.备份短信
//2.1获取xml序列器
XmlSerializer xmlSerializer = Xml.newSerializer();
try {
//2.2设置xml文件保存的路径
//os : 保存的位置
//encoding : 编码格式
xmlSerializer.setOutput(new FileOutputStream(new File("/mnt/sdcard/backupsms.xml")), "utf-8");
//2.3设置头信息
//standalone : 是否独立保存
xmlSerializer.startDocument("utf-8", true);
//2.4设置根标签
xmlSerializer.startTag(null, "smss");
//1.5.解析cursor
while(cursor.moveToNext()){
//2.5设置短信的标签
xmlSerializer.startTag(null, "sms");
//2.6设置文本内容的标签
xmlSerializer.startTag(null, "address");
String address = cursor.getString(0);
//2.7设置文本内容
xmlSerializer.text(address);
xmlSerializer.endTag(null, "address");
xmlSerializer.startTag(null, "date");
String date = cursor.getString(1);
xmlSerializer.text(date);
xmlSerializer.endTag(null, "date");
xmlSerializer.startTag(null, "type");
String type = cursor.getString(2);
xmlSerializer.text(type);
xmlSerializer.endTag(null, "type");
xmlSerializer.startTag(null, "body");
String body = cursor.getString(3);
xmlSerializer.text(body);
xmlSerializer.endTag(null, "body");
xmlSerializer.endTag(null, "sms");
System.out.println("address:"+address+" date:"+date+" type:"+type+" body:"+body);
}
xmlSerializer.endTag(null, "smss");
xmlSerializer.endDocument();
//2.8将数据刷新到文件中
xmlSerializer.flush();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
备份效果# (重点:progressdialog+回调函数)
1.progerssdialog
//progressdialog是可以在子线程中更新
progressDialog = new ProgressDialog(this);
progressDialog.setCancelable(false);//不能取消
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
/*progressDialog.setMax(max);//设置最大进度
progressDialog.setProgress(value);//设置当前进度
*/progressDialog.show();
2.使用progerssbar
progerssbar.setMax(max);//设置最大进度
progerssbar.setProgress(value)
回调函数
1.创建刷子
public interface ShowProgress{
//设置最大进度
public void setMax(int max);
//设置当前进度
public void setProgress(int progerss);
}
2.媳妇儿给你刷子
public static void getAllSMS(Context context,ShowProgress showProgress){
//设置最大进度
int count = cursor.getCount();//获取短信的个数
showProgress.setMax(count);
//设置当前进度
progress++;
showProgress.setProgress(progress);
}
3.我们接受刷子,进行刷牙,刷鞋,刷马桶.....
SMSEngine.getAllSMS(getApplicationContext(),new ShowProgress() {
@Override
public void setProgress(int progerss) {
//progressDialog.setProgress(progerss);
pb_atools_sms.setProgress(progerss);
}
@Override
public void setMax(int max) {
//progressDialog.setMax(max);
pb_atools_sms.setMax(max);
}
});
短信还原#
/**
* 短信还原
* @param v
*/
public void restoresms(View v){
//解析xml
//XmlPullParser xmlPullParser = Xml.newPullParser();
//插入短信
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://sms");
ContentValues values = new ContentValues();
values.put("address", "95588");
values.put("date", System.currentTimeMillis());
values.put("type", 1);
values.put("body", "zhuan zhang le $10000000000000000000");
resolver.insert(uri, values);
}
可扩展的listview#
1.布局文件中
<ExpandableListView
android:id="@+id/exlitview"
android:layout_width="match_parent"
android:layout_height="match_parent"
></ExpandableListView>
2.使用
exlitview = (ExpandableListView) findViewById(R.id.exlitview);
exlitview.setAdapter(new MyAdapter());
private class MyAdapter extends BaseExpandableListAdapter{
//获取组的个数
@Override
public int getGroupCount() {
// TODO Auto-generated method stub
return 8;
}
//获取每组孩子的个数
@Override
public int getChildrenCount(int groupPosition) {
// TODO Auto-generated method stub
return groupPosition+1;
}
//设置组对应的相应的信息
@Override
public Object getGroup(int groupPosition) {
// TODO Auto-generated method stub
return null;
}
//设置每组孩子对应的信息
//groupPosition :组的位置
//childPosition : 孩子的位置
@Override
public Object getChild(int groupPosition, int childPosition) {
// TODO Auto-generated method stub
return null;
}
//获取组的id
@Override
public long getGroupId(int groupPosition) {
// TODO Auto-generated method stub
return 0;
}
//获取每组孩子的id
@Override
public long getChildId(int groupPosition, int childPosition) {
// TODO Auto-generated method stub
return 0;
}
//获取id是否稳定,有没有设置id,有设置,返回true,没有设置返回false
@Override
public boolean hasStableIds() {
// TODO Auto-generated method stub
return false;
}
//设置组的样式
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
TextView textView = new TextView(getApplicationContext());
textView.setText(" 我是第"+groupPosition+"组");
textView.setTextColor(Color.RED);
return textView;
}
//设置每组孩子样式
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
TextView textView = new TextView(getApplicationContext());
textView.setText("我是第"+groupPosition+"组的第"+childPosition+"个孩子");
textView.setTextColor(Color.GREEN);
return textView;
}
//设置孩子是否能够被点击,true:可以 false:不可以
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
// TODO Auto-generated method stub
return false;
}
}
抽屉效果# (重点)
slidingmenu 第三方法框架
1.SlidingDrawer
<SlidingDrawer
android:layout_width="match_parent"
android:layout_height="match_parent"
android:handle="@+id/handle"
android:content="@+id/content"
android:orientation="vertical"
>
<LinearLayout
android:id="@+id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/shenmabg"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff0000"
></LinearLayout>
</SlidingDrawer>
2.android.support.v4.widget.DrawerLayout
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/dl"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="流量统计"
android:background="#8866ff00"
android:gravity="center_horizontal"
android:textSize="22sp"
android:paddingTop="5dp"
android:paddingBottom="5dp"
/>
</RelativeLayout>
<!-- layout_gravity : 在布局中决定控件存在位置 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="right"
android:background="#ff0000"
></LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="left"
android:background="#0000ff"
></LinearLayout>
代码中使用
DrawerLayout dl = (DrawerLayout) findViewById(R.id.dl);
// dl.openDrawer(Gravity.RIGHT);//表示默认打开哪个方向布局
dl.openDrawer(Gravity.LEFT);
Day13##
病毒#
病毒 : 一段带有恶意行为的计算机程序
恶搞 : 炫耀技术,熊猫烧香,损人不利己
灰鸽子 : 盗号,qq,游戏账号
肉机 : 通过一段程序控制的电脑
杀毒软件历史
1.黑名单 : 将已有的病毒保存到数据库中,卡巴斯基,江民,瑞星,金山,问题:不能及时发现病毒,蜜罐,云查杀
2.白名单 : 360 把查杀过的应用发送数据库中
手机端:黑名单
手机杀毒界面#
layer-list : 图层,帧布局framlayout,可以将图片一层叠一层展示,布局文件中最下面的控件,显示的时候在最上面
1.布局
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<ImageView
android:id="@+id/iv_antivirus_anitimageview"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/ic_scanner_malware"
android:layout_margin="10dp"
/>
<ImageView
android:id="@+id/iv_antivirus_scanner"
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/act_scanning_03"
android:layout_margin="10dp"
/>
<TextView
android:id="@+id/tv_antivirus_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="正在扫描..."
android:layout_toRightOf="@id/iv_antivirus_anitimageview"
android:layout_marginTop="25dp"
android:textSize="18sp"
android:layout_marginRight="15dp"
/>
<!-- progress : 设置进度条当前进度 -->
<ProgressBar
android:id="@+id/pb_antivirus_progressbar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tv_antivirus_text"
android:layout_toRightOf="@id/iv_antivirus_anitimageview"
android:layout_marginTop="5dp"
android:layout_marginRight="15dp"
android:progressDrawable="@drawable/progerssbar_antivirus_bg"
android:progress="30"
/>
</RelativeLayout>
2.图层 res->drawable -> xxx.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<bitmap android:src="@drawable/security_progress_bg"
android:gravity="center" />
</item>
<item android:id="@android:id/secondaryProgress">
<bitmap android:src="@drawable/security_progress"
android:gravity="center" />
</item>
<item android:id="@android:id/progress">
<bitmap android:src="@drawable/security_progress"
android:gravity="center" />
</item>
</layer-list>
3.更改样式,progressbar的属性
android:progressDrawable="@drawable/progerssbar_antivirus_bg"
扫描操作# (重点:动画)
//设置旋转动画
//fromDegrees : 旋转开始的角度
//toDegrees : 结束的角度
//后面四个:是以自身变化还是以父控件变化
//Animation.RELATIVE_TO_SELF : 以自身旋转
//Animation.RELATIVE_TO_PARENT : 以父控件旋转
RotateAnimation rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setDuration(1000);//持续时间
rotateAnimation.setRepeatCount(Animation.INFINITE);//设置旋转的次数,INFINITE:一直旋转
//设置动画插补器
LinearInterpolator linearInterpolator = new LinearInterpolator();
rotateAnimation.setInterpolator(linearInterpolator);
iv_antivirus_scanner.startAnimation(rotateAnimation);
展示软件列表 1.listview 2.linearlayout添加textview
/**
* 扫描程序
*/
private void scanner() {
//1.获取包的管理者
final PackageManager pm = getPackageManager();
tv_antivirus_text.setText("正在初始化64核杀毒引擎....");
new Thread(){
public void run() {
//延迟一秒中扫描程序
SystemClock.sleep(100);
//2.获取所有安装应用程序信息
List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
//3.1设置进度条最大进度
pb_antivirus_progressbar.setMax(installedPackages.size());
//3.2设置当前进度
int count=0;
for (PackageInfo packageInfo : installedPackages) {
SystemClock.sleep(100);
//3.设置进度条的最大进度和当前进度
count++;
pb_antivirus_progressbar.setProgress(count);
//4.设置扫描显示软件名称
final String name = packageInfo.applicationInfo.loadLabel(pm).toString();
//在ui线程(主线程)进行操作
runOnUiThread(new Runnable() {//封装了handler
@Override
public void run() {
tv_antivirus_text.setText("正在扫描:"+name);
//6.展示扫描软件的名称信息
TextView textView = new TextView(getApplicationContext());
textView.setTextColor(Color.BLACK);
textView.setText(name);
//textview添加到线性布局中
//ll_antivirus_safeapks.addView(textView);
ll_antivirus_safeapks.addView(textView, 0);//index:将textview添加到线性布局的哪个位置
}
});
};
//5.扫描完成,显示扫描完成信息,同时旋转动画停止
runOnUiThread(new Runnable() {
@Override
public void run() {
//设置显示信息以及停止动画
tv_antivirus_text.setText("扫描完成,未发现病毒");
iv_antivirus_scanner.clearAnimation();//移出动画
}
});
};
}.start();
}
获取签名信息# (重点:指定信息的标签)
1.指定获取签名信息标签
List<PackageInfo> installedPackages = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
2.获取签名信息
//7.获取应用程序的签名,并进行md5加密
Signature[] signatures= packageInfo.signatures;//获取应用程序的签名信息,获取签名的数组
String charsString = signatures[0].toCharsString();
//对签名信息进行MD5加密
String signature = MD5Util.passwordMD5(charsString);
System.out.println(name+" : "+signature);
手机杀毒#
1.将病毒库导入到手机中,参考splashactivity中拷贝数据库操作,增加方法传递参数
2.创建查询数据库操作
//查询Md5值是否在数据库中
public class AntiVirusDao {
/**
* 查询Md5值是否在病毒数据库中
* @param context
* @param md5
* @return
*/
public static boolean queryAntiVirus(Context context,String md5){
boolean ishave = false;
//1.获取数据库的路径
File file = new File(context.getFilesDir(), "antivirus.db");
SQLiteDatabase database = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
Cursor cursor = database.query("datable", null, "md5=?", new String[]{md5}, null, null, null);
if (cursor.moveToNext()) {
ishave = true;
}
cursor.close();
database.close();
return ishave;
}
}
3.使用
//8.查询MD5值是否在病毒数据库中
boolean b = AntiVirusDao.queryAntiVirus(getApplicationContext(), signature);
4.根据查询值,设置病毒标示
//6.展示扫描软件的名称信息
TextView textView = new TextView(getApplicationContext());
//9.根据返回的值将病毒标示出来
if (b) {
//有病毒
textView.setTextColor(Color.RED);
//将病毒的包名添加到list集合
list.add(packageInfo.packageName);
}else{
//没有病毒
textView.setTextColor(Color.BLACK);
}
textView.setText(name);
5.对查询到病毒进行处理
//10.根据是否有病毒进行显示操作
if (list.size() > 0) {
//有病毒
tv_antivirus_text.setText("扫描完成,发现"+list.size()+"个病毒");
//卸载病毒应用
AlertDialog.Builder builder = new Builder(AntivirusActivity.this);
builder.setTitle("提醒!发现"+list.size()+"个病毒");
builder.setIcon(R.drawable.ic_launcher);
builder.setMessage("是否卸载病毒应用!");
builder.setPositiveButton("卸载", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//卸载操作
for (String packagname : list) {
Intent intent = new Intent();
intent.setAction("android.intent.action.DELETE");
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse("package:"+packagname));
startActivity(intent);
}
dialog.dismiss();
}
});
builder.setNegativeButton("取消", null);
builder.show();
}else{
//设置显示信息以及停止动画
tv_antivirus_text.setText("扫描完成,未发现病毒");
}
缓存清理框架# (重点!!!!!!)
1.将activity改为继承fragmentActivity
//如果要对fragment进行管理操作,activity必须继承fragmentActivity
public class ClearCacheActivity extends FragmentActivity {
}
2.设置布局文件
!-- fragment替换的控件 -->
<FrameLayout
android:id="@+id/fram_clearcache_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
></FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="缓存清理"
android:background="@drawable/selector_contact_button"
android:onClick="cache"
/>
<Button
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="SD卡清理"
android:background="@drawable/selector_contact_button"
android:onClick="sd"
/>
</LinearLayout>
3.创建fragment
public class CacheFragment extends Fragment {
//初始化操作
@Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
//设置fragment的布局
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//参数1:布局文件
//参数2:容器
//参数3:自动挂载 ,一律false
View view= inflater.inflate(R.layout.fragment_cache, container, false);
return view;
}
//设置填充显示数据
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
}
}
4.创建fragment布局文件,线性布局,背景是红色
5.在actiivty中添加fragment
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_clearcache);
//挂载fragment
//1.获取fragment
cacheFragment = new CacheFragment();
sdFragment = new SDFragment();
//2.加载fragment
//2.1获取fragment的管理者
supportFragmentManager = getSupportFragmentManager();
//2.2开启事务
FragmentTransaction beginTransaction = supportFragmentManager.beginTransaction();
//2.3添加fragment操作
//数据不会重新刷新
//参数1:fragment要替换的控件的id
//参数2:要替换fragment
beginTransaction.add(R.id.fram_clearcache_fragment, cacheFragment);
//界面展示以最后一个添加的为准
beginTransaction.add(R.id.fram_clearcache_fragment, sdFragment);
//隐藏fragment操作
beginTransaction.hide(sdFragment);
//也可以实现替换操作,数据会重新刷新
//beginTransaction.replace(arg0, arg1)
//2.4提交事务
beginTransaction.commit();
}
6.隐藏显示fragment操作
//缓存清理
public void cache(View v){
//隐藏sdfragment,显示cacheFragment
//1.获取事务
FragmentTransaction beginTransaction = supportFragmentManager.beginTransaction();
//2.隐藏显示操作
beginTransaction.hide(sdFragment);//隐藏fragmnt
beginTransaction.show(cacheFragment);//显示fragment操作
//3.提交
beginTransaction.commit();
}
缓存清理扫描操作#
/**
* 扫描
*/
private void scanner() {
//1.获取包的管理者
//getActivity() : 获取fragment所在的activity
final PackageManager pm = getActivity().getPackageManager();
tv_cachefragment_text.setText("正在初始化128核扫描引擎.....");
new Thread(){
public void run() {
SystemClock.sleep(100);
List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
//设置进度条最大进度
pb_cachefragment_progressbar.setMax(installedPackages.size());
int count=0;
for (PackageInfo packageInfo : installedPackages) {
SystemClock.sleep(100);
//设置进度条最大进度和当前进度
count++;
pb_cachefragment_progressbar.setProgress(count);
//设置扫描显示的应用的名称
final String name = packageInfo.applicationInfo.loadLabel(pm).toString();
if (getActivity() != null) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
tv_cachefragment_text.setText("正在扫描:"+name);
}
});
}
}
if (getActivity() != null) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
tv_cachefragment_text.setVisibility(View.GONE);
pb_cachefragment_progressbar.setVisibility(View.GONE);
}
});
}
};
}.start();
}
获取缓存# (重点:反射+源码查看)
1.反射操作
//反射获取缓存
try {
Class<?> loadClass = getActivity().getClass().getClassLoader().loadClass("android.content.pm.PackageManager");
Method method = loadClass.getDeclaredMethod("getPackageSizeInfo", String.class,IPackageStatsObserver.class);
//receiver : 类的实例,隐藏参数,方法不是静态的必须指定
method.invoke(pm, packageInfo.packageName,mStatsObserver);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
2.远程服务aidl
//获取缓存大小
IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
long cachesize = stats.cacheSize;//缓存大小
long codesize = stats.codeSize;//应用程序的大小
long datasize = stats.dataSize;//数据大小
String cache = Formatter.formatFileSize(getActivity(), cachesize);
String code = Formatter.formatFileSize(getActivity(), codesize);
String data = Formatter.formatFileSize(getActivity(), datasize);
System.out.println(stats.packageName+"cachesize:"+cache +" codesize:"+code+" datasize:"+data);
}
};
3.权限
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
展示缓存应用#
1.使用listview进行展示
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View cachView;
ViewHolder viewHolder;
if (convertView == null) {
cachView = View.inflate(getActivity(), R.layout.item_cache, null);
viewHolder = new ViewHolder();
viewHolder.iv_itemcache_icon = (ImageView) cachView.findViewById(R.id.iv_itemcache_icon);
viewHolder.tv_itemcache_name = (TextView) cachView.findViewById(R.id.tv_itemcache_name);
viewHolder.tv_itemcache_size = (TextView) cachView.findViewById(R.id.tv_itemcache_size);
cachView.setTag(viewHolder);
}else{
cachView = convertView;
viewHolder = (ViewHolder) cachView.getTag();
}
//设置显示数据
CachInfo cachInfo = list.get(position);
//根据包名获取application信息
try {
ApplicationInfo applicationInfo = pm.getApplicationInfo(cachInfo.getPackageName(), 0);
Drawable icon = applicationInfo.loadIcon(pm);
String name = applicationInfo.loadLabel(pm).toString();
//设置显示
viewHolder.iv_itemcache_icon.setImageDrawable(icon);
viewHolder.tv_itemcache_name.setText(name);
viewHolder.tv_itemcache_size.setText(cachInfo.getCachesize());
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return cachView;
}
清理缓存#
android中当前程序是不能清理其他程序缓存
1.进入详情界面进行清理
lv_cachefragment_caches.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
//跳转到详情页面
Intent intent = new Intent();
intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
intent.setData(Uri.parse("package:"+list.get(position).getPackageName()));
startActivity(intent);
}
});
2.通过反射清理所有应用程序的缓存
//清理缓存
btn_cachefragment_clear.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//真正的实现清理缓存
try {
Class<?> loadClass = getActivity().getClass().getClassLoader().loadClass("android.content.pm.PackageManager");
//Long.class Long TYPE long
Method method = loadClass.getDeclaredMethod("freeStorageAndNotify", Long.TYPE,IPackageDataObserver.class);
method.invoke(pm, Long.MAX_VALUE,new MyIPackageDataObserver());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//清理缓存
list.clear();
//更新界面
myadapter.notifyDataSetChanged();
//隐藏button按钮
btn_cachefragment_clear.setVisibility(View.GONE);
}
});
异常捕获# (重点)
//异常捕获
//Application : 当前的应用程序,所有的应用最先的执行都是applicaiton
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
System.out.println("applcation启动了......");
Thread.currentThread().setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
}
private class MyUncaughtExceptionHandler implements UncaughtExceptionHandler{
//系统中由未捕获的异常的时候调用
//Throwable : Error和Exception的父类
@Override
public void uncaughtException(Thread thread, Throwable ex) {
System.out.println("哥捕获异常了......");
ex.printStackTrace();
try {
//将捕获到异常,保存到SD卡中
ex.printStackTrace(new PrintStream(new File("/mnt/sdcard/log.txt")));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//myPid() : 获取当前应用程序的进程id
//自己把自己杀死
android.os.Process.killProcess(android.os.Process.myPid());
}
}
}
需要将清单文件中的application改为我们自定义的application
<application
android:name=".MyApplication"
代码混淆# (重点)
1.将sdk\tools\proguard\proguard-android.txt拷贝到工程的根目录下
2.在工程的project.properties文件中将proguard.config=proguard-android.txt:proguard-project.txt代码放开
广告#
1.app特别火,广告厂商找你
2.广告平台 sdk 广告厂商 -> 广告平台 -> 开发者
1000条 1毛
300000下载量 60000活跃量 玩半个小时,1分钟3-5条广告
60000*30*3 * 30/1000/10
开发必用
id:4de79868e98eae74
秘钥:26bf4111c161c2fc