校验QQ号码

必须5-15位数字

0不能开头

纯代码式

import java.util.Scanner;
public class RegExpDemo {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
       @SuppressWarnings("resource")
	Scanner scanner = new Scanner(System.in);
	   System.out.println("RegExpDemo.main():请输入您的QQ号码: ");
	   String qq = scanner.nextLine();
	   //java代码验证
	   boolean flag = matchrule(qq);
	   System.out.println("RegExpDemo.main(): "+flag);
       return;
	}
	private static boolean matchrule(String qq) {
		// TODO Auto-generated method stub
		boolean flag = false;
		if(qq.length()>=5&&qq.length()<=15){
			char[] charreg = qq.toCharArray();
			if(charreg[0]=='0'){
				return flag;
			}
			for(char c:charreg){
				if(!(c>='0'&&c<='9')){
					return flag;
				}
				flag = true;
			}
		}
		return flag;
    }
}



加上正则

import java.util.Scanner;
public class RegExpDemo {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
	   Scanner scanner = new Scanner(System.in);
	   System.out.println("RegExpDemo.main():请输入您的QQ号码: ");
	   String qq = scanner.nextLine();
	   //正则代码验证
	   boolean flag = qq.matches("^[1-9]\\d{4,14}");
	   System.out.println("RegExpDemo.main(): "+flag);
       return;
	}
}



demo邮箱

