Android之手机定位方式(GPS定位,网络定位,基站定位)

从前天学习GPS定位开始,这两天断断续续都在学习Android的三种基本定位方式。

1.GPS定位(基本Android机上都会有,缺点是必须在空旷的地方才有用)

2.网络定位(NetWork,这个很多手机上没有这个功能,比如我的联想机就没有这个功能)

4.基站定位(获取基站信息,然后向谷歌发送解析要求,解析地址)

在编写代码时,遇到了很多问题,大多数时间都是花费在一些不太注意的小问题上了,而且最后基站定位也还是有BUG(Http post不会执行)

遇到的问题:

1.权限没有添加,定位时需要的权限

1.1:(网络连接)

1.2:(基站定位时需要的权限)

1.3: (网络定位需要的权限)

1.4(GPS精确定位时需要的权限)

2.GPS定位和网络定位时,没有添加监听函数:requestLocationUpdates().

正确的做法应该是在 onResume里面注册监听函数GPS位置监听和GPS状态监听(NetWork位置监听),然后在onPause()里面取消对应的监听

3.抛出错误:ERROR/LocationManagerService(1236): java.lang.IllegalArgumentException: provider=network(这是由于手机上不支持NetWork定位功能,没办法)

4.基站定位:程序执行到HttpResponse resp = client.execute(post)时会自动抛出错误,而且Catch捕获的错误是null.

解决方法1:在主线程里面使用了耗时操作,Android4.0以后不被允许,加上一下代码即可解决:

(但是这个方法会将程序一直卡着,虽然没出错,但是我的手机运行这个程序后直接就死掉了)

	/**解决4.0中不能进行网络操作要求
		 * 暴力方法
		 * 其实最好是另外单独使用一个线程,完了后告诉主线程
		if (Build.VERSION.SDK_INT >= 11) {
			StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectDiskReads  ().detectDiskWrites().detectNetwork().penaltyLog().build());
			StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects().detectLeakedClosableObjects().penaltyLog().penaltyDeath().build());
			}
			*/


解决方法2(Thread+Handler):重新开一个线程MyThread,然后在MyThread里面执行耗时操作,将结果通知MyHandler,其中创建MyHandler时,将MyHandler与主线程的Looper绑定,所以在子线程MyThread里面通过MyHandler即可发送消息给主线程,主线程只需要重写handleMessage(Message msg)函数即可接收相应的消息并进行处理。这是异步线程交互的一种最常用的方式。

***但是问题来了:在我用了(Thread+Handler)的方式后,程序执行到HttpResponse resp = client.execute(post)时还是会自动抛出错误,而且捕获不到错误(如果尝试输出错误信息又会抛出新的错误(java.lang.NullPointerException:println needs a message),因为捕获到的是null)。目前还不知道为什么会这样。

一些小结:

1.在是线程间传送消息时,用到了bundle,关于bundle的使用

1.1bundle可以直接设置一些常用的类型,String,int,boolean等

1.2当需要自定义类型Object时,需要将Object类序列化(可以Serializable也可以Parcelable,其中实现Parcelable时需要重写几个方法),需要注意的时,父类序列化时,子类也是默认序列化的。

2.关于Toast的显示,Toast是依附于Activity的,所以在Activity中的子线程里面最好不要使用Toast,如果需要显示,则有下列几种解决方法

2.1:Looper.prepare();  Toast显示内容Looper.loop();(注意,Toast显示一定要用 .show())

2.2:使用线程见的通信方式,子线程通过MyHandle发送消息给主线程,然后在主线程里面选择对应的消息类型然后进行相应的处理。

3.子线程访问主线程中的UI界面,这里介绍两种方法:

3.1:通过MyHandler.post(new Thread())其实是只调用线程的run()方法,然不是调用start()方法来启动一个新线程,其本质是在主线程里面调用了另外一个线程的run()方法,而不是新开一个线程,所以还是不能做耗时操作。

3.2new Thread(new Runnable()).start(),新开一个线程后,通过MyHandler来给主线程发送消息,主线程发送消息后在进行相应的处理,新开了一个线程,异步执行,所以可以做耗时操作。

4.在创建Activity对应的Handler时,可以使用弱引用,避免Handler内存泄漏(OOM):

如MyHandler(Activity activity) this.activity=new WeakReference(activity).get();

