android定位的实现

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android定位的实现相关的知识,希望对你有一定的参考价值。


基于android的定位无非就两种:network、gps。两者各有优劣。
Network:定位快,准确度低,受环境影响小。
GPS:定位慢,准确度高,受环境影响大。

本文要解决的问题:
1.      locationManager.getLastKnownLocation方法返回null。
2.      如何实现快速而又精确的定位。

E文好的话,直接看官网就好了 ​​​http://developer.android.com/guide/topics/location/strategies.html​​​

在你的程序里如果有这样的代码你就要注意了(现在看来这些倒是多余了)

Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);//高精度
criteria.setAltitudeRequired(false);//无海拔要求
criteria.setBearingRequired(false);//无方位要求
criteria.setCostAllowed(true);//允许产生资费
criteria.setPowerRequirement(Criteria.POWER_LOW);//低功耗

// 获取最佳服务对象
String provider = locationManager.getBestProvider(criteria,true);
locationManager.getLastKnownLocation(provider);


locationManager.getBestProvider(criteria,true);方法看起来很完美,但其实返回值就network、gps二选一。而且如果你要求高精度,它会优先检查GPS,如果手机开启了GPS就返回GPS,否则返回network。如果都没开启则返回null。

结合Network、GPS两种定位方式的优劣不难看出为什么getLastKnownLocation方法会返回null(这只针对第一次定位)。


当你开启GPS,provider的值为GPS。这时的定位方式为GPS,由于GPS定位慢(我测试的时间大约为50秒),所以它不可能立即返回你一个Location对象,所以就返回null了。还有人用下面的方法解决这个问题:

while (location ==null) 
location = locationManager.getLastKnownLocation(provider);

这绝对是个愚蠢的做法!举个例子:如果你在室内,gps无法定位到,你的程序将陷入死循环。当然使用requestLocationUpdates可以做到定位且不让程序陷入死循环,但是定位耗时长,甚至得不到定位。

如果使用网络定位呢,不得说这也是一个不错的选择。locationManager.requestLocationUpdates(

              LocationManager.NETWORK_PROVIDER, 0, 0,networkListener);

网络定位耗时一般在2秒左右(网络差,时间会更长),只要你接入网络,基本上都能获得定位。唯一的缺点就是精度不高。


那能不能将两者结合,这也是本文的重点。既然结合两者,就要同时为两者添加监听

locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,1000 * 2,50,gpsListener);
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0,networkListener);


这样,大概2秒我们就可以得到来自网络的定位,一分钟后得到来自GPS定位。这时用GPS定位替换网络定位就好了。当然这只是个理想的情况,现实要复杂的多。

比如:

你第一次定位成功返回location,由于网络问题第二次返回null。这时会发现,更新的location没有上次的精确,甚至是null,无法使用,这时我们要判断当前的location和新获得的location那个更好。可能你获得GPS定位后,由于天气、进入隧道等原因GPS服务器丢失,无法更新location(这时一个好的做法是切换到network定位)。还有可能用户没有开启GPS和network,根本就谈不上定位(其实每次定位成功都会有个定位缓存的,可以使用getLastKnownLocation获得)。


终上所述,我们要做的就是:

1.  尝试通过getLastKnownLocation获取上次定位信息

2.  开启network和gps监听

3.  获得network定位信息location

4.  比较当前location和新获取的location哪个更好(来自network)

5.  获得gps定位信息location

6.  停掉network监听

7.  比较当前location和新获取的location哪个更好(来自gps)

8.  如果gps服务器丢失,重新开启network监听


以GPS监听为例

// GPS监听的回调函数
private class GPSLocationListener implements LocationListener

private boolean isRemove = false;//判断网络监听是否移除

@Override
public void onLocationChanged(Location location)
// TODO Auto-generatedmethod stub
boolean flag =betterLocation.isBetterLocation(location,
currentBestLocation);

if (flag)
currentBestLocation = location;
updateLocation(currentBestLocation);

// 获得GPS服务后,移除network监听
if (location !=null && !isRemove)
locationManager.removeUpdates(networkListener);
isRemove = true;



@Override
public void onProviderDisabled(String provider)
// TODO Auto-generatedmethod stub


@Override
public void onProviderEnabled(String provider)
// TODO Auto-generatedmethod stub


@Override
public void onStatusChanged(String provider, int status, Bundleextras)
// TODO Auto-generatedmethod stub
if (LocationProvider.OUT_OF_SERVICE == status)
Toast.makeText(MainActivity.this,"GPS服务丢失,切换至网络定位",
Toast.LENGTH_SHORT).show();
locationManager
.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 0, 0,
networkListener);