public class RegExpDemo2 {
	/*
	 * 15178006580@qq.com
	 * liuxiang@163.com
	 * zhangsanfeng@sina.com
	 * jinyong@cskaoyan.com
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		   Scanner scanner = new Scanner(System.in);
		   System.out.println("RegExpDemo.main():请输入您的邮箱: ");
		   String email = scanner.nextLine();
		   //正则代码验证
		   boolean flag = email.matches("\\w+@\\w{2,}\\.[a-zA-Z{2,}]+");
		   System.out.println("RegExpDemo.main(): "+flag);
	       return;         
	}
}



demo3  替换分割等操作都是基于匹配的

public class RegExpDemo3 {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
	   //将数字替换成*  替换分割都是在匹配的基础上进行的
		String input = "helloqq12344worlddkh634o34ia12java";
	    String output = input.replaceAll("\\d", "*");
	    String output2 = input.replaceAll("\\d+", "*");
	    System.out.println(output);
	    System.out.println(output2);
	}
}



输出

helloqq*****worlddkh***o**ia**java
helloqq*worlddkh*o*ia*java





match方法是逐字符匹配的

本例中 ^表示以什么开头   []看后面   \表示下一个字符  \d表示 0至9的数字 {}表示上一个表达式的出现次数

常用的     \D 非数字      \s 空白字符:空格 制表 回车 换页 换行    \S 非空白   \w单词字符:0-9数字、26个字母、下划线   \W 非单词

关于[]  [abc]其中任一字符 [a-f]范围  [^a-f] 非a-f字符  


查询

//第一位必须是1,位数必须是11,只要某些号码段135、136、137、139、158、159
		String regexp_cmcc = "1[3][5679]\\d{8}";
		String regexp_cmcc2 = "1[5][89]\\d{8}";	
		if(num.matches(regexp_cmcc) || num.matches(regexp_cmcc2)){
			num=num.substring(0,7);

本例中只需规定首位和位数

//第一位必须是1,位数必须是11
		String regexp = "^1\\d{10}";
		if(num.matches(regexp)){
			num=num.substring(0,7);



对输入框做优化

输入一个数字后,能够提示满足这个条件的号码

这需要在QueryAddressActivity的editText里注册监听器,addTextChangedListener,他需要一个接口作为参数,TextWatcher,需要重载方法,然后动态显示

et_queryaddr_inputnum.addTextChangedListener(new TextWatcher(){
			@Override
			public void beforeTextChanged(CharSequence s, int start, int count,
					int after) {
				// TODO Auto-generated method stub
				
			}
			@Override
			public void onTextChanged(CharSequence s, int start, int before,
					int count) {
				// TODO Auto-generated method stub
				
			}
			@Override
			public void afterTextChanged(Editable s) {
				// TODO Auto-generated method stub
				
			}
		});



在这里只用的上第一个方法

public void beforeTextChanged(CharSequence s, int start, int count,
					int after) {
				// 拼成string
				String addr = AdressQueryDao.queryAddr( s.toString());
				tv_queryaddr_result.setText(addr);	
			}



比如符合某个条件的是什么号码,在AdressQueryDao里分个类

if(num.matches(regexp)){
			num=num.substring(0,7);
			SQLiteDatabase  db =  SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, SQLiteDatabase.OPEN_READONLY);
			Cursor cursor =db.rawQuery("select location from data2 where id = (select outkey from data1 where id = ?);", new String[]{num});
			//默认游标指向了第一行的前一行,需要next
			//查询结果只能是一个地方,要么查询到,要么没有,当向下一行读取时,则获取
			if (cursor.moveToNext()) {
	   			addr =cursor.getString(0);
			 }	
		}else if(num.length()==5){
			addr="客服服务电话";
		}else {
			System.out.println("AdressQueryDao.querryAddr() not match regular express");
			//提示位数不足
		 } 	
		return addr;
	}



关于文本框自动提示文本输入 

AutoCompleteTextView()

接下来要在来电时显示号码归属地,这里用到自定义toast

需要一个新的service  ShowCallLocation  manifest里注册


<service  android:name="com.rjl.mobilephonemanager.service.LocationService"></service>
         <service  android:name="com.rjl.mobilephonemanager.service.ShowCallLocation"></service>





public class ShowCallLocation extends Service {

	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		
		return super.onStartCommand(intent, flags, startId);
	}

}



要看第二个方法,他要实现一个TelephonyManager,这个manager需要一个listener,这个listener监听状态的改变,需要onCallStateChanged

@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);	
	    tm.listen(new MyPhoneCallListener(), PhoneStateListener.LISTEN_CALL_STATE);
		
		return super.onStartCommand(intent, flags, startId);
	}
	class MyPhoneCallListener extends PhoneStateListener{
		@Override
		public void onCallStateChanged(int state, String incomingNumber) {
			// TODO Auto-generated method stub
			super.onCallStateChanged(state, incomingNumber);
		}
		
	}



而这个state有3个状态

switch (state) {
			case TelephonyManager.CALL_STATE_IDLE:
				
				break;
			case TelephonyManager.CALL_STATE_RINGING:
				
				break;
			case TelephonyManager.CALL_STATE_OFFHOOK:
				
				break;			  
			default:
				break;
			}



主要是第2个状态,来电

case TelephonyManager.CALL_STATE_RINGING:
				String addr = AdressQueryDao.queryAddr(incomingNumber);
				System.out.println("ShowCallLocation.MyPhoneCallListener.onCallStateChanged()"+addr);
				Toast.makeText(getApplicationContext(), addr, 1).show();
				break;



我们要进入APP的时候启动这个service,放到splash里面,这么坑。。。另外监听电话要权限的,不过之前加过

copydb(this);
		Intent intent = new Intent(this,ShowCallLocation.class);
		startService(intent);





这个时候一打电话,进入管家,service就启动了,但是toast时间很短,用户不一定能看到,所以需要自定义

来看一下toast以及他的makeText方法的机制

源码

public static Toast makeText(Context context, CharSequence text, int duration) {
        Toast result = new Toast(context);

        LayoutInflater inflate = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
        tv.setText(text);
        
        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }



首先可以看到inflate,填充,是一个service,用来填充view,看他的两个参数,第一个参数前面一部分是一个layout,后一部分,transient是根节点,是R文件的一个ID值

这个值可以在SDK文件资源里找到,toast的布局,系统里面R的layout就是resource里的layout,所以R里的drawable就是resource的drawable

Android EditText多行时控制光标的位置_ide

这其中有一个transient_notification,打开可以看到

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="?android:attr/toastFrameBackground">

    <TextView
        android:id="@android:id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_gravity="center_horizontal"
        android:textAppearance="@style/TextAppearance.Toast"
        android:textColor="@color/bright_foreground_dark"
        android:shadowColor="#BB000000"
        android:shadowRadius="2.75"
        />

</LinearLayout

里面是一个文本,填充时需要一个ID为message的东西,就是这个textview

然后调用者传一个text进来,setText(text)

然后给result设置显示和时间,这个result就是toast

回到调用者

Toast.makeText(getApplicationContext(), addr, 1).show();



maketext就是一个toast实体,这就相当于一个链式调用,最后调用到show方法

在刚刚的源码中调用outline,ctrl+o;

public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }



可以看到show又是一个新的service

private static INotificationManager sService;

    static private INotificationManager getService() {
        if (sService != null) {
            return sService;
        }
        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
        return sService;
    }



实际上是通过另一个service暴露的方法获取他的类,stub是两个service必须都有一个接口,他可以把接口实例化出来

tn是一个啥?


private static class TN extends ITransientNotification.Stub {
        final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                handleShow();
            }
        };

        final Runnable mHide = new Runnable() {
            @Override
            public void run() {
                handleHide();
                // Don't do this in handleHide() because it is also invoked by handleShow()
                mNextView = null;
            }
        };

        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        final Handler mHandler = new Handler();    

        int mGravity;
        int mX, mY;
        float mHorizontalMargin;
        float mVerticalMargin;


        View mView;
        View mNextView;

        WindowManager mWM;

        TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        }





是一个类,继承于某某,是runnable的一个类,可以放在线程里

他有两个方法,handleShow和handleHide,一个处理显示,一个处理隐藏

public void handleShow() {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                Context context = mView.getContext().getApplicationContext();
                if (context == null) {
                    context = mView.getContext();
                }
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                // We can resolve the Gravity here by using the Locale for getting
                // the layout direction
                final Configuration config = mView.getContext().getResources().getConfiguration();
                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
                mParams.gravity = gravity;
                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                    mParams.horizontalWeight = 1.0f;
                }
                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                    mParams.verticalWeight = 1.0f;
                }
                mParams.x = mX;
                mParams.y = mY;
                mParams.verticalMargin = mVerticalMargin;
                mParams.horizontalMargin = mHorizontalMargin;
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                mWM.addView(mView, mParams);
                trySendAccessibilityEvent();
            }
        }



新填充的mNextView给了tn,进了handleshow之后才会把nextView给view,之前是不一样的,进来后先隐藏之前的mview,然后在把nextView给view,如此循环


上面这一段是show的核心代码

可以看到其中需要通过上下文获取view,然后使用windowmanager这个service才能将toast加到当前的窗口页面,然后后面就是一些参数的设置

其中TN有一个构造函数,一开始就把把一些参数初始化好,给toast加了一个淡入淡出的动画

比较重要的是参数有一个flags,之前要让textview可点击需要声明可点击可获取焦点,这里toast也可以在layout里用参数设置,即用纯代码来实现而无需XML

最后有一个wm,就是上面的窗口管理器,把mview显示出来


可以总结下:

toast的初始化需要makeText,然后利用handleShow加载到窗体上


现在就可以自定义toast了

模拟一下,显示中国联通

case TelephonyManager.CALL_STATE_RINGING:
				String addr = AdressQueryDao.queryAddr(incomingNumber);
				System.out.println("ShowCallLocation.MyPhoneCallListener.onCallStateChanged()"+addr);
				/*Toast.makeText(getApplicationContext(), addr, 1).show();*/
				//显示一个自定义的toast
				showMyToast();
				break;
			case TelephonyManager.CALL_STATE_OFFHOOK:
				