为什么会内存泄漏:当创建一个Handler对象时,Handler对象会隐式的持有一个外部对象(通常是一个Activity的引用),而Handler通常会伴随着一个耗时的后台线程(这个后台线程在任务执行完毕,之后,通过消息机制通知Handler,然后Handler把图片更新到界面。)然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到线程结束。(如果打开一个Activiity,关闭它内存泄漏,然后继续打开,关闭。。。那么内存占用就很夸张了)

什么是WeakReference:它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向,该对象就会在被GC检查到时回收掉。比
如用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。

用到了判断用户网络状态,根据不同网络状态做不同操作,比如WIFI时加载广告,GPRS时没有广告:

代码:

/**检查网络连接状态*/
	private void checkNetworkState()
	{
		if(LocationUtils.isNetworkAvaliable(mConnecManager))
		{
			//网络连接正常,判断网络状态
			judgeNetworkState();
		}
		else//网络没有连接,进行网络连接设置
		{
			setNetwork();
		}
	}
	/**网络未连接状态下,调用设置网络连接*/
	private void setNetwork()
	{
		 Toast.makeText(this, "网络连接不可用!", Toast.LENGTH_SHORT)
		 .show();  
		 //下面调用设置Dialog
		 AlertDialog.Builder builder=new AlertDialog.Builder(this);
		 builder.setIcon(R.drawable.ic_notify);
		 builder.setTitle("网络提示信息!");
		 builder.setMessage("网络不可用,如果继续,请先设置网络!");
		 builder.setPositiveButton("设置", new DialogInterface.OnClickListener() {		
			@Override
			  public void onClick(DialogInterface dialog, int which) {  
                Intent intent = null;  
                /** 
                 * 判断手机系统的版本!如果API大于10 就是3.0+ 
                 * 因为3.0以上的版本的设置和3.0以下的设置不一样,调用的方法不同 
                 */  
                if (android.os.Build.VERSION.SDK_INT > 10) {  
                    intent = new Intent(android.provider.Settings.ACTION_WIFI_SETTINGS);  
                } else {  
                    intent = new Intent();  
                    ComponentName component = new ComponentName(  
                            "com.android.settings",  
                            "com.android.settings.WirelessSettings");  
                    intent.setComponent(component);  
                    intent.setAction("android.intent.action.VIEW");  
                }  
                startActivity(intent);  
            }
		});
		 builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {  
	            @Override  
	            public void onClick(DialogInterface dialog, int which) {  
	  
	            } 
	        });  
		 builder.create();
		 builder.show();
	}
	/**网络已经连接,判断是wifi还是GPRS
	 * 设置一些自己的逻辑调用
	 * */
	private void judgeNetworkState()
	{
	State gprs = mConnecManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState();  
	State wifi = mConnecManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState();  
	//如果是gprs连接
	if(gprs==State.CONNECTED||gprs==State.CONNECTING)
	{
		Toast.makeText(this, "网络连接可用! GPRS模式!", Toast.LENGTH_SHORT)
		.show();
	}
	//如果是wifi状态,一般wifi状态可以做一些其它的事情(如加载广告)
	if(wifi==State.CONNECTED|| wifi == State.CONNECTING)
	{
		Toast.makeText(this, "网络连接可用! WIFI模式!", Toast.LENGTH_SHORT)
		.show();
		//加载广告
		loadADmod();
	}
	}
/**得到基站位置的函数*/
	private void onGetTelephonyPosition()
	{
		Log.i(TAG,"进行基站定位!");
		txt_TelephonyPosition.setText("正在通过基站获取定位!");		
		//开启一个获取基站定位的线程,获取成功后会自动通知Activity
		//启动runnable的线程必须这样
		//因为实现Runnable接口没有start方法
		MyThread myTelephony=new MyThread();
		new Thread(myTelephony).start();
		//updateUIToNewLocation(currentLocation,myLocation,currentCellInfo);
	}

 
  
其中LocationUtils里面的isNetworkAvaliable函数:

/**判断网络是否连接诶*/
	public static boolean isNetworkAvaliable(ConnectivityManager mConnecManager)
	{
		 boolean flag = false;  
		  //去进行判断网络是否连接  
	     if (mConnecManager.getActiveNetworkInfo() != null) {  
	         flag = mConnecManager.getActiveNetworkInfo().isAvailable();  
	        }  
	        return flag;
	}

GPS连接NetWork连接以及基站连接就不具体贴代码了,最后上传完整工程。(由于我机子上没有NetWork连接,这个Demo里面GPS连接和NetWork连接放一块,优先使用NetWork连接,如果没有NetWork连接在使用GPS连接)

目前没解决的问题,代码如下:(程序执行到HttpResponse resp = client.execute(post)时还是会自动抛出错误)