其中isBetterLocation是用来判断哪个location更好的。这个方法来自android官网的,通过location获取的时间,精度等信息进行判断。

private static final int TWO_MINUTES = 1000 * 60 * 2;
/**
* Determines whether one Location reading is better than the current
* Location fix
*
* @param location
* The new Location that you want to evaluate
* @param currentBestLocation
* The current Location fix, to which you want to compare the new
* one
*/
protected boolean isBetterLocation(Location location,
Location currentBestLocation)
if (currentBestLocation == null)
// A new location is always better than no location
return true;


// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;

// If its been more than two minutes since the current location, use
// the new location
// because the user has likely moved
if (isSignificantlyNewer)
return true;
// If the new location is more than two minutes older, it must be
// worse
else if (isSignificantlyOlder)
return false;


// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;

// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(),
currentBestLocation.getProvider());

// Determine location quality using a combination of timeliness and
// accuracy
if (isMoreAccurate)
return true;
else if (isNewer && !isLessAccurate)
return true;
else if (isNewer && !isSignificantlyLessAccurate
&& isFromSameProvider)
return true;

return false;


/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2)
if (provider1 == null)
return provider2 == null;

return provider1.equals(provider2);

因为之前上传的demo,大家觉得意义不大,所以就不再提供了。

下图的‘微秒’单位错了,应该是毫秒

android定位的实现_ide


andriod 自动切换网络和gps定位

获取到位置服务以后,同时请求网络和gps定位更新,然后就会同时上报网络和gps的Location 信息。在没有gps信号的时候,会自动获取网络定位的位置信息,如果有gps信号,则优先获取gps提供的位置信息.isBetterLocation 根据 时间、准确性、定位方式等判断是否更新当前位置信息,该方法来源于开发指南的Obtaining User Location 下。

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import cn.tangdada.tangbang.R;

public class SecondFragment extends BaseFragment


private TextView tv;

LocationManager lm = null;

Location myLocation = null;

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ");

public SecondFragment()

super();


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_second, null);
tv = (TextView) view.findViewById(R.id.tv);

lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);

return view;


@Override
public void onCreate(Bundle savedInstanceState)

// TODO Auto-generated method stub
super.onCreate(savedInstanceState);


@Override
public void onDestroy()

// TODO Auto-generated method stub
super.onDestroy();


@Override
public void onPause()

// TODO Auto-generated method stub
super.onPause();
lm.removeUpdates(listener);


@Override
public void onResume()

// TODO Auto-generated method stub
super.onResume();
lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, listener);
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);


LocationListener listener = new LocationListener()


@Override
public void onLocationChanged(Location location)

// 实际上报时间
// String time = sdf.format(new Date(location.getTime()));
// timeText.setText("实际上报时间:" + time);

if (isBetterLocation(location, myLocation))

// 获取纬度
double lat = location.getLatitude();
// 获取经度
double lon = location.getLongitude();
// 位置提供者
String provider = location.getProvider();
// 位置的准确性
float accuracy = location.getAccuracy();
// 高度信息
double altitude = location.getAltitude();
// 方向角
float bearing = location.getBearing();
// 速度 米/秒
float speed = location.getSpeed();

String locationTime = sdf.format(new Date(location.getTime()));
String currentTime = null;

if (myLocation != null)

currentTime = sdf.format(new Date(myLocation.getTime()));
myLocation = location;


else

myLocation = location;


// 获取当前详细地址
StringBuffer sb = new StringBuffer();
if (myLocation != null)

Geocoder gc = new Geocoder(context);
List<Address> addresses = null;
try

addresses = gc.getFromLocation(myLocation.getLatitude(), myLocation.getLongitude(), 1);

catch (IOException e)

// TODO Auto-generated catch block
e.printStackTrace();


if (addresses != null && addresses.size() > 0)

Address address = addresses.get(0);
sb.append(address.getCountryName() + address.getLocality());
sb.append(address.getSubThoroughfare());




tv.setText("经度:" + lon + "\\n纬度:" + lat + "\\n服务商:" + provider + "\\n准确性:" + accuracy + "\\n高度:" + altitude + "\\n方向角:" + bearing
+ "\\n速度:" + speed + "\\n上次上报时间:" + currentTime + "\\n最新上报时间:" + locationTime + "\\n您所在的城市:" + sb.toString());





@Override
public void onStatusChanged(String provider, int status, Bundle extras)

Log.i("tag", "onStatusChanged: " + provider);



@Override
public void onProviderEnabled(String provider)

