手机卫士 第九天

##day09##
- 清除来电记录


代码挂断电话后,被挂断的号码仍然会进入通话记录中, 我们需要将这种记录删除.


查看数据库contacts2中的表calls


/**
* 删除通话记录
*/
private void deleteCallLog(String number) {
getContentResolver().delete(Uri.parse("content://call_log/calls"),
"number=?", new String[] {number});
}


注意加权限: <uses-permission android:name="android.permission.READ_CALL_LOG"/>
    <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>


- 通过内容观察者,解决通话记录删除失败的问题


系统在往通话记录的数据库中插入数据时是异步逻辑,所以当数据库还没来得及添加电话日志时,我们就执行了删除日志的操作,从而导致删除失败,为了避免这个问题,可以监听数据库变化,当数据库发生变化后,我们才执行删除操作,从而解决这个问题


/**
* 内容观察者
* @author Kevin
*
*/
class MyContentObserver extends ContentObserver {

private String incomingNumber;

public MyContentObserver(Handler handler, String incomingNumber) {
super(handler);
this.incomingNumber = incomingNumber;
}

/**
* 当数据库发生变化时,回调此方法
*/
@Override
public void onChange(boolean selfChange) {
System.out.println("call log changed...");

//删除日志
deleteCallLog(incomingNumber);

//删除完日志后,注销内容观察者
getContentResolver().unregisterContentObserver(mObserver);
}
}


------------------------------


//监听到来电时,注册内容观察者
mObserver = new MyContentObserver(new Handler(),
incomingNumber);

//注册内容观察者
getContentResolver().registerContentObserver(
Uri.parse("content://call_log/calls"), true,
mObserver);

------------------------------


注意:
补充Android2.3模拟器上需要多加权限
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
- 短信备份


- 查看短信数据库


data/data/com.android.provider.telephony/databases/mmssms.db
address 短信收件人发件人地址
date 短信接收的时间
type 1 发进来短信 2 发出去短信
read 1 已读短信 0 未读短信
body 短信内容


- 读取短信数据库内容


查看系统源码,找到uri地址:packages\provider\platform_packages_providers_telephonyprovider-master


Uri uri = Uri.parse("content://sms/");// 所有短信
Cursor cursor = ctx.getContentResolver().query(uri,
new String[] { "address", "date", "type", "body" }, null, null,
null);


遍历cursor,获取短信信息


注意权限: <uses-permission android:name="android.permission.READ_SMS"/>
       <uses-permission android:name="android.permission.WRITE_SMS"/>


- 将短信内容序列化为xml文件


sms.xml
<?xml version="1.0" encoding="utf-8"?>
<smss>
<sms>
   <address>5556</address>
   <date>10499949433</date>
   <type>1</type>
<body>wos shi haoren</body>
</sms>

<sms>
   <address>13512345678</address>
   <date>1049994889433</date>
   <type>2</type>
<body>hell world hei ma</body>
</sms>
</smss>


------------------------------


XmlSerializer serializer = Xml.newSerializer();// 初始化xml序列化工具
serializer.setOutput(new FileOutputStream(output), "utf-8");//设置输出流
/*
* startDocument(String encoding, Boolean standalone)encoding代表编码方式
* standalone 用来表示该文件是否关联其它外部的约束文件。 若值是 ”yes” 表示没有关联外部规则文件,若值是 ”no”
* 则表示有关联外部规则文件。默认值是 “yes”。
* <?xml version="1.0" encoding="utf-8" standalone=true ?>

* 参数2传null时,不会生成standalone=true/false的语句
*/
serializer.startDocument("utf-8", null);// 生成xml顶栏描述语句<?xml
// version="1.0"
// encoding="utf-8"?>
serializer.startTag(null, "smss");//起始标签
serializer.text(body);// 设置内容
serializer.endTag(null, "smss");//结束标签
serializer.endDocument();//结束xml文档


------------------------------


AToolsActivity.java

/**
* 短信备份
*/
public void smsBackup(View view) {
if (Environment.MEDIA_MOUNTED.equals(Environment
.getExternalStorageState())) {
try {
SmsUtils.smsBackup(this,
new File(Environment.getExternalStorageDirectory(),
"sms.xml"));
Toast.makeText(this, "备份成功!", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "备份失败!", Toast.LENGTH_SHORT).show();
}

} else {
Toast.makeText(this, "没有检测到sdcard!", Toast.LENGTH_SHORT).show();
}
}


- 异步备份短信,并显示进度条


mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage("正在备份短信...");
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);//设置显示风格,此风格将展示一个进度条
mProgressDialog.show();


将ProgressDialog的引用传递给工具类,在工具类中更新进度
SmsUtils.smsBackup(AToolsActivity.this, new File(
Environment.getExternalStorageDirectory(),
"sms.xml"), mProgressDialog);


