目录
安卓开发——利用高德地图进行开发
在开发校园公交项目时,司机端需要实现地图导航的相关功能。主要包括位置上传,位置获取,线路及站点绘制。
-
首先考虑数据库的设计。数据库需要用司机位置表来存放司机的坐标,外键链接司机表,来方便存储和获取目标司机的位置。
-
然后考虑后端视图函数的设计。后端视图函数中要实现的功能有接收司机发来的位置信息及获取数据库位置信息。(可以把站点线路等信息存储在数据库中,登录软件后检测并获取更新线路及站点信息,但我这里只有一条线和一组站点,故只用bean类存储了相关信息)
-
然后是Android前端UI的设计及交互。只需要一个地图控件和两个功能按钮。
-
最后是Android主线程的设计,需要利用okhttp发送和接收request请求,然后处理相关数据最后呈现在UI界面。最重要的是地图的生命周期需要重视,因为容易出现反复加载地图导致地图数量达到上限并产生异常。(这里我使用了ViewModel来进行观察者模式,在http请求中仅仅改变参数就能监听这个变化的参数)
下面我来一个一个讲解
1.数据库的设计
数据库上就建立一个司机位置表,用于记录每个司机的坐标等信息。位置更新时间更是能帮助管理员知晓司机的工作状态。
class Driver_location(models.Model):
location_id=models.AutoField(primary_key=True,verbose_name="id")
location_driver=models.ForeignKey(Driver,on_delete=models.CASCADE,verbose_name="司机")
latitude = models.DecimalField(null=True,max_digits=9, decimal_places=6,verbose_name="纬度")
longitude = models.DecimalField(null=True,max_digits=9, decimal_places=6,verbose_name="经度")
last_modified = models.DateTimeField(auto_now=True,verbose_name="经纬更新时间") # 自动更新时间
isworking = models.BooleanField(default=False,verbose_name="是否在岗")
direction = models.BooleanField(default=False,verbose_name="行驶方向")
next_station =models.ForeignKey(Bus_station,null=True,related_name='driver_next_station',on_delete=models.CASCADE,verbose_name="下一站点")
now_station =models.ForeignKey(Bus_station,null=True,related_name='driver_now_station',on_delete=models.CASCADE,verbose_name="所在站点")
def __str__(self):
return self.name
class Meta:
db_table='driver_location'
2.后端视图函数的设计
下面的视图函数包括处理司机坐标信息,处理在岗信息,获取同事信息。函数的注释也比较详细,这里不多解释
def driver_locationSend(request): #处理发送来的司机坐标 并推算出其行驶方向和目标站点
if request.method=='POST':
driverGet=request.POST.get('driver_account', '')
latitudeGet=request.POST.get('latitude', '')
longitudeGet=request.POST.get('longitude', '')
get_nearest_station(latitudeGet,longitudeGet,driverGet)
driver=Driver.objects.filter(account=driverGet)
if(driver.exists):
# Driver_location.objects.filter(location_driver=driver[0]).update(latitude=latitudeGet,longitude=longitudeGet)
# Driver_location.save()
driver_location = Driver_location.objects.get(location_driver=driver[0])
driver_location.latitude = latitudeGet
driver_location.longitude = longitudeGet
driver_location.save() # 手动保存对象,触发DateTimeField自动更新
print(latitudeGet,longitudeGet)
return HttpResponse('OK')
def get_nearest_station(latitudeGet,longitudeGet,driverGet): #此函数目的是通过新获得的时间及坐标来算出最近的站点以及下一站
latitude=latitudeGet
longitude=longitudeGet
#latitude=45.778838
#longitude=126.679600
close_station=Bus_station.objects.raw('SELECT station_id,\
ST_DISTANCE(POINT(station_latitude, station_longitude),POINT(%s, %s)) AS distance \
FROM bus_station ORDER BY distance limit 1',[latitude,longitude])
driver=Driver.objects.filter(account=driverGet)
if(driver.exists()):
driver_location = Driver_location.objects.get(location_driver=driver[0])
if(driver_location.now_station is not None and driver_location.now_station!=close_station[0]):
#目前位置不为空,且新位置和前位置不同
#print(">>>>>????")
station_old=Bus_station.objects.get(station_name=driver_location.now_station)
ntstation=station_old.next_station
ltstation=station_old.last_station
nw_station=Bus_station.objects.get(station_name=close_station[0])
if(ntstation!=close_station[0] and ltstation!=close_station[0]):
#若跑的离另一个不是目标站的站近了,有两种可能:1.车刚开动,上次的残留信息还在。//直接在onDestroy时加上清除信息不就行了(已加)
#2.车出现错乱,不小心离另一个站近了,这时直接忽略掉这个站,直到进行到下一个站
return
#如果新站既是之前站的下一站又是之前站的上一站,则说明到达终点站,其下一站应为非上一站的站
if(ntstation==ltstation):
if(nw_station.next_station==station_old):
driver_location.next_station=nw_station.last_station
elif(nw_station.last_station==station_old):
driver_location.next_station=nw_station.next_station
elif(ntstation==close_station[0]): #如果新站为之前站的下一站,则定义方向为正,并给出下一站
true_next=nw_station.next_station
driver_location.next_station=true_next
#print(true_next)
elif(ltstation==close_station[0]): #若为前一站,同理
true_last=nw_station.last_station
driver_location.next_station=true_last
#print(true_last)
#print(station_old[0].station_name)
driver_location.now_station=close_station[0]
driver_location.save()
#print(close_station[0])
return
def driver_on_the_job(request): #处理司机在岗信息
if request.method=='POST':
driverGet=request.POST.get('driver_account', '')
ONorOFFGet=(int)(request.POST.get('ONorOFF', ''))
driver=Driver.objects.filter(account=driverGet)
#print(ONorOFFGet)
if(driver.exists):
# print(ONorOFFGet)
driver_isworking = Driver_location.objects.get(location_driver=driver[0])
if ONorOFFGet==1:
driver_isworking.isworking=True
else:
driver_isworking.isworking=False
driver_isworking.now_station=None
driver_isworking.next_station=None
driver_isworking.save() # 手动保存对象
return HttpResponse('OK')
def driver_get_workmates(request): #获取司机同事信息
if request.method=='POST':
driverGet=request.POST.get('driver_account', '')
driver_line_get=Driver.objects.get(account=driverGet).driver_line
driver_school_get=Driver.objects.get(account=driverGet).driver_school
line_id_get=Bus_line.objects.get(line_name=driver_line_get,line_school=driver_school_get).line_id
with connection.cursor() as cursor:
cursor.execute("select * from driver_and_location \
where driver_line_id=%s \
and isworking=%s and account!=%s",[line_id_get,1,driverGet])
columns = [col[0] for col in cursor.description]
results = [dict(zip(columns, row)) for row in cursor.fetchall()]
#results = cursor.fetchall()
#print(results)
res=[
{
'name':result['name'],
'account':result['account'],
'latitude':result['latitude'],
'longitude':result['longitude'],
}
for result in results
]
#print(res)
return JsonResponse(res,safe=False)
#return HttpResponseServerError('账号不存在')
3.UI的设计及交互
这里我打开软件截了个图,可以看到“显示同事”按钮是深色的,这是按下去后设置的颜色效果,图片上就是线路,蓝色公交标识就是其他同事的位置。
下面是xml文件,一些图片我也不放了你们可以自己找图标然后放进去就可以了。效果就是上图那样。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragment.Map_Driver_Fragment"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.amap.api.maps.TextureMapView
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="7"/>
<Button android:id="@+id/show_others_button"
android:layout_width="180dp"
android:layout_height="50dp"
android:background="@drawable/background_driver_5"
android:text="显示同事"
android:drawableTop="@drawable/baseline_people_alt_24"
app:layout_constraintStart_toEndOf="@+id/start_button"
app:layout_constraintTop_toTopOf="@+id/start_button"/>
<Button
android:id="@+id/start_button"
android:layout_width="180dp"
android:layout_height="50dp"
android:background="@drawable/background_driver_4"
android:drawableTop="@drawable/baseline_location_on_24"
android:text="位置发送"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.112"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.904" />
</androidx.constraintlayout.widget.ConstraintLayout>
4.Android主线程的设计
我的代码使用了高德地图api,如果需要使用的话可以访问高德地图API
想在开发中获取地图某点的坐标,可以访问高德地图坐标拾取器
package com.example.myapplication.fragment;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Observable;
import android.graphics.Color;
import android.location.Location;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import com.amap.api.location.AMapLocationClient;
import com.amap.api.maps.AMap;
import com.amap.api.maps.CameraUpdateFactory;
import com.amap.api.maps.TextureMapView;
import com.amap.api.maps.model.BitmapDescriptor;
import com.amap.api.maps.model.BitmapDescriptorFactory;
import com.amap.api.maps.model.CameraPosition;
import com.amap.api.maps.model.LatLng;
import com.amap.api.maps.model.Marker;
import com.amap.api.maps.model.MarkerOptions;
import com.amap.api.maps.model.MyLocationStyle;
import com.amap.api.maps.model.Polyline;
import com.amap.api.maps.model.PolylineOptions;
import com.android.volley.AuthFailureError;
import com.android.volley.NetworkResponse;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.example.myapplication.R;
import com.example.myapplication.javabean.HEU_line;
import com.example.myapplication.javabean.ServerSetting;
import com.example.myapplication.javabean.django_url;
import com.example.myapplication.Activity.DriverRegisterActivity;
import com.example.myapplication.Activity.Driver_main_interface;
import com.example.myapplication.Activity.LoginActivity;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.navigation.NavigationBarView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
/**
* A simple {@link Fragment} subclass.
* Use the {@link Map_Driver_Fragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class Map_Driver_Fragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
//静态不可修改常量
private final Handler handler = new Handler(); //消息处理器
//private final BitmapDescriptor driver_picture = BitmapDescriptorFactory.fromResource(R.drawable.bus_station);
private ArrayList<MarkerOptions> workmates_List = new ArrayList<>(); //同事坐标列表
protected static CameraPosition cameraPosition;
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
private View rootView;
private Button start_button;
private int ONorOFF=-1;
private TextureMapView textureMapView;
//com.amap.api.maps.TextureMapView mMapView = null;
private AMap aMap;
private String driver_account;
private String all_latitude,all_longitude;
public static final LatLng HEU = new LatLng(126.681946,45.775789);
private Button show_others_button;
private int SHOWorNOT_SHOW=-1;
public MyViewModel myViewModel;
private boolean show_mapText,show_mapBuild;
public Map_Driver_Fragment() {
// Required empty public constructor
}
public class MyViewModelFactory implements ViewModelProvider.Factory {
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
if (modelClass.isAssignableFrom(MyViewModel.class)) {
return (T) new MyViewModel();
}
throw new IllegalArgumentException("Unknown ViewModel class");
}
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment Map_Driver_Fragment.
*/
// TODO: Rename and change types and number of parameters
public static Map_Driver_Fragment newInstance(String param1, String param2) {
Map_Driver_Fragment fragment = new Map_Driver_Fragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
AMapLocationClient.updatePrivacyShow(getActivity(), true, true);
AMapLocationClient.updatePrivacyAgree(getActivity(), true);
//ONorOFF=-1;
// 如果 rootView 为空,说明尚未创建过视图
if(rootView==null)
{
rootView=inflater.inflate(R.layout.fragment_map__driver_, container, false);
}
//获取地图控件引用
start_button=rootView.findViewById(R.id.start_button);
show_others_button=rootView.findViewById(R.id.show_others_button);
textureMapView = (TextureMapView) rootView.findViewById(R.id.map);
//mMapView = rootView.findViewById(R.id.map);
//在activity执行onCreate时执行mMapView.onCreate(savedInstanceState),创建地图
SharedPreferences sharedCheckbox= requireActivity().getSharedPreferences("user_checkBox", Context.MODE_PRIVATE);
show_mapText=sharedCheckbox.getBoolean("Show_map_text", false);
show_mapBuild= sharedCheckbox.getBoolean("Show_map_build", false);
//myViewModel =new ViewModelProvider(this).get(MyViewModel.class);
myViewModel = new ViewModelProvider(this, new MyViewModelFactory()).get(MyViewModel.class);
if(textureMapView != null) {
textureMapView.onCreate(savedInstanceState);
if(aMap==null) {
aMap = textureMapView.getMap();
}
if (getCameraPosition() == null) {
aMap.moveCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(HEU, 10, 0, 0)));
}else {
aMap.moveCamera(CameraUpdateFactory.newCameraPosition(getCameraPosition()));
}
}
SharedPreferences sharedPreferences= requireContext().getSharedPreferences("user", Context.MODE_PRIVATE);
driver_account=sharedPreferences.getString("driver_account","");
Get_bus_location();
print_station(); //绘制站点
print_line(); //绘制线路
return rootView;
}
private void print_line() {
HEU_line line=new HEU_line();
List<LatLng> line_first=line.Get_line_first();
Polyline polyline = aMap.addPolyline(new PolylineOptions()
.addAll(line_first)
.width(10)
.color(Color.argb(255, 36, 164, 255)));
}
private void print_station() {
HEU_line station=new HEU_line();
ArrayList<MarkerOptions> stations=station.Get_station_first();
aMap.addMarkers(stations,false);
}
/**
* 方法必须重写
*/
@Override
public void onResume() {
super.onResume();
textureMapView.onResume();
int backgroundResource_start_button_gray = R.drawable.background_dirver_4_gray;
int backgroundResource_start_button = R.drawable.background_driver_4;
start_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ONorOFF=-ONorOFF;
On_the_job_information(); //发送在岗信息
startLocationUpdates(); //发送坐标信息
if(ONorOFF==1)
start_button.setBackground(ContextCompat.getDrawable(requireContext(), backgroundResource_start_button_gray));
else
start_button.setBackground(ContextCompat.getDrawable(requireContext(), backgroundResource_start_button));
//Toast.makeText(getContext(), "点了", Toast.LENGTH_SHORT).show();
}
});
int backgroundResource_show_others_gray = R.drawable.background_dirver_5_gray;
int backgroundResource_show_others = R.drawable.background_driver_5;
show_others_button.setOnClickListener(new View.OnClickListener() { //通过按钮来定时获取其他司机位置
@Override
public void onClick(View view) {
SHOWorNOT_SHOW=-SHOWorNOT_SHOW;
Show_workmates();
if(SHOWorNOT_SHOW==1)
show_others_button.setBackground(ContextCompat.getDrawable(requireContext(), backgroundResource_show_others_gray));
else
show_others_button.setBackground(ContextCompat.getDrawable(requireContext(), backgroundResource_show_others));
}
});
myViewModel.getMyVariable().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer value) {
if (value == 1) {
handler.postDelayed(Show_workmatesRunnable, 5000);
myViewModel.setMyVariable(0);
}
}
});
}
private void Show_workmates() {
if(SHOWorNOT_SHOW==1)
{
handler.postDelayed(Show_workmatesRunnable,5000); //开启同事位置获取
}else if(SHOWorNOT_SHOW==-1)
{
handler.removeCallbacks(Show_workmatesRunnable);
clean_last_workmates();
}
}
public void On_the_job_information() {
String url = ServerSetting.ServerPublicIpAndPort+"driver_on_the_job/";
OkHttpClient okHttpClient = new OkHttpClient(); //构建一个网络类型的实例
RequestBody requestBody = new FormBody.Builder()
.add("driver_account", driver_account)
.add("ONorOFF", String.valueOf(ONorOFF))
.build();
Request request = new Request.Builder() //构建一个具体的网络请求对象,具体的请求url,请求头,请求体等
.url(url)
.post(requestBody)
.build();
Call call = okHttpClient.newCall(request); //将具体的网络请求与执行请求的实体进行绑定
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
requireActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(requireActivity(), "发送失败:请检查网络", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException {
if (!response.isSuccessful()) {
// 请求不成功,处理错误响应
final String errorMessage = response.body().string();
// 切换到主线程更新 UI
requireActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(requireActivity(), "发送失败:" + errorMessage, Toast.LENGTH_SHORT).show();
}
});
}
}
});
}
private void startLocationUpdates() {
if(ONorOFF==1)
{
handler.postDelayed(locationRunnable,3000);
}else if(ONorOFF==-1)
{
handler.removeCallbacks(locationRunnable);
}
}
private final Runnable Show_workmatesRunnable = new Runnable() {
@Override
public void run() {
get_new_workmates(); //获取同事位置
}
};
private void print_new_workmates() {
aMap.addMarkers(workmates_List,false);
}
private void clean_last_workmates() {
workmates_List.clear(); //清除原来列表
for (Marker marker : aMap.getMapScreenMarkers()) {
if (marker!=null && marker.getTitle()!=null && marker.getTitle().equals("workers")) {
marker.remove();
}
}
}
public void get_new_workmates() {
String url = ServerSetting.ServerPublicIpAndPort+"driver_get_workmates/";
OkHttpClient okHttpClient = new OkHttpClient(); //构建一个网络类型的实例
RequestBody requestBody = new FormBody.Builder()
.add("driver_account", driver_account)
.build();
Request request = new Request.Builder() //构建一个具体的网络请求对象,具体的请求url,请求头,请求体等
.url(url)
.post(requestBody)
.build();
Call call = okHttpClient.newCall(request); //将具体的网络请求与执行请求的实体进行绑定
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
requireActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(requireActivity(), "获取失败:请检查网络", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException {
if (!response.isSuccessful()&&response.body() != null) {
// 请求不成功,处理错误响应
final String errorMessage = response.body().string();
// 切换到主线程更新 UI
requireActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(requireActivity(), "获取失败:" + errorMessage, Toast.LENGTH_SHORT).show();
}
});
} else {
String workmates_name,workmates_account,workmates_latitude,workmates_longitude;
BitmapDescriptor driver_picture = BitmapDescriptorFactory.fromResource(R.drawable.driver_mates);
try {
if (response.body() != null){
clean_last_workmates(); //清除之前的同事位置
JSONArray jsonArray =new JSONArray(response.body().string()); //将string类型的response转换为JSONObject类型的object
for(int i=0;i<jsonArray.length();i++)
{
JSONObject object = jsonArray.getJSONObject(i);
workmates_name=object.getString("name");
workmates_account=object.getString("account");
workmates_latitude=object.getString("latitude");
workmates_longitude=object.getString("longitude");
double latitude=Double.parseDouble(workmates_latitude);
double longitude=Double.parseDouble(workmates_longitude);
//Log.e("TAG?!?!?!?!", "run: "+ latitude+longitude);
workmates_List.add(new MarkerOptions().position(new LatLng(latitude,longitude)).title("workers").icon(driver_picture).snippet(workmates_name));
}
if(SHOWorNOT_SHOW==1) //若还处于展示阶段
{
requireActivity().runOnUiThread(new Runnable() { //在主线程处理绘制任务
@Override
public void run() {
print_new_workmates(); //绘制新的同事位置
}
});
}
//LiveData的setValue方法应该在主线程上调用,因为它是用来UI上观察数据变化的组件
myViewModel.setMyVariable(1); //为避免使用handker导致的互相调用导致的资源浪费导致崩溃,使用观察者来监听变量
//handler.postDelayed(Show_workmatesRunnable, 5000);
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
//Toast.makeText(getActivity(), "查找成功", Toast.LENGTH_SHORT).show();
}
}
});
}
// 位置更新的任务
private final Runnable locationRunnable = new Runnable() {
@Override
public void run() {
// 发送位置信息给服务器
driver_locationSend(all_latitude,all_longitude);
//Toast.makeText(getContext(), "点了", Toast.LENGTH_SHORT).show();
// 再次将任务推送到消息队列,实现循环执行
handler.postDelayed(this, 3000);
}
};
/**
* 方法必须重写
*/
@Override
public void onPause() {
super.onPause();
textureMapView.onPause();
}
/**
* 方法必须重写
*/
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
textureMapView.onSaveInstanceState(outState);
}
@Override
public void onDestroyView() { //这个方法一旦调用就会在该资源释放时崩溃,调查发现是textureMapView.onDestroy();
//的调用和安卓Heap Pointer Tagging特性冲突,第三方库可能不支持这个特性,所以我在AndroidManifest中屏蔽
try {
if (textureMapView != null) {
textureMapView.onDestroy();
}
} catch (Exception e) {
e.printStackTrace();
}
super.onDestroyView();
}
/**
* 方法必须重写
*/
@Override
public void onDestroy() {
handler.removeCallbacks(locationRunnable);//释放时要强制停止位置发送
handler.removeCallbacks(Show_workmatesRunnable);//停止位置获取
ONorOFF=-1;
On_the_job_information(); //将在岗信息换为下岗
setCameraPosition(aMap.getCameraPosition());
super.onDestroy();
// try { //在 Fragment 被销毁时getActivity().getSupportFragmentManager().isDestroyed()会导致空指针异常
// if (getActivity() != null) {
// if (!getActivity().getSupportFragmentManager().isDestroyed()) {
// Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager");
// childFragmentManager.setAccessible(true);
// childFragmentManager.set(this, null);
// }
// }
// } catch (NoSuchFieldException | IllegalAccessException e) {
// e.printStackTrace();
// }
}
CameraPosition getCameraPosition() {
return cameraPosition;
}
void setCameraPosition(CameraPosition cameraPosition) {
Map_Driver_Fragment.cameraPosition = cameraPosition;
}
private void Get_bus_location() { //获取定位
if (aMap == null) {
aMap = textureMapView.getMap();
}
aMap.showBuildings(show_mapBuild); //显示楼房
aMap.showMapText(show_mapText); //显示地图文字
MyLocationStyle myLocationStyle; //设置定位模式
myLocationStyle = new MyLocationStyle();//初始化定位蓝点样式类myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE);//连续定位、且将视角移动到地图中心点,定位点依照设备方向旋转,并且会跟随设备移动。(1秒1次定位)如果不设置myLocationType,默认也会执行此种模式。
myLocationStyle.interval(1000); //设置连续定位模式下的定位间隔,只在连续定位模式下生效,单次定位模式下不会生效。单位为毫秒。
myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_MAP_ROTATE); //设置为方向跟随手机方向
myLocationStyle.showMyLocation(true); //设置为显示蓝点,为false时不显示蓝点
//将设置应用到地图中
aMap.setMyLocationStyle(myLocationStyle);//设置定位蓝点的Style
//aMap.getUiSettings().setMyLocationButtonEnabled(true);设置默认定位按钮是否显示,非必需设置。
aMap.setMyLocationEnabled(true);// 设置为true表示启动显示定位蓝点,false表示隐藏定位蓝点并不进行定位,默认是false。
aMap.setOnMyLocationChangeListener(onMyLocationChangeListener);//启动定位监听器
}
public AMap.OnMyLocationChangeListener onMyLocationChangeListener = new AMap.OnMyLocationChangeListener() {
@Override
public void onMyLocationChange(Location location) {
if (location != null) { //检查是否为空,确保获得了有效信息
double latitude = location.getLatitude();//纬度
double longitude = location.getLongitude();//经度
all_latitude=String.valueOf(latitude);
all_longitude=String.valueOf(longitude);
}
}
};
public void driver_locationSend(String latitude, String longitude) {
String url = ServerSetting.ServerPublicIpAndPort+"driver_locationSend/";
OkHttpClient okHttpClient = new OkHttpClient(); //构建一个网络类型的实例
RequestBody requestBody = new FormBody.Builder()
.add("driver_account", driver_account)
.add("latitude", latitude)
.add("longitude", longitude)
.build();
Request request = new Request.Builder() //构建一个具体的网络请求对象,具体的请求url,请求头,请求体等
.url(url)
.post(requestBody)
.build();
Call call = okHttpClient.newCall(request); //将具体的网络请求与执行请求的实体进行绑定
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
requireActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(requireActivity(), "发送失败:请检查网络", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onResponse(@NonNull Call call, @NonNull okhttp3.Response response) throws IOException {
if (!response.isSuccessful()) {
// 请求不成功,处理错误响应
final String errorMessage = response.body().string();
// 切换到主线程更新 UI
requireActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(requireActivity(), "发送失败:" + errorMessage, Toast.LENGTH_SHORT).show();
}
});
}
}
});
}
public class MyViewModel extends ViewModel {
private MutableLiveData<Integer> myVariable = new MutableLiveData<>(); //livedata的具体类,为可变livedata,可以更改并在观察者模式观察并随时更新
public MyViewModel(){
}
public LiveData<Integer> getMyVariable() {
return myVariable;
}
public void setMyVariable(int value) {
//myVariable.setValue(value);
myVariable.postValue(value); //postvalue用于在主线程更新value,避免产生异常
}
}
}
这段代码可以说相当长了(我写的代码框架结构有待改进,太shi了),主要就包括地图初始化(172-193行)以及按钮的监听(217行开始)。
相机视角初始化和恢复
if(textureMapView != null) { textureMapView.onCreate(savedInstanceState); if(aMap==null) { aMap = textureMapView.getMap(); } if (getCameraPosition() == null) { aMap.moveCamera(CameraUpdateFactory.newCameraPosition(new CameraPosition(HEU, 10, 0, 0))); }else { aMap.moveCamera(CameraUpdateFactory.newCameraPosition(getCameraPosition())); } }
这段的作用是在已经存在这个地图时不再重复创建,并且如果没有地图时初始化时可以把相机视角放在我的目标地图附近(我这里设置的位置在HEU,这个HEU是一个LatLng坐标类型)
那个checkbox是用于选择是否显示地图建筑及地名的,我在设置界面存储了相关数据,可以在地图上进行更改。
地图生命周期处理
下面这段代码保证了地图的销毁时内容的清理,避免了内存的泄露。
/**
* 方法必须重写
*/
@Override
public void onPause() {
super.onPause();
textureMapView.onPause();
}
/**
* 方法必须重写
*/
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
textureMapView.onSaveInstanceState(outState);
}
@Override
public void onDestroyView() { //这个方法一旦调用就会在该资源释放时崩溃,调查发现是textureMapView.onDestroy();
//的调用和安卓Heap Pointer Tagging特性冲突,第三方库可能不支持这个特性,所以我在AndroidManifest中屏蔽
try {
if (textureMapView != null) {
textureMapView.onDestroy();
}
} catch (Exception e) {
e.printStackTrace();
}
super.onDestroyView();
}
/**
* 方法必须重写
*/
@Override
public void onDestroy() {
handler.removeCallbacks(locationRunnable);//释放时要强制停止位置发送
handler.removeCallbacks(Show_workmatesRunnable);//停止位置获取
ONorOFF=-1;
On_the_job_information(); //将在岗信息换为下岗
setCameraPosition(aMap.getCameraPosition());
super.onDestroy();
// try { //在 Fragment 被销毁时getActivity().getSupportFragmentManager().isDestroyed()会导致空指针异常
// if (getActivity() != null) {
// if (!getActivity().getSupportFragmentManager().isDestroyed()) {
// Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager");
// childFragmentManager.setAccessible(true);
// childFragmentManager.set(this, null);
// }
// }
// } catch (NoSuchFieldException | IllegalAccessException e) {
// e.printStackTrace();
// }
}
CameraPosition getCameraPosition() {
return cameraPosition;
}
void setCameraPosition(CameraPosition cameraPosition) {
Map_Driver_Fragment.cameraPosition = cameraPosition;
}
这段在销毁时保存了相机位置,便于下一次进入时视角不会先定到别的地方。(只要自己做过就知道,不这样设置的话每次进入都是定位北京)。
位置信息的获取
下面的代码可以监听位置。所以坐标可以设为全局变量,之后就会一直更新了,随时取用。
public AMap.OnMyLocationChangeListener onMyLocationChangeListener = new AMap.OnMyLocationChangeListener() {
@Override
public void onMyLocationChange(Location location) {
if (location != null) { //检查是否为空,确保获得了有效信息
double latitude = location.getLatitude();//纬度
double longitude = location.getLongitude();//经度
all_latitude=String.valueOf(latitude);
all_longitude=String.valueOf(longitude);
}
}
};
以上就是在安卓中使用高德地图组件进行开发的说明了,希望对大家有帮助。