线程开启方式,TelephonyDemoActivity里面的一个按钮监听函数onGetTelephonyPosition()

/**得到基站位置的函数*/
	private void onGetTelephonyPosition()
	{
		Log.i(TAG,"进行基站定位!");
		txt_TelephonyPosition.setText("正在通过基站获取定位!");		
		//开启一个获取基站定位的线程,获取成功后会自动通知Activity
		//启动runnable的线程必须这样
		//因为实现Runnable接口没有start方法
		MyThread myTelephony=new MyThread();
		new Thread(myTelephony).start();	
	}


线程类:

private class MyThread implements Runnable
	{

		@Override
		public void run() {
			  //从消息池中取出一个message
		   Message msg = myHandler.obtainMessage();   
		   //Bundle是message中的数据
		   Bundle b = new Bundle();
		   //设置一些数据,如Location,等等
			CellIdInfoManager cellIdInfoManager;	
			 cellIdInfoManager=new CellIdInfoManager(getBaseContext());
			List currentCellInfo=cellIdInfoManager.getCellInfo();
			Location currentLocation = null;
			String myLocation="";
			try {
			currentLocation=TelePhonyNetWorkLocationManager
					.getBaseStationLocation(currentCellInfo);			
			 myLocation=TelePhonyNetWorkLocationManager.getLocation(currentLocation);
			 msg.arg1=1;
			} catch (Exception e) {
				//设置msg为解析地址错误
				Log.e(TAG,"地址解析错误!");
				msg.arg1=0;		
			}	
			//设置数据
			b.putString("PhysicLocation", myLocation);
			b.putSerializable("Location", (Serializable) currentLocation);
			b.putSerializable("CellInfo", (Serializable) currentCellInfo);
		   msg.setData(b);
		   Log.i(TAG,"基站获取地址完毕,向主线程发送对应消息!");
		   myHandler.sendMessage(msg); // 向Handler发送消息,更新UI
		}
		
	}

TelePhonyNetWorkLocationManager .getBaseStationLocation(currentCellInfo)函数完整代码:功能是将基站信息解析成为地址信息