--------------------------------


//短信工具类中更新进度条的逻辑
progressDialog.setMax(cursor.getCount());// 设置进度条最大值


Thread.sleep(500);//为了方便看效果,故意延时1秒钟
progress++;
progressDialog.setProgress(progress);//更新进度


--------------------------------


模拟需求变动的情况
1. A负责短信备份界面, B负责短信工具类
2. 将ProgressDialog改动为ProgressBar, 需要A通知B改动
3. 又将ProgressBar改回ProgressDialog, 需要A通知B改动
4. 既有ProgressBar,又要求有ProgressDialog, 需要A通知B改动


问题: B除了负责底层业务逻辑之外,额外还需要帮A处理界面逻辑,如何实现A和B的解耦?


- 使用回调接口通知进度,优化代码,实现解耦


/**
* 短信备份回调接口
* @author Kevin
*
*/
public interface SmsBackupCallback {
/**
* 备份之前获取短信总数
* @param total
*/
public void preSmsBackup(int total);
/**
* 备份过程中实时获取备份进度
* @param progress
*/
public void onSmsBackup(int progress);
}


----------------------------


SmsUtils.smsBackup(AToolsActivity.this, new File(
Environment.getExternalStorageDirectory(),
"sms.xml"), new SmsBackupCallback() {


@Override
public void preSmsBackup(int total) {
mProgressDialog.setMax(total);
}


@Override
public void onSmsBackup(int progress) {
mProgressDialog.setProgress(progress);
}
});


- 短信还原(介绍)


- 应用管理器(AppManagerActivity)


- 介绍金山卫士的应用管理器
- 参考金山卫士,编写布局文件
- 计算内置存储空间和sdcard剩余空间


/**
* 获取剩余空间

* @param path
* @return
*/
private String getAvailSpace(String path) {
StatFs stat = new StatFs(path);

// Integer.MAX_VALUE;
// int最大只能表示到2G, 在一些高端手机上不足够接收大于2G的容量,所以可以用long来接收, 相乘的结果仍是long类型
long blocks = stat.getAvailableBlocks();// 获取可用的存储块个数
long blockSize = stat.getBlockSize();// 获取每一块的大小

return Formatter.formatFileSize(this, blocks * blockSize);// 将字节转化为带有容量单位的字符串
}


//获取内存的地址
Environment.getDataDirectory().getAbsolutePath()
//获取sdcard的地址
Environment.getExternalStorageDirectory().getAbsolutePath()


- 获取已安装的应用列表


/**
* 应用信息封装

* @author Kevin

*/
public class AppInfo {

public String name;// 名称
public String packageName;// 包名
public Drawable icon;// 图标

public boolean isUserApp;// 是否是用户程序
public boolean isRom;// 是否安装在内置存储器中

@Override
public String toString() {
return "AppInfo [name=" + name + ", packageName=" + packageName + "]";
}
}


-------------------------------

AppInfoProvider.java


/**
* 获取已安装的应用信息
* @param ctx
*/
public static ArrayList<AppInfo> getAppInfos(Context ctx) {

ArrayList<AppInfo> infoList = new ArrayList<AppInfo>();

PackageManager pm = ctx.getPackageManager();
List<PackageInfo> packages = pm.getInstalledPackages(0);// 获取已经安装的所有包

for (PackageInfo packageInfo : packages) {
AppInfo info = new AppInfo();

String packageName = packageInfo.packageName;// 获取包名
Drawable icon = packageInfo.applicationInfo.loadIcon(pm);// 获取图标
String name = packageInfo.applicationInfo.loadLabel(pm).toString();// 获取名称

info.packageName = packageName;
info.icon = icon;
info.name = name;

infoList.add(info);
}

return infoList;
}


进行单元测试,打印应用列表


- Android的应用程序安装位置

  pc电脑默认安装在C:\Program Files
  Android 的应用安装在哪里呢,如果是用户程序,安装在data/app/目录下,
系统自带应用安装在system/app/目录下
  
  安装Android软件 做两件事 
    A:把APK拷贝到data/app/目录下
    B:把安装包信息写到data/system/目录下两个文件packages.list 和 packages.xml


安装包信息在data/system/
Packages.list 里面的0 表示系统应用 1 表示用户应用
Packages.xml是存放应用的一些权限信息的;


- 判断是系统应用还是用户应用


1. 解释标识左移几位的效果public static final int FLAG_SYSTEM = 1<<0; 
2. 不同标识可以加起来一起使用, 加起来的结果和特定标识进行与运算,通过计算结果可以知道具不具备该特定标识的相关功能, 这种方式叫做状态机
3. 玩游戏距离: 喝药水,加功能(加血,加攻击力,加防御,加魔法值),可以通过状态机来表示该药水具备哪些特性 
4. 实际开发的机顶盒举例:{"cctv1":true,"cctv2":false,"cctv3":true}->{"flag":101}, 可以节省流量


