手机卫士day09

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)"
             />
        

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

        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()
                            + ")");
                }
            }
        }
    });
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值