/**
	 * 通过得到的基站信息,发送给谷歌服务器,解析并取得结果(经纬度)
	 */
	public static Location getBaseStationLocation(List cellID) {  
	        if (cellID == null) {  
	            Log.i("TAG", "cellId is null.");  
	            return null;  
	        }  
	        Log.i("TAG","基站数量为:"+cellID.size());
	        Log.i("TAG","向google服务器解析基站地址!");
	        //默认的heep客户端
	        DefaultHttpClient client = new DefaultHttpClient();  
	        //提出请求
	        HttpPost post = new HttpPost("");  
	        //得到JSONObject 对象,并设置好各种参数
	        JSONObject holder = new JSONObject();  
	        try {  
	        	 Log.i("TAG","得到第一个基站!");
	            CellInfo info = cellID.get(0);  
	            holder.put("version", "1.1.0");  
	            holder.put("host", "maps.google.com");  
	            holder.put("home_mobile_country_code", info.getMobileCountryCode());  
	            holder.put("home_mobile_network_code", info.getMobileNetworkCode());  
	            holder.put("request_address", true);  
	            holder.put("radio_type", info.getRadio_type());  
	            if ("460".equals(info.getMobileCountryCode())) {  
	                holder.put("address_language", "zh_CN");  
	            } else {  
	                holder.put("address_language", "en_US");  
	            }  
	            //设置数据
	            JSONObject data, current_data;  
	            JSONArray array = new JSONArray();  
	  
	            current_data = new JSONObject();  
	            Log.i("TAG","设置第一个基站的数据!");
	            current_data.put("cell_id", info.getCellId());  
	            current_data.put("location_area_code", info.getLocationAreaCode());  
	            current_data.put("mobile_country_code", info.getMobileCountryCode());  
	            current_data.put("mobile_network_code", info.getMobileNetworkCode());  
	            current_data.put("age", 0);  
	            array.put(current_data);  
	            Log.i("TAG","第一个基站的数据设置完毕!");
	            if (cellID.size() > 2) {  
	                for (int i = 1; i < cellID.size(); i++) {  
	                	 Log.i("TAG","设置第"+i+1+"个基站的数据!");
	                    data = new JSONObject();  
	                    data.put("cell_id", info.getCellId());  
	                    data.put("location_area_code", info.getLocationAreaCode());  
	                    data.put("mobile_country_code", info.getMobileCountryCode());  
	                    data.put("mobile_network_code", info.getMobileNetworkCode());  
	                    data.put("age", 0);  
	                    array.put(data);  
	                }  
	            }  
	            //放置数据
	            holder.put("cell_towers", array);  
	            Log.i("TAG","将基站信息放到解析对象中!");
	            StringEntity se = new StringEntity(holder.toString());  
	            Log.i("TAG","得到holder的信息!"+se);
	            post.setEntity(se);  
	            Log.i("TAG","设置post请求内容!"+se);
	            /** 发出POST数据并获取返回数据 
	             *  HttpResponse resp = client.execute(post);   这句话出问题了
	             *  因为Android4.0中,网络操作不能在UI线程中
	             *  解决方法-开启新的线程,用来处理网络连接
	             *  也就是说,这个函数实在新的线程里面运行
	             *  但是,在新的Thread里面执行这个函数,这句话也会出错。。。Why?
	             * */
	            HttpResponse resp = client.execute(post);  
	            int state = resp.getStatusLine().getStatusCode();  
	            Log.i("TAG","得到系统响应的状态!"+state);
	            if (state == HttpStatus.SC_OK) {  
	                HttpEntity entity = resp.getEntity();  
	                if (entity != null) {  
	                    BufferedReader br = new BufferedReader(  
	                            new InputStreamReader(entity.getContent())); 
	                    //存放结果的字符串,sb
	                    StringBuffer sb = new StringBuffer();  
	                    String resute = "";  
	                    while ((resute = br.readLine()) != null) {  
	                        sb.append(resute);  
	                    }  
	                    br.close();  
	  
	                    data = new JSONObject(sb.toString());  
	                    data = (JSONObject) data.get("location");  
	  
	                    Location loc = new Location(  
	                            android.location.LocationManager.NETWORK_PROVIDER);  
	                    loc.setLatitude((Double) data.get("latitude"));  
	                    loc.setLongitude((Double) data.get("longitude"));  
	                    loc.setAccuracy(Float.parseFloat(data.get("accuracy")  
	                            .toString()));  
	                    loc.setTime(System.currentTimeMillis());  
	                    return loc;  
	                } else {  
	                    return null;  
	                }  
	            }//正常的http相应处理完毕 
	            else {  
	                Log.v("TAG", state + "");  
	                return null;  
	            }  
	  
	        } catch (Exception e) {  
	           // Log.e("TAG", e.getMessage());  
	        	 Log.e("TAG", "未知错误");  
	            return null;  
	        }  
	    }  
最后,MyHandler类代码(继承Handler):在Activity的onCreate里面调用 myHandler=new MyHandler(TelephonyDemoActivity.this);

/**myHandler类,继承Handler,重写了handleMessage方法*/
	private class MyHandler extends Handler
	{
		private Activity activity;
		public MyHandler(Activity activity)
		{
			 this.activity=new WeakReference(activity).get();//使用弱引用避免Handler内存泄露
		}
		//重写handleMessage,用于处理message
		@Override
		public void handleMessage(Message msg) {
			Log.i(TAG,"主线程接收到消息!");
			//选择不同的口令类型
			//0-获取地址失败  1-获取地址成功
			switch(msg.arg1)
			{
			case 0:
				Toast.makeText(activity.getApplicationContext(), "解析地址出错,不能将经纬度解析为正确地址!", Toast.LENGTH_SHORT)
				.show();
				Update(msg);
				break;
			case 1:
				Toast.makeText(activity.getApplicationContext(), "地址获取成功!", Toast.LENGTH_SHORT)
				.show();
				Update(msg);
				break;		
			default:
				Toast.makeText(activity.getApplicationContext(), "解析地址出错,未知错误!", Toast.LENGTH_SHORT)
				.show();
				Update(msg);
				break;
			}				
		}
		/**更新UI方法*/	
		private void Update(Message msg)
		{
			//这里用于更新网络信息---更新Location
			Bundle b=msg.getData();
			//记得设置bundle时,将Location 键的值设为对应的location
			Location currentLocation=(Location) b.getSerializable("Location");
			String myLocation=b.getString("PhysicLocation");
			List currentCellInfo=(List) b.getSerializable("CellInfo");
			TelephonyDemoActivity.this.updateUIToNewLocation(currentLocation, myLocation, currentCellInfo);
		}
	}


那位大侠能解决这个问题。。。感激不尽!

完整工程路径:http://download.csdn.net/detail/u010979495/8106861

你可能感兴趣的:(总结,Android学习)