				break;			  
			default:
				break;
			}
			super.onCallStateChanged(state, incomingNumber);
		}		
	}
	private void showMyToast() {
		TextView tv = new TextView(this);
		tv.setText("中国联通");
		WindowManager mWM = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
		
		final WindowManager.LayoutParams params = new WindowManager.LayoutParams();;
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.format = PixelFormat.TRANSLUCENT;
        //params.windowAnimations = com.android.internal.R.style.Animation_Toast;
        params.type = WindowManager.LayoutParams.TYPE_TOAST;
        params.setTitle("Toast");
        params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        mWM.addView(tv, params);
	}



后面还要完善,比如现在这个词一直还显示着,需处理handleHide

case TelephonyManager.CALL_STATE_IDLE:
				hideMyToast();
				break;
private void hideMyToast(){
		
		if (tv!=null) {
			mWM.removeView(tv);
		}
		tv=null;
	}



说白了就是通过将每个新的mNextView给mView,如此循环,来显示toast


关于toast的背景,需要在layout文件夹里加一个XML,比如本例叫做mytoast_showaddr


<?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" 
    android:background="@drawable/call_locate_gray">    
<TextView
        android:id="@+id/tv_mytoast_addr"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:drawableLeft="@android:drawable/stat_sys_phone_call"
        android:textColor="#000000"
        android:shadowColor="#BB000000"
        android:shadowRadius="2.75"/>