Log.i("tag", "onProviderEnabled: " + provider);


@Override
public void onProviderDisabled(String provider)

Log.i("tag", "onProviderDisabled: " + provider);


;

private static final int TWO_MINUTES = 1000 * 1 * 2;

/**
* Determines whether one Location reading is better than the current Location fix
*
* @param location The new Location that you want to evaluate
* @param currentBestLocation The current Location fix, to which you want to compare the new one
*/
protected boolean isBetterLocation(Location location, Location currentBestLocation)

if (currentBestLocation == null)

// A new location is always better than no location
return true;


// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
boolean isNewer = timeDelta > 0;

// If its been more than two minutes since the current location, use
// the new location
// because the user has likely moved
if (isSignificantlyNewer)

return true;
// If the new location is more than two minutes older, it must be
// worse

else if (isSignificantlyOlder)

return false;


// Check whether the new location fix is more or less accurate
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;

// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(), currentBestLocation.getProvider());

// Determine location quality using a combination of timeliness and
// accuracy
if (isMoreAccurate)

return true;

else if (isNewer && !isLessAccurate)

return true;

else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider)

return true;

return false;


/** Checks whether two providers are the same */
private boolean isSameProvider(String provider1, String provider2)

if (provider1 == null)

return provider2 == null;

return provider1.equals(provider2);


android定位的实现_android_02

  • 大小: 27.5 KB
  • 查看图片附件

Android 滑动定位+吸附悬停效果实现

在前两篇文章中,分别介绍了tablayout+scrollview 和 tablayout+recyclerview 实现的滑动定位的功能,文章链接:
Android 实现锚点定位
Android tabLayout+recyclerView实现锚点定位
仔细看的话,这种滑动定位的功能,还可以整体滑动,再加上顶部tablayout 吸附悬停的效果。

实现效果:

技术分享图片

布局

这里采用的是两个 tablayout。
一个用于占位,位于原始位置,scrollview内部,随scrollview滚动;另一个则是在滑动过程中,不断滑动,滑动到顶部时吸附在屏幕顶部,用户实际操作的也是这个tablayout。

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

    <com.tabscroll.CustomScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

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

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="200dp"
                    android:background="#ccc"
                    android:gravity="center">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="这里是顶部内容区域"
                        android:textSize="16sp" />

                </LinearLayout>

                <!--占位的tablayout-->
                <android.support.design.widget.TabLayout
                    android:id="@+id/tablayout_holder"
                    android:layout_width="match_parent"
                    android:layout_height="50dp"
                    android:background="#ffffff"
                    app:tabIndicatorColor="@color/colorPrimary"
                    app:tabMode="scrollable"
                    app:tabSelectedTextColor="@color/colorPrimary" />

                <LinearLayout
                    android:id="@+id/container"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:padding="16dp" />

            </LinearLayout>


            <!--实际用户操作的tablayout-->
            <android.support.design.widget.TabLayout
                android:id="@+id/tablayout_real"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="#ffffff"
                android:visibility="invisible"
                app:tabIndicatorColor="@color/colorPrimary"
                app:tabMode="scrollable"
                app:tabSelectedTextColor="@color/colorPrimary" />
        </FrameLayout>


    </com.tabscroll.CustomScrollView>

</LinearLayout>

实现

滑动定位的功能可以参考之前的文章,这里主要是进行吸附悬停的效果。

数据初始化:

/**
 * 占位tablayout,用于滑动过程中去确定实际的tablayout的位置
 */
private TabLayout holderTabLayout;
/**
 * 实际操作的tablayout,
 */
private TabLayout realTabLayout;
private CustomScrollView scrollView;
private LinearLayout container;
private String[] tabTxt = {"客厅", "卧室", "餐厅", "书房", "阳台", "儿童房"};

private List<AnchorView> anchorList = new ArrayList<>();

//判读是否是scrollview主动引起的滑动,true-是,false-否,由tablayout引起的
private boolean isScroll;
//记录上一次位置,防止在同一内容块里滑动 重复定位到tablayout
private int lastPos = 0;
//监听判断最后一个模块的高度,不满一屏时让最后一个模块撑满屏幕
private ViewTreeObserver.OnGlobalLayoutListener listener;

for (int i = 0; i < tabTxt.length; i++) {
    AnchorView anchorView = new AnchorView(this);
    anchorView.setAnchorTxt(tabTxt[i]);
    anchorView.setContentTxt(tabTxt[i]);
    anchorList.add(anchorView);
    container.addView(anchorView);
}
for (int i = 0; i < tabTxt.length; i++) {
    holderTabLayout.addTab(holderTabLayout.newTab().setText(tabTxt[i]));
    realTabLayout.addTab(realTabLayout.newTab().setText(tabTxt[i]));
}