if ((flags & ApplicationInfo.FLAG_SYSTEM) == ApplicationInfo.FLAG_SYSTEM) {
info.isUserApp = false;// 系统应用
} else {
info.isUserApp = true;//用户应用
}

if ((flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == ApplicationInfo.FLAG_EXTERNAL_STORAGE) {
info.isRom = false;// 安装位置是sdcard
} else {
info.isRom = true;//安装位置是内置存储器
}


- ListView列表展现


- 仿照金山卫士编写item布局
- 异步加载应用列表数据,并显示进度条


加载应用列表有时会比较耗时,最好放在子线程执行. 加载期间显示加载中的布局

<FrameLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent" >

       <ListView
           android:id="@+id/lv_list"
           android:layout_width="match_parent"
           android:layout_height="match_parent" >
       </ListView>

       <LinearLayout
           android:id="@+id/ll_loading"
           android:visibility="gone"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_gravity="center"
           android:gravity="center"
           android:paddingBottom="50dp"
           android:orientation="vertical" >

           <ProgressBar
               android:id="@+id/progressBar1"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content" />

           <TextView
               android:id="@+id/textView1"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="正在加载应用列表..." />
       </LinearLayout>


<TextView
           android:id="@+id/tv_head"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:background="#FF888888"
           android:textColor="#fff"
           android:text="用户程序(5)"
            />
   </FrameLayout>


- 清单文件中注册安装位置


manifest根标签具有这样的属性,用来指定apk的安装位置, 缺省值是手机内存 android:installLocation="auto", 可以修改为auto, 表示优先使用手机内存, 如果内存不够,再使用sdcard. 不建议强制安装在sdcard,因为某些手机没有sdcard,会导致安装失败.设置完成后,就可以在系统应用管理中,移动apk的安装位置了.


- 复杂ListView的展现方式


核心思想: 重写BaseAdapter自带的getItemViewType方法来返回item类型,重写getViewTypeCount方法返回类型个数


//应用信息适配器
class AppInfoAdapter extends BaseAdapter {

/**
* 返回总数量,包括两个标题栏
*/
@Override
public int getCount() {
return 1 + mUserAppList.size() + 1 + mSystemAppList.size();
}

/**
* 返回当前的对象
*/
@Override
public AppInfo getItem(int position) {
if (position == 0 || position == 1 + mUserAppList.size()) {//判断是否是标题栏
return null;
}

AppInfo appInfo;
if (position < mUserAppList.size() + 1) {//判断是否是用户应用
appInfo = mUserAppList.get(position - 1);
} else {//系统应用
appInfo = mSystemAppList
.get(position - mUserAppList.size() - 2);
}
return appInfo;
}

@Override
public long getItemId(int position) {
return position;
}

/**
* 返回当前view的类型
*/
@Override
public int getItemViewType(int position) {
if (position == 0 || position == 1 + mUserAppList.size()) {
return 1;// 标题栏
} else {
return 0;// 应用信息
}
}

/**
* 返回当前item的类型个数
*/
@Override
public int getViewTypeCount() {
return 2;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);//获取item的类型

if (convertView == null) {//初始化convertView
switch (type) {
case 1://标题
convertView = new TextView(AppManagerActivity.this);
((TextView) convertView).setTextColor(Color.WHITE);
((TextView) convertView).setBackgroundColor(Color.GRAY);
break;
case 0://应用信息
ViewHolder holder;
convertView = View.inflate(AppManagerActivity.this,
R.layout.list_appinfo_item, null);

holder = new ViewHolder();
holder.ivIcon = (ImageView) convertView
.findViewById(R.id.iv_icon);
holder.tvName = (TextView) convertView
.findViewById(R.id.tv_name);
holder.tvLocation = (TextView) convertView
.findViewById(R.id.tv_location);

convertView.setTag(holder);
break;

default:
break;
}
}

//根据类型来更新view的显示内容
switch (type) {
case 1:
if (position == 0) {
((TextView) convertView).setText("用户程序("
+ mUserAppList.size() + ")");
} else {
((TextView) convertView).setText("系统程序("
+ mUserAppList.size() + ")");
}
break;
case 0:
ViewHolder holder = (ViewHolder) convertView.getTag();
AppInfo appInfo = getItem(position);
holder.ivIcon.setImageDrawable(appInfo.icon);
holder.tvName.setText(appInfo.name);

if (appInfo.isRom) {
holder.tvLocation.setText("手机内存");
} else {
holder.tvLocation.setText("外置存储卡");
}

break;
default:
break;
}

return convertView;
}
}


- PopupWindow使用


专门写一个Demo,用于PopupWindow的演示