</LinearLayout>





这样,我们再来显示查询结果,而不是设定的中国联通

需要传一个addr

class MyPhoneCallListener extends PhoneStateListener{
		@Override
		public void onCallStateChanged(int state, String incomingNumber) {
			// TODO Auto-generated method stub
			switch (state) {
			case TelephonyManager.CALL_STATE_IDLE:
				hideMyToast();
				break;
			case TelephonyManager.CALL_STATE_RINGING:
				String addr = AdressQueryDao.queryAddr(incomingNumber);
				System.out.println("ShowCallLocation.MyPhoneCallListener.onCallStateChanged()"+addr);
				Toast.makeText(getApplicationContext(), addr, 1).show();
				//显示一个自定义的toast
				showMyToast(addr);
				break;
			case TelephonyManager.CALL_STATE_OFFHOOK:
				
				break;			  
			default:
				break;
			}
			super.onCallStateChanged(state, incomingNumber);
		}		
	}
	private void showMyToast(String addr) {
		/*tv = new TextView(this);
		tv.setText("中国联通");*/
		LayoutInflater inflate = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        v = inflate.inflate(R.layout.mytoast_showaddr, null);
        TextView tv = (TextView)v.findViewById(R.id.tv_mytoast_addr);
        tv.setText(addr);
		mWM = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
		
		final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.format = PixelFormat.TRANSLUCENT;
        //params.windowAnimations = com.android.internal.R.style.Animation_Toast;
        params.type = WindowManager.LayoutParams.TYPE_TOAST;
        params.setTitle("Toast");
        params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        mWM.addView(v, params);
	}
    private void hideMyToast(){
		
		if (v!=null) {
			mWM.removeView(v);
		}
		v=null;
	}
}





接下来设置一下,让号码归属地这个功能能够被勾选

去settingitem里添加

<com.rjl.mobilephonemanager.ui.SettingItem
          android:id="@+id/settingitem_showAddr"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          rjl:itemtitle="显示号码归属地"
          rjl:desc_checkbox_on="显示号码归属地开启"
          rjl:desc_checkbox_off="显示号码归属地关闭"/>



然后在settingactivity里获取 这类可以把监听这一步抽成方法,方便阅读

局部变量变成成员变量的快捷键 Ctrl+1 在选择,SharedPreferences应该改成全局的,然后就无需通过参数传递了,直接用


public class SettingActivity extends Activity {
	private CheckBox cb;
	private SettingItem settingItem ;
	private SharedPreferences sp;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);		
		setContentView(R.layout.activity_setting);	
		sp = getSharedPreferences("config", MODE_PRIVATE);
		initAutoUpdateItem();
	}
	private void initAutoUpdateItem() {
		settingItem = (SettingItem) findViewById(R.id.settingitem_autoupdate);			
		cb = (CheckBox) settingItem.findViewById(R.id.cb_settingitem);
		//默认是true
	    boolean ischeck=  sp.getBoolean("autoupdate", true);
		cb.setChecked(ischeck);
		if (ischeck) {
			settingItem.setdescriptionon();   
		}
        else {
        	settingItem.setdescriptionoff();     
		}
		settingItem.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
			    Editor editor = sp.edit();
				if(cb.isChecked()){
					//勾上点击后要置成false
					cb.setChecked(false);
					settingItem.setdescriptionoff();			
					editor.putBoolean("autoupdate", false);			
			    }else{
			    	cb.setChecked(true);
			    	settingItem.setdescriptionon();			
					editor.putBoolean("autoupdate", true);					
			    }
				editor.commit();
			}		 
		 });
	}
}




