`
李光正
  • 浏览: 27883 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Android Location在GPS中的应用

阅读更多

Android LocationGPS中的应用()

分类: Android 2011-03-25 10:06 1386人阅读 评论(0) 收藏 举报

 

新建Android Project,注意选择Google APIs

 

打开AndroidManifest.xml,在其中加入GPS使用权限:

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

 

main.java的代码如下:

public class main extends Activity {

    /** Called when the activity is first created. */

private LocationManager locationManager ;

private String provider ;

private Location location ;

private Address address ;

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super .onCreate(savedInstanceState);

        setContentView(R.layout. main );

        // 获取 LocationManager 服务

        locationManager = (LocationManager) this

                .getSystemService(Context. LOCATION_SERVICE );

        // 获取 Location Provider

        getProvider();

        // 如果未设置位置源,打开 GPS 设置界面

        openGPS();

        // 获取位置

        location = locationManager .getLastKnownLocation( provider );

        // 显示位置信息到文字标签

        updateWithNewLocation( location );

        // 注册监听器 locationListener ,第 2 3 个参数可以控制接收 gps 消息的频度以节省电力。第 2 个参数为毫秒,

        // 表示调用 listener 的周期,第 3 个参数为米 , 表示位置移动指定距离后就调用 listener

        locationManager .requestLocationUpdates( provider , 2000, 10,

                        locationListener );

    }

    // 判断是否开启 GPS ,若未开启,打开 GPS 设置界面

    private void openGPS() {       

        if ( locationManager .isProviderEnabled(android.location.LocationManager. GPS_PROVIDER )

        || locationManager .isProviderEnabled(android.location.LocationManager. NETWORK_PROVIDER )

        ) {

            Toast.makeText ( this , " 位置源已设置! " , Toast. LENGTH_SHORT ).show();

            return ;

        }

        Toast.makeText ( this , " 位置源未设置! " , Toast. LENGTH_SHORT ).show();

        // 转至 GPS 设置界面

        Intent intent = new Intent(Settings. ACTION_SECURITY_SETTINGS );

        startActivityForResult(intent,0);

    }

    // 获取 Location Provider

    private void getProvider(){

    // 构建位置查询条件

        Criteria criteria = new Criteria();

        // 查询精度:高

        criteria.setAccuracy(Criteria. ACCURACY_FINE );

         // 是否查询海拨:否

        criteria.setAltitudeRequired( false );

        // 是否查询方位角 :

        criteria.setBearingRequired( false );

        // 是否允许付费:是

        criteria.setCostAllowed( true );

        // 电量要求:低

        criteria.setPowerRequirement(Criteria. POWER_LOW );

        // 返回最合适的符合条件的 provider ,第 2 个参数为 true 说明 , 如果只有一个 provider 是有效的 , 则返回当前 provider

        provider = locationManager .getBestProvider(criteria, true );  

    }

  // Gps 消息监听器

    private final LocationListener locationListener = new LocationListener() {

    // 位置发生改变后调用

        public void onLocationChanged(Location location) {

        updateWithNewLocation(location);

        }

        // provider 被用户关闭后调用

        public void onProviderDisabled(String provider){

        updateWithNewLocation( null );

        }

         // provider 被用户开启后调用

        public void onProviderEnabled(String provider){ }

        // provider 状态变化时调用

        public void onStatusChanged(String provider, int status,

        Bundle extras){ }

    };

    // Gps 监听器调用,处理位置信息

    private void updateWithNewLocation(Location location) {

        String latLongString;

        TextView myLocationText = (TextView)findViewById(R.id. text );

        if (location != null ) {

        double lat = location.getLatitude();

        double lng = location.getLongitude();

        latLongString = " 纬度 :" + lat + "/n 经度 :" + lng;

        } else {

        latLongString = " 无法获取地理信息 " ;

        }

        myLocationText.setText( " 您当前的位置是 :/n" +

        latLongString+ "/n" +getAddressbyGeoPoint(location));

    }

// 获取地址信息

private List<Address> getAddressbyGeoPoint(Location location) {

List<Address> result = null ;

// 先将 Location 转换为 GeoPoint

// GeoPoint gp =getGeoByLocation(location);

try {

if (location != null ) {

// 获取 Geocoder ,通过 Geocoder 就可以拿到地址信息

Geocoder gc = new Geocoder( this , Locale.getDefault ());

result= gc.getFromLocation(location.getLatitude(), location.getLongitude(), 1);

}

} catch (Exception e) {

e.printStackTrace();

}

return result;

}

}

如果,要想在模拟器中看到效果,还需要在DDMSEmulator Control面板中进行一些设置。如果你看不到Emulator Control面板,可以从window->Show view->Other…中打开它:

 

Emulator Control面板中,你可以找到一个Location Controls的地方:

 

你可以在LongitudeLatitude中输入一个虚拟的经纬度,然后点击Send,可以把模拟器的位置修改为指定位置。此时运行程序,即可看到如图所示的信息:

 

有时候,你可能出现“无法获取地理信息”的错误。这可能是没有开启“启用GPS卫星”选项。不要奇怪,在模拟器中,“使用无线网络”是无效的,使用该选项无法获取地理信息,因为模拟器中根本没有sim卡,也就无法通过基站来定位了。

如果在真机上就不同了。如果机器没有内置GPS模块,那么启用GPS卫星选项反而无法进行定位,具体情况只有多试几次才能明白。

@font-face { font-family: "宋体"; }@font-face { font-family: "Cambria"; }p.MsoNormal, li.MsoNormal, div.MsoNormal { margin: 0cm 0cm 10pt; font-size: 12pt; font-family: "Times New Roman"; }div.Section1 { page: Section1; }

Geocoder需要访问internet,在真机上调试请打开wifi或者3G网络。

Android LocationGPS中的应用()

分类: Android 2011-04-02 14:01 1030人阅读 评论(1) 收藏 举报

 

这一篇其实跟GPS毫无关系。 继续上一篇的内容,讲GPS以外的东西,比如说Service的使用。比如说gps监控,它并不需要任何UI,在后台默默地运行就行。为什么不做成Service呢?悄悄地向服务器发送用户的位置坐标是一个不错的想法,因为它完全不需要用户的干预。当然为了保留用户权利,我们应当留一个地方让用户把服务关掉。

继续前一篇的工程,如果你没有保留前面的工作也没有关系,从头来就是了。

一、AndroidManifext.xml

新建Google Project ,注意选择Google APIs

编辑AndroidManifest.xml,加入相应的权限:

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

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

 

其次,由于我们使用了服务,需要在<application></application>标签加入一个<service>标签,以注册自己的服务:

< service android:label = "@string/app_name" android:name = ".GpsService" >

< intent-filter >

< action android:name = "start_gps_service" ></ action >

< category android:name = "android.intent.category.DEFAULT" ></ category >

</ intent-filter >

</ service >

这个.GpsService是我们自定义的service类,它可以相应一个”start_aps_servicee”的Action

二、main.java

我们这个应用就一个Activitymain.xml不用做任何修改。打开main.java,以下代码在菜单中添加了2个菜单项,以便让用户开启和关闭gps

// 创建菜单

public boolean onCreateOptionsMenu(Menu menu) {

// TODO Auto-generated method stub

super .onCreateOptionsMenu(menu);

menu.add(0, Menu. FIRST + 1, 1, " 打开 GPS 监控 " );

menu.add(0, Menu. FIRST + 2, 2, " 关闭 GPS 监控 " );

return true ;

}

 

// 菜单项监听

public boolean onOptionsItemSelected(MenuItem item) {

Intent i;

// TODO Auto-generated method stub

super .onOptionsItemSelected(item);

switch (item.getItemId()) {

case Menu. FIRST + 1: // 打开 GPS 监控

this .setTitle( "GPS Service Started" );

i = new Intent( this , GpsService. class );

this .bindService(i, connection , Context. BIND_AUTO_CREATE );

break ;

case Menu. FIRST + 2: // 关闭 GPS 监控

this .setTitle( "GPS Service Stopped" );

// 通过检测 mBinder 是否为空来判断服务是否已绑定,从而避免 service not registered 错误

if ( mBinder != null ){

i = new Intent( this , GpsService. class );

this .unbindService( connection );

mBinder=null;//释放mBinder,防止重复解绑定

}

break ;

}

return true ;

}

this.bindService this. unbindService 和分别启动和关闭服务。这里的服务是GpsService类,呆会再介绍。

AndroidService有两种使用方式。一种比较简单,使用ActivitystartService/stopService方法启动关闭服务。但这种方式不能满足我们的需要,因为无法向Service类传递参数。因此我们决定使用第2种方式,即IBinder的方式。其实第2种方式跟第1种方式的区别只在于,service实现了onBind方法,在onBind方法中返回了IBinder对象,你可以把更多的代码实现在IBinder中。同时service类会调用IBinder中的方法,于是我们可以通过该方法向IBinder传递参数。

而且在调用方法上,bindServiceunbindService都需要一个 ServiceConnection 类的参数。因此我们在main.java中申明了这个 ServiceConnection:

ServiceConnection connection = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName arg0, IBinder arg1) {

// TODO Auto-generated method stub

mBinder = (IGpsBinder) arg1;

if ( mBinder != null ) {

mBinder .bindService(main. this );

}

 

}

@Override

public void onServiceDisconnected(ComponentName name) {

// TODO Auto-generated method stub

 

}

};

在这个 ServiceConnection 示例中,有两个方法需要实现。我们关注的只有 onServiceConnected方法。

这个方法在连接服务后调用。我们在这个方法中使用了一个接口对象IGpsBinder

IGpsBinder mBinder = null ;

IGpsBinder是一个接口,它对应的实现是GpsBinder类。这两个东西后面会说。

先看服务的绑定,代码:

i = new Intent( this , GpsService. class );

this .bindService(i, connection , Context. BIND_AUTO_CREATE );

 

首先,Intent中已经包含了服务的实现类GpsService,这样在bindService时就可以把服务类传递进去。其次,ServiceConnection也被传递进去了。

这样,当bindService时,会先调用服务类GpsServiceonBind方法,这个方法会返回一个IBinder对象(其实是IGpsBinder)。在后面GpsService的代码中可以看到,其实onBinder方法返回了一个IGpsBinder的实现类GpsBinder

这样就获得了一个GpsBinder,然后把这个GpsBinder传递给connectiononServiceConnected 方法。通过 onServiceConnected 代码,最终调用的是GpsBinder的接口方法bindService(这个方法中包含了一个参数,我们通过它把main这个Activity传递进去)。

对于服务的解绑定,先调用GpsServiceunOnbind方法。

了解了服务绑定的大致工作过程。接下来就是服务类GpsService了。

三、GpsService.java

public class GpsService extends Service {

private GpsBinder binder = new GpsBinder();

@Override

public void onCreate() {

super .onCreate();

}

 

@Override

public void onDestroy() {

// TODO Auto-generated method stub

super .onDestroy();

}

 

@Override

public void onStart(Intent intent, int startId) {

// TODO Auto-generated method stub

super .onStart(intent, startId);

}

 

@Override

public boolean onUnbind(Intent intent) {

// TODO Auto-generated method stub

binder .unbindService();

return true ;

}

 

@Override

public IBinder onBind(Intent intent) {

// TODO Auto-generated method stub

return binder ;

}

}

可以看到,GpsService的代码很简单,继承了Service,覆盖Service5个生命周期方法。但除了onUnbindonBinder方法外,我们都使用了默认的实现。在onUnbind方法中,我们调用binderunbindService方法进行解绑定。其中,binder是一个GpsBinder,后面会介绍。在onBind方法中,我们返回了GpsBinder对象。这个GpsBinder对象能为我们做些什么呢?

四、GpsBinder.java

public class GpsBinder extends Binder implements IGpsBinder{

private LocationManager locationManager ;

private Location location ;

private String provider ;

private Context mContext = null ;

public GpsBinder(){

 

}

@Override

// 接口暴露方法

public void bindService(Context ctx){

mContext =ctx;

// 获取 LocationManager 服务

locationManager = (LocationManager) mContext

.getSystemService(Context. LOCATION_SERVICE );

 

// 如果未设置位置源,打开 GPS 设置界面

openGPS();

// 获取 Location Provider

getProvider();

// 获取位置

location = locationManager .getLastKnownLocation( provider );

// 显示位置信息到文字标签

updateWithNewLocation( location );

// 注册监听器 locationListener ,第 2 3 个参数可以控制接收 gps 消息的频度以节省电力。第 2 个参数为毫秒,

// 表示调用 listener 的周期,第 3 个参数为米 , 表示位置移动指定距离后就调用 listener

locationManager .requestLocationUpdates( provider , 2000, 10,

locationListener );

}

public void unbindService(){

// 注销 location 监听器

locationManager .removeUpdates( locationListener );

}

// 获取 Location Provider

private void getProvider() {

// 构建位置查询条件

Criteria criteria = new Criteria();

// 查询精度:高

criteria.setAccuracy(Criteria. ACCURACY_FINE );

// 是否查询海拨:否

criteria.setAltitudeRequired( false );

// 是否查询方位角 :

criteria.setBearingRequired( false );

// 是否允许付费:是

criteria.setCostAllowed( true );

// 电量要求:低

criteria.setPowerRequirement(Criteria. POWER_LOW );

// 返回最合适的符合条件的 provider ,第 2 个参数为 true 说明 , 如果只有一个 provider 是有效的 , 则返回当前 provider

provider = locationManager .getBestProvider(criteria, true );

}

 

// Gps 消息监听器

private final LocationListener locationListener = new LocationListener() {

// 位置发生改变后调用

public void onLocationChanged(Location location) {

updateWithNewLocation(location);

}

 

// provider 被用户关闭后调用

public void onProviderDisabled(String provider) {

updateWithNewLocation( null );

}

 

// provider 被用户开启后调用

public void onProviderEnabled(String provider) {

}

 

// provider 状态变化时调用

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

}

};

 

// Gps 监听器调用,处理位置信息

private void updateWithNewLocation(Location location) {

// 利用反射机制调用 mContext locationChanged 方法

Class<?>[] types = new Class[] {Location. class }; // 这个方法有 1 个参数

try {

Method m = mContext .getClass().getMethod( "locationChanged" , types);

if (m != null ) m.invoke( mContext , location);

} catch (Exception e) {

Log.e (GpsBinder. class .getName(), e.toString());

}

Log.i (GpsBinder. class .getName(), "location is changed:" +location);

}

// 判断是否开启 GPS ,若未开启,打开 GPS 设置界面

private void openGPS() {

boolean bGPS = locationManager

.isProviderEnabled(android.location.LocationManager. GPS_PROVIDER );

boolean bNetwork = locationManager

.isProviderEnabled(android.location.LocationManager. NETWORK_PROVIDER );

Log.e ( ":::" , "bGPS=" + bGPS + "bNetwork=" + bNetwork);

if (bGPS || bNetwork) {

Toast.makeText ( mContext , " 位置源已设置! " , Toast. LENGTH_SHORT ).show();

return ;

}

// Toast.makeText(this, " 位置源未设置! ", Toast.LENGTH_SHORT).show();

((Activity) mContext ).showDialog(0);

}

}

这个代码不解释了,完全就是把我们在上一篇中关于Location的代码搬到这里了。需要注意的是 unbindService bindService updateWithNewLocation 方法。

unbindService方法前面已提过,调用main对象的unbindService方法时被调用,它会把location监听器注销,于是位置变化不再通知监听器。

bindService方法是IGpsBinder中的接口方法,它注册了location监听器,当Gps芯片检测到位置发生改变时通知locationListener监听器。值得注意的是这个方法中的ctx参数,实际上是把main这个Activity传递进来了,并且保存在mContext变量中,这样在 updateWithNewLocation 方法中可以使用main

updateWithNewLocation 方法是监听器里的主要方法,它使用了一个技巧:利用java反射机制调用mContextlocationChanged 方法。如果main没有实现 locationChanged 方法,则什么也不会做。这个技巧用来更新UI是很实用的。

当然,我们在main.java中也实现了这个 locationChanged 方法:

 

public void locationChanged(Location loc) {

updateWithNewLocation(loc);

}

// Gps 监听器调用,处理位置信息

private void updateWithNewLocation(Location location) {

String latLongString;

TextView myLocationText = (TextView) findViewById(R.id. text );

if (location != null ) {

double lat = location.getLatitude();

double lng = location.getLongitude();

latLongString = " 纬度 :" + lat + "/n 经度 :" + lng;

} else {

latLongString = " 无法获取地理信息 " ;

}

myLocationText.setText( " 您当前的位置是 :/n" + latLongString + "/n 地址 :"

+ getAddressString(getAddressbyGeoPoint(location)));

}

// 获取地址信息

private List<Address> getAddressbyGeoPoint(Location location) {

List<Address> result = null ;

// 先将 Location 转换为 GeoPoint

// GeoPoint gp =getGeoByLocation(location);

try {

if (location != null ) {

// 获取 Geocoder ,通过 Geocoder 就可以拿到地址信息

Geocoder gc = new Geocoder( this , Locale.getDefault ());

// double geoLatitude = (int ) gp.getLatitudeE6() / 1E6;

// double geoLongitude = (int ) gp.getLongitudeE6() / 1E6;

result = gc.getFromLocation(location.getLatitude(),

location.getLongitude(), 1);

}

} catch (Exception e) {

e.printStackTrace();

}

return result;

}

 

// 把地址信息转换为一定格式的字符串

private String getAddressString(List<Address> list) {

if (list != null && list.size() > 0) {

Address add = list.get(0);

return String.format ( "%s%s%s%s" , add.getCountryName(),

add.getAdminArea(), add.getLocality(),

add.getThoroughfare());

} else

return "" ;

}

内容很多,但大部分仍然是上一篇中实现过的内容。

此外,在main.java中还要实现托管对话框:

protected Dialog onCreateDialog( int id) {

Log.e ( "::::" , "ddsdg" );

return new AlertDialog.Builder( this )

.setMessage( " 位置源未设置!是否现住设置位置源? " )

.setPositiveButton( " " , new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int which) {

// 转至 GPS 设置界面

Intent intent = new Intent(

Settings. ACTION_SECURITY_SETTINGS );

startActivityForResult(intent, 0);

}

})

.setNegativeButton( " " , new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss(); // removeDialog(0); 移除对话框

}

}).create();

}

因为在GpsBinderopenGps方法中调用了mContext.showDialog方法。

五、IGpsBinder.java

IGpsBinder中只定义了1个接口方法,这也是在ServiceConnection唯一能识别和调用的IBinder方法:

 

public interface IGpsBinder {

// 接口暴露方法

public void bindService(Context ctx);

}

方法本身含有一个参数,这个就是把main对象传递给GpsBinder的地方。

 

六、测试

可以在模拟器中进行测试了,别忘记前面说的利用DDMSLocation Controls来修改Gps坐标。点击“打开GPS监控”菜单,可以看到模拟器从Gps获取到当前位置信息,修改坐标经纬度后点”send”,新坐标立即被捕获并显示在窗口里;点“关闭GPS监控”菜单后,再修改坐标就没用了,还是显示原来的坐标。

 

分享到:
评论

相关推荐

    Android GPS定位简单 DEMO

    一,在很多提供定位服务的应用程序中,不仅需要获取当前的位置信息,还需要监视位置的变化,在位置改变时调用特定的处理方法 ,其中LocationManager提供了一种便捷、高效的位置监视方法requestLocationUpdates(),...

    Android应用实现GPS定位返回经纬度Demo

    在Android应用中实现GPS返回Location经纬度值 不为null 可以实现

    androidGPS应用开发

    GPS的应用开发,里面都是详细的代码还有注释 有一下内容: 支持GPS的核心API 获取LocationProvider 获取可用的LocationProvider 通过名称获取指定LocationProvider 根据Criteria获得LocationProvider

    Android应用源码之androidGPS及WIFI基站定位坐标源码.zip

    Android应用源码之androidGPS及WIFI基站定位坐标源码.zip

    Android应用开发详解

    Android中的GPS应用,讲述了LocationManager、LocationProvider、跟踪、定位、Geocoder正逆向编解码和可视化位置服务 第三篇 应用篇 第15章 Android应用案例——移动警务通 Android 应用案例——移动警务通,通过...

    安卓GPS应用案例

    安卓GPS应用案例,LocationManager和locationProvider,Criteria

    Android实现GPS卫星定位Demo

    Android中应用此代码实现返回location经纬度,已验证可以实现

    Android入门到精通源代码.

    第11章 Android的GPS应用开发 11.1 GPS在手机中的应用 11.2 Android Location-Based API简介 11.3 Android模拟器支持的GPS定位文件 11.3.1 KML 11.3.2 NMEA 11.4 应用实例详解:确定当前 位置的GPS程序 11.4.1 实例...

    Android高级编程--源代码

     由于Android构建在开源代码的框架之上,而且提供了强大的SDK库和开放的理念,所以它为广大的没有任何移动应用程序开发经验的新手开辟了一条开发完美的移动应用程序的康庄大道。而富有经验的移动开发人员现在也可以...

    Android编程获取GPS数据的方法详解

    GPS是Android系统中重要的组成部分,通过它可以衍生出众多的与位置相关的应用。 Android的GPS有一个专门的管理类,称为LocationManager,所有的GPS定位服务都由其对象产生并进行控制。 首先需要明确的是,...

    mock_location:MockLocation 可用于设置 GPS 提供商返回的虚假 GPS 位置以进行测试

    MockLocation 是一个项目,是一个 Android 应用程序,可用于设置 GPS 提供商返回的虚假 GPS 位置以进行测试,让您记录路线并回放。 它使用 ActionBarSherlock、Google Maps Android API v2 和我编写的位置库。 ...

    安卓源码包 Android GPS 开发 地图&导航&定位&指南等设计代码合集 (45个).zip

    AMap_Android_API_Demo_V2.0.4(Location_API_V1.0.2).zip Android 4.0下指南针开发源码,可在Nexus 4上完美运行.zip Android GPS 开发client端代码分享.zip android 获取精度纬度.rar androidGPS及WIFI基站定位坐标...

    Android GPS定位测试(附效果图和示例)

    今天因为工作需要,把以前编写的一个GPS测试程序拿出来重新修改了一下。...NMEA是一种标准化数据格式,不仅仅GPS上应用了,其他一些工业通信也是使用这种标准化数据格式。解析相关数据然后显示出来,就完

    Overland_android:适用于Android设备的GPS记录器

    该应用程序基于Aaron Parecki开发的iOS版Overland应用程序 这是一项从Android设备收集数据以测试Android的Core Location API及其各种设置的实验。 该应用程序跟踪: GPS定位运动状态(步行,跑步,驾驶,骑自行车,...

    《Android高级编程》

    9.2.1 在应用程序中使用SMS 9.2.2 发送SMS信息 9.2.3 监听SMS消息 9.2.4 紧急响应的SMS示例 9.2.5 紧急响应自动化 9.3 小结 第10章 访问Android硬件 10.1 使用媒体API 10.1.1 播放媒体资源 10.1.2 录制多媒体 10.2 ...

    tracking-location-provider-android:在Google Maps中顺利跟踪android移动和动画标记。 跟踪已在前台和后台进行。 在Android Oreo 8.1,Android 6.0和Android 5.0中进行了测试

    跟踪位置提供者Android 在此演示中,我同时介绍了前景跟踪和背景跟踪。前景追踪仅当应用程序在前景中时,前景跟踪才起作用。 要开始前景跟踪,请点击“ START FOREGROUND TRACKING”按钮。 在请求位置更新之前,请...

    老罗android开发视频教程全集百度网盘下载

    Android进阶高级:蓝牙/WIFI SMS/MMS 应用实现 深层次解析GPS原理,实现LocationManager/LocationProvider 进行定位/跟踪/查找/趋近警告以及Geocoder正逆向编解码等技术细节 2D图形库(Graphics/View)详解 SDCARD/...

    新版Android开发教程.rar

    的 Android SDK 提供了在 Android 平台上使用 JaVa 语言进行 Android 应用开发必须的工具和 API 接口。 特性 • 应用程序框架 支持组件的重用与替换 • Dalvik Dalvik Dalvik Dalvik 虚拟机 专为移动设备优化 • ...

Global site tag (gtag.js) - Google Analytics