/**
* 显示弹窗

* @param view
*/
public void showPopupWindow(View view) {
TextView contentView = new TextView(this);
contentView.setText("我是弹窗哦!");
contentView.setTextColor(Color.RED);

PopupWindow popup = new PopupWindow(contentView, 100, 100, true);//设置尺寸及获取焦点
popup.setBackgroundDrawable(new ColorDrawable(Color.BLUE));//设置背景颜色
// popup.showAtLocation(rlRoot, Gravity.LEFT + Gravity.TOP, 0,
// 0);//显示在屏幕的位置
popup.showAsDropDown(btnPop, 0, 0);// 显示在某个控件的正下方
}


- 将PopupWindow应用到项目当中


//listview监听
lvList.setOnItemClickListener(new OnItemClickListener() {


@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
mCurrentAppInfo = mAdapter.getItem(position);
System.out.println(mCurrentAppInfo.name + "被点击!");
showPopupWindow(view);
}
});


-----------------------------------


/**
* 显示弹窗
*/
private void showPopupWindow(View view) {
View contentView = View.inflate(this, R.layout.popup_appinfo, null);

TextView tvUninstall = (TextView) contentView
.findViewById(R.id.tv_uninstall);
TextView tvLaunch = (TextView) contentView.findViewById(R.id.tv_launch);
TextView tvShare = (TextView) contentView.findViewById(R.id.tv_share);

//设置监听事件
tvUninstall.setOnClickListener(this);
tvLaunch.setOnClickListener(this);
tvShare.setOnClickListener(this);

//初始化PopupWindow
PopupWindow popup = new PopupWindow(contentView,
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, true);
popup.setBackgroundDrawable(new ColorDrawable());// 必须设置背景,否则无法返回
popup.showAsDropDown(view, 50, -view.getHeight());// 显示弹窗

//渐变动画
AlphaAnimation alpha = new AlphaAnimation(0, 1);
alpha.setDuration(500);
alpha.setFillAfter(true);

//缩放动画
ScaleAnimation scale = new ScaleAnimation(0, 1, 0, 1,
Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF,
0.5f);
scale.setDuration(500);
scale.setFillAfter(true);

//动画集合
AnimationSet set = new AnimationSet(false);
set.addAnimation(alpha);
set.addAnimation(scale);

//运行动画
contentView.startAnimation(set);
}


-----------------------------------


@Override
public void onClick(View v) {
if (mCurrentAppInfo == null) {
return;
}
switch (v.getId()) {
case R.id.tv_uninstall:
System.out.println("卸载" + mCurrentAppInfo.name);
break;
case R.id.tv_launch:
System.out.println("启动" + mCurrentAppInfo.name);
break;
case R.id.tv_share:
System.out.println("分享" + mCurrentAppInfo.name);
break;

default:
break;
}
}


- 卸载,启动和分享的逻辑


/**
* 卸载
*/
private void uninstall() {
if (mCurrentAppInfo.isUserApp) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_DELETE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(Uri.parse("package:" + mCurrentAppInfo.packageName));
startActivityForResult(intent, 0);
} else {
Toast.makeText(this, "无法卸载系统程序!", Toast.LENGTH_SHORT).show();
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 卸载成功后重新加载应用列表
// 此处不必判断resultCode是否是RESULT_OK, 因为4.1+系统即使卸载成功也始终返回RESULT_CANCEL
loadAppInfos();
super.onActivityResult(requestCode, resultCode, data);
}


------------------------------------


/**
* 启动App
*/
private void launchApp() {
try {
PackageManager pm = this.getPackageManager();
Intent intent = pm
.getLaunchIntentForPackage(mCurrentAppInfo.packageName);// 获取应用入口的Intent
startActivity(intent);// 启动应用
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "无法启动该应用!", Toast.LENGTH_SHORT).show();
}
}


------------------------------------


/**
* 分享 此方法会呼起系统中所有支持文本分享的app列表
*/
private void shareApp() {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT,
"分享给你一个很好的应用哦! 下载地址: https://play.google.com/apps/details?id="
+ mCurrentAppInfo.packageName);
startActivity(intent);
}


- ListView分类栏常驻效果


//原理: 写一个TextView常驻在ListView顶栏, 样式和item中分类栏的样式完全一样. 监听ListView的滑动事件,动态修改TextView的内容


//设置listview的滑动监听
lvList.setOnScrollListener(new OnScrollListener() {


@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}


@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
System.out.println("onScroll:" + firstVisibleItem);
if (mUserAppList != null && mSystemAppList != null) {
if (firstVisibleItem <= mUserAppList.size()) {
tvListHead.setText("用户应用(" + mUserAppList.size() + ")");
} else {
tvListHead.setText("系统应用(" + mSystemAppList.size()
+ ")");
}
}
}
});

你可能感兴趣的:(手机卫士 第九天)