由于在settingitem中已经有setcheck方法,我们无需再settingactivity再去找子控件cb,可以注释掉,另外往后有好几个item,给名字区别一下

public class SettingActivity extends Activity {

	private SettingItem settingItem_autoupdate ;
	private SharedPreferences sp;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);		
		setContentView(R.layout.activity_setting);	
		settingItem_autoupdate = (SettingItem) findViewById(R.id.settingitem_autoupdate);	
		sp = getSharedPreferences("config", MODE_PRIVATE);
		initAutoUpdateItem();
	}
	private void initAutoUpdateItem() {				
		/*cb = (CheckBox) settingItem.findViewById(R.id.cb_settingitem);*/
		//默认是true
	    boolean ischeck=  sp.getBoolean("autoupdate", true);
		/*cb.setChecked(ischeck);*/
	    settingItem_autoupdate.setCheck(ischeck);
		/*if (ischeck) {
			settingItem_autoupdate.setdescriptionon();   
		}
        else {
        	settingItem_autoupdate.setdescriptionoff();     
		}*/
		settingItem_autoupdate.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
			    Editor editor = sp.edit();
				if(settingItem_autoupdate.getChecked()){
					//勾上点击后要置成false
					settingItem_autoupdate.setCheck(false);
					settingItem_autoupdate.setdescriptionoff();			
					editor.putBoolean("autoupdate", false);			
			    }else{
			    	settingItem_autoupdate.setCheck(true);
			    	settingItem_autoupdate.setdescriptionon();			
					editor.putBoolean("autoupdate", true);					
			    }
				editor.commit();
			}		 
		 });
	}
}





现在加上自己需要的方法,监听器走起


sp = getSharedPreferences("config", MODE_PRIVATE);
		initAutoUpdateItem();
		initShowAddressItem();
	}
	private void initShowAddressItem() {
		// TODO Auto-generated method stub
		settingItem_showAddress.setCheck(sp.getBoolean("showaddr", false));
		settingItem_showAddress.setOnClickListener(new OnClickListener(){

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				
			}
			
		});
	}



监听器:

cb都无需,之前是把组合控件里的子控件cb拿出来用,现在是把他当做整体用

@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				Editor editor = sp.edit();
				if(settingItem_showAddress.getChecked()){
					//已勾上的点击后要置成false
					settingItem_showAddress.setCheck(false);
					settingItem_showAddress.setdescriptionoff();			
					editor.putBoolean("showaddr", false);			
			    }else{
			    	settingItem_showAddress.setCheck(true);
			    	settingItem_showAddress.setdescriptionon();			
					editor.putBoolean("showaddr", true);					
			    }
				editor.commit();
			}



相应的打开关闭同时要操作service的启动关闭,把splash开关拿过来,之前是开启app自动起来,现在是用户点击才开启,那边的就可以注释掉了

@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				Editor editor = sp.edit();
				if(settingItem_showAddress.getChecked()){
					//已勾上的点击后要置成false
					settingItem_showAddress.setCheck(false);
					settingItem_showAddress.setdescriptionoff();			
					editor.putBoolean("showaddr", false);	
					Intent intent = new Intent(SettingActivity.this,ShowCallLocation.class);
					stopService(intent);
			    }else{
			    	settingItem_showAddress.setCheck(true);
			    	settingItem_showAddress.setdescriptionon();			
					editor.putBoolean("showaddr", true);	
					Intent intent = new Intent(SettingActivity.this,ShowCallLocation.class);
					startService(intent);
			    }
				editor.commit();
			}



走起,这时候再关闭后service仍然启动了,里面有问题,没有在service,ShowCallLocation里去关闭,类似于之前的mediaplayer,service关了还在播放,mediaplayer是底层硬件相关的,必须释放了才能关闭

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