一开始让实际的tablayout 移动到占位的tablayout 处,覆盖占位的tablayout。

listener = new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        //计算让最后一个view高度撑满屏幕
        int screenH = getScreenHeight();
        int statusBarH = getStatusBarHeight(AliHomeMoreActivity.this);
        int tabH = holderTabLayout.getHeight();
        int lastH = screenH - statusBarH - tabH - 16 * 3;
        AnchorView anchorView = anchorList.get(anchorList.size() - 1);
        if (anchorView.getHeight() < lastH) {
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            params.height = lastH;
            anchorView.setLayoutParams(params);
        }

        //一开始让实际的tablayout 移动到 占位的tablayout处,覆盖占位的tablayout
        realTabLayout.setTranslationY(holderTabLayout.getTop());
        realTabLayout.setVisibility(View.VISIBLE);
        container.getViewTreeObserver().removeOnGlobalLayoutListener(listener);

    }
};
container.getViewTreeObserver().addOnGlobalLayoutListener(listener);

private int getScreenHeight() {
    return getResources().getDisplayMetrics().heightPixels;
}

public int getStatusBarHeight(Context context) {
    int result = 0;
    int resourceId = context.getResources()
            .getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = context.getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

scrollview滑动

主要在滑动过程这不断监听滑动的距离,再移动实际的tablayout ,当在屏幕内时,让其一直覆盖在占位的tablayout 上,看上去是跟着scrollview 一起滑动的;当滑出屏幕时,实际的tablayout 不断移动 使其相对屏幕静止,看上去是吸附在屏幕顶部。

scrollView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            isScroll = true;
        }
        return false;
    }
});

//监听scrollview滑动
scrollView.setCallbacks(new CustomScrollView.Callbacks() {
    @Override
    public void onScrollChanged(int x, int y, int oldx, int oldy) {
        //根据滑动的距离y(不断变化的) 和 holderTabLayout距离父布局顶部的距离(这个距离是固定的)对比,
        //当y < holderTabLayout.getTop()时,holderTabLayout 仍在屏幕内,realTabLayout不断移动holderTabLayout.getTop()距离,覆盖holderTabLayout
        //当y > holderTabLayout.getTop()时,holderTabLayout 移出,realTabLayout不断移动y,相对的停留在顶部,看上去是静止的
        int translation = Math.max(y, holderTabLayout.getTop());
        realTabLayout.setTranslationY(translation);

        if (isScroll) {
            for (int i = tabTxt.length - 1; i >= 0; i--) {
                //需要y减去顶部内容区域的高度(具体看项目的高度,这里demo写死的200dp)
                if (y - 200 * 3 > anchorList.get(i).getTop() - 10) {
                    setScrollPos(i);
                    break;
                }
            }
        }

    }
});

private void setScrollPos(int newPos) {
    if (lastPos != newPos) {
        realTabLayout.setScrollPosition(newPos, 0, true);
    }
    lastPos = newPos;
}

tablayout点击切换

由于实际操作的是realtablayout ,所以这里只需要一直监听该tablayout。

//实际的tablayout的点击切换
realTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        isScroll = false;
        int pos = tab.getPosition();
        int top = anchorList.get(pos).getTop();
        //同样这里滑动要加上顶部内容区域的高度(这里写死的高度)
        scrollView.smoothScrollTo(0, top + 200 * 3);
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {

    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {

    }
});

至此,滑动定位+顶部吸附悬停 的效果结束了。做完之后,再看这个效果,其实和 支付宝-首页 更多 那个页面里的滑动效果一样。
代码与之前文章的在同一个git地址里。

详细代码见
github地址:https://github.com/taixiang/tabScroll

欢迎关注我的博客:https://blog.manjiexiang.cn/
更多精彩欢迎关注微信号:春风十里不如认识你
技术分享图片

有个「佛系码农圈」,欢迎大家加入畅聊,开心就好!
技术分享图片
过期了,可加我微信 tx467220125 拉你入群。











以上是关于android定位的实现的主要内容,如果未能解决你的问题,请参考以下文章

iOS 和 Android 上相对地理定位的准确性

在android中使用gps安全定位

Android无埋点数据收集SDK关键技术解析

Android GPS定位速度不可靠

Android利用百度地图SDK定位问题,如何更加精准

Android LBS 百度地图(参考: 《第一行代码》第二版(郭霖著)11.3.4 显示看得懂的定位信息:准确文字地址)