但是仅仅这样还不行,listener也要销毁掉,把上面的listener抽成成员变量

public class ShowCallLocation extends Service {
	private TelephonyManager tm;
	private WindowManager mWM;
	private View v;
	private MyPhoneCallListener listener;
@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO Auto-generated method stub
		tm = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);	
		listener = new MyPhoneCallListener();
	    tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
	    System.out.println("ShowCallLocation.onStartCommand()");
		return super.onStartCommand(intent, flags, startId);
	}



@Override
	public void onDestroy() {
		// TODO Auto-generated method stub	  
    	tm.listen(listener, PhoneStateListener.LISTEN_NONE);
		if (tm!=null) {
			tm=null;
		}
		if (listener!=null) {
			listener=null;
		}
		super.onDestroy();
		System.out.println("ShowCallLocation.onDestroy()");
	}



还有一个bug,用户在service里关掉之后,APP里任然显示,为毛呢?判断要不要开启是读SharedPreferences的,但是手动关了之后虽然service关了,但是值并没有变化

这里在判断service是否开启的时候,应该让系统告诉你service是否处于运行状态,而不是去sp里获取,很多场合下他的值没有相应变动

去写一个工具类,又需要一个service,getSystemService,他是一个ActivityManager,也可以管理进程,是安卓底层最核心最复杂的一个组件,如果做偏底层,一定要研究他的源码,这里是杀鸡用牛刀,只是用来看service是否在运行,service都需要用到context

public class ServiceUtils {
   public static boolean  isRunning(Context ctx, String myservice){		
		boolean flag =false;
		//如何判断:
	    ActivityManager ams = (ActivityManager) ctx.getSystemService(ctx.ACTIVITY_SERVICE);
	    //返回当前在运行的service,循环查到myservice
	    List<RunningServiceInfo> list =  ams.getRunningServices(100);
	    for (RunningServiceInfo runningServiceInfo : list) {
	    	String service =  runningServiceInfo.service.getClassName();
	    	if (service.equals(myservice)) {
				flag=true;
			}
		}		
		return flag;
	}
}



这时候也无需通过edit去保存了

private void initShowAddressItem() {
		// TODO Auto-generated method stub
		/*settingItem_showAddress.setCheck(sp.getBoolean("showaddr", false));*/
		//让系统告诉你service是否在运行,而不是通过SharedPreferences
		settingItem_showAddress.setCheck(ServiceUtils.isRunning(this, "com.rjl.mobilephonemanager.service.ShowCallLocation"));
		settingItem_showAddress.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				/*Editor editor = sp.edit();*/
				if(settingItem_showAddress.getChecked()){
					//已勾上的点击后要置成false
					settingItem_showAddress.setCheck(false);
					settingItem_showAddress.setdescriptionoff();			
					/*editor.putBoolean("showaddr", false);	*/
					Intent intent = new Intent(SettingActivity.this,ShowCallLocation.class);
					stopService(intent);
			    }else{
			    	settingItem_showAddress.setCheck(true);
			    	settingItem_showAddress.setdescriptionon();			
					/*editor.putBoolean("showaddr", true);	*/
					Intent intent = new Intent(SettingActivity.this,ShowCallLocation.class);
					startService(intent);
			    }
				/*editor.commit();*/
			}
			
		});
	}



这时候又来一个bug,点home键没效果,activity并没有销毁,重进的时候没有调用oncreate,而是走了onrestart,初始化动作没有更新,所以初始化应该在onresume里

@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);		
		setContentView(R.layout.activity_setting);	
		settingItem_autoupdate = (SettingItem) findViewById(R.id.settingitem_autoupdate);	
		settingItem_showAddress = (SettingItem) findViewById(R.id.settingitem_showAddr);
		sp = getSharedPreferences("config", MODE_PRIVATE);
	}
	@Override
	protected void onResume() {
		// TODO Auto-generated method stub
		initAutoUpdateItem( );
	    initShowAddressItem();
	    
	    System.out.println("SettingActivity.onResume()");
		super.onResume();
	}