I'm trying to use a SupportFragment Google Map with a navigation drawer, initializing the map every two seconds with new overlays pulled from a server. I should also mention that the google maps supportfragment is the default supportfragment, so it is already supposed to be open when the app starts. The problem is that the app is very slow and unresponsive when I run it, prompting my phone to open a dialog asking to close the app because it is unresponsive. I am assuming that it may be getMapAsync(), but that is just a very broad guess. I'm trying to fix the problem to make the app just as responsive when it was a regular fragment and without a navigationdrawer. Here is the code for some of the files that are relevant with the problem. If you need any other information or any other files, feel free to ask.
MainActivity.java:
package com.main.main;
import android.app.FragmentManager;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Polygon;
import com.google.android.gms.maps.model.PolygonOptions;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutionException;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener, OnMapReadyCallback {
private SupportMapFragment sMapFragment;
private android.support.v4.app.FragmentManager sFm;
private GoogleMap mMap;
List<ParkingSpot> list; //list of parking spots
private static final LatLng DEFAULT_ZOOM_IN = new LatLng(40, -120); //default zoom-in of map
public static final String IP_ADDRESS = "some ip";
public static final String DATABASE = "some db";
public static final String USER_USERNAME = "something";
public static final String USER_PASSWORD = "something";
public LatLngBounds bounds;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sMapFragment = SupportMapFragment.newInstance();
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.setDrawerListener(toggle);
toggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
if(bounds == null) {
sFm = getSupportFragmentManager();
sFm.beginTransaction().remove(sMapFragment).commit();
sFm.beginTransaction().add(R.id.map, sMapFragment).commit();
}
try {
Object result = new LongOperation().execute().get();
} catch (InterruptedException e) {
throw new NullPointerException("INTERRUPTED EXCEPTION");
} catch (ExecutionException e) {
throw new NullPointerException("EXECUTION EXCEPTION");
}
}
@Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
FragmentManager fm = getFragmentManager();
int id = item.getItemId();
sFm.beginTransaction().hide(sMapFragment).commitAllowingStateLoss();
if (id == R.id.nav_map) {
Toast.makeText(MainActivity.this, "MAP CLICKED!", Toast.LENGTH_SHORT);
sFm.beginTransaction().show(sMapFragment).commitAllowingStateLoss();
} else if (id == R.id.nav_history) {
Toast.makeText(MainActivity.this, "HISTORY CLICKED!", Toast.LENGTH_SHORT);
} else if (id == R.id.nav_notifications) {
Toast.makeText(MainActivity.this, "NOTIFICATIONS CLICKED!", Toast.LENGTH_SHORT);
} else if (id == R.id.nav_help) {
Toast.makeText(MainActivity.this, "HELP CLICKED!", Toast.LENGTH_SHORT);
} else if (id == R.id.nav_settings) {
Toast.makeText(MainActivity.this, "SETTINGS CLICKED!", Toast.LENGTH_SHORT);
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
@Override
public void onMapReady(GoogleMap googleMap) {
try {
Object result = new LongOperation().execute().get();
mMap = googleMap;
mMap.clear();
if (bounds == null) { //only completes first time when bounds == null
CameraPosition cameraPosition = new CameraPosition.Builder() //zooms in camera on default location
.target(DEFAULT_ZOOM_IN) //location (LatLng)
.zoom(18) //zoom integer
.build(); //builds camera
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); //animates camera to spot
mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID); //sets map to hybrid type
}
bounds = mMap.getProjection().getVisibleRegion().latLngBounds; //gets latlng for next query based on camera spot
PolygonOptions rectOptions;
for (ParkingSpot spot : list) { //loops through ArrayList of Parking Spots, adding them to map
rectOptions = new PolygonOptions()
.addAll(spot.getCoordinates())
.clickable(true); //adds all coordinates to map
if (spot.getStatus() == 'F') { //see ParkingSpot.java for char definitions/explanations
rectOptions.fillColor(Color.RED);
} else if (spot.getStatus() == 'T') {
rectOptions.fillColor(Color.GREEN);
} else if (spot.getStatus() == 'H') {
rectOptions.fillColor(Color.YELLOW);
}
Polygon polygon = mMap.addPolygon(rectOptions); //adds rectangle to map
spot.setPolygonId(polygon.getId());
}
mMap.setOnPolygonClickListener(new GoogleMap.OnPolygonClickListener() {
@Override
public void onPolygonClick(Polygon polygon) {
Toast.makeText(MainActivity.this, "You clicked on polygon " + getInfoById(polygon.getId()), Toast.LENGTH_SHORT).show();
}
});
mMap.setMyLocationEnabled(true);
mMap.setOnMyLocationButtonClickListener(new GoogleMap.OnMyLocationButtonClickListener() {
@Override
public boolean onMyLocationButtonClick() {
return false;
}
});
} catch (SecurityException e) {
throw new NullPointerException("SECURITY EXCEPTION");
} catch (InterruptedException e) {
throw new NullPointerException("INTERRUPTED EXCEPTION");
} catch (ExecutionException e) {
throw new NullPointerException("EXECUTION EXCEPTION");
}
}
public String getInfoById(String id) {
for (ParkingSpot spot : list) {
if (spot.getPolygonId().equals(id)) {
return spot.toString();
}
}
return "-1";
}
private class LongOperation extends AsyncTask<String, Void, List> { //main network operation to initialize list
@Override
protected List<ParkingSpot> doInBackground(String... params) {
list = new ArrayList<ParkingSpot>(); //initializes list to ArrayList
String url = "jdbc:mysql://" + IP_ADDRESS + "/" + DATABASE; //creates URL for MySQL query
try {
Class.forName("com.mysql.jdbc.Driver"); //Driver for MySQL query
Connection con = DriverManager.getConnection(url, USER_USERNAME, USER_PASSWORD); //Starts connection to server
PreparedStatement pst; //initializes PreparedStatement outside to allow scope
if (bounds == null) { //if requesting first time
pst = con.prepareStatement("select * from sample_parking_spots");
} else { //if requesting later than first time;
String statement = "select * from sample_parking_spots where coord1lat>=" + bounds.southwest.latitude + " and coord3lat<=" + bounds.northeast.latitude + " and coord3lon>=" + bounds.southwest.longitude + " and coord1lon<=" + bounds.northeast.longitude;
pst = con.prepareStatement(statement);
}
ResultSet rs = pst.executeQuery(); //gets information from server
while (rs.next()) {
String uid = "U" + rs.getString("id"); //gets UID
String coordinate1Lat = rs.getString("coord1lat"); //gets top left latitude (x)
String coordinate1Lon = rs.getString("coord1lon"); //gets top left longitude (y)
String coordinate3Lat = rs.getString("coord3lat"); //gets bottom right latitude (x)
String coordinate3Lon = rs.getString("coord3lon"); //gets bottom right longitude (y)
char status = rs.getString("status").charAt(0); //get status as char
list.add(new ParkingSpot(uid, status, coordinate1Lat, coordinate1Lon, coordinate3Lat, coordinate3Lon)); //adds new ParkingSpot object to ArrayList
}
return list;
} catch (RuntimeException e) {
throw new NullPointerException("SOME OTHER RUNTIME EXCEPTION");
//Json parser can't find some variable or UID or NullPointerException
} catch (SQLException e) {
throw new NullPointerException("SQL EXCEPTION");
//if credentials for SQL are bad or something is wrong with the server
} catch (ClassNotFoundException e) {
throw new NullPointerException("CLASS NOT FOUND EXCEPTION");
//if cannot find SQL driver
}
}
@Override
protected void onPostExecute(List result) { //called after doInBackground completes
super.onPostExecute(list);
list = result; //sets public list to List initialized from database
Timer timer = new Timer("updateMapAvailTask");
TimerTask myTask = new TimerTask() { //creates new task to execute again after timer
@Override
public void run() {
try {
Object result = new LongOperation().execute().get(); //second network thread to download data and initialize map
//gets object so ENTIRE LongOperation thread completes
} catch (InterruptedException e) {
throw new NullPointerException("INTERRUPTED EXCEPTION");
} catch (ExecutionException e) {
throw new NullPointerException("EXECUTION EXCEPTION");
}
}
};
sMapFragment.getMapAsync(MainActivity.this);
timer.schedule(myTask, 2000); //schedules for server pull to happen every couple of seconds
}
}
}
content_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.main.main.MainActivity"
tools:showIn="@layout/app_bar_main">
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"></FrameLayout>
<FrameLayout
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">
<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
activity_main_drawer.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_map"
android:icon="@drawable/ic_map"
android:title="Map" />
<item
android:id="@+id/nav_history"
android:icon="@drawable/ic_search_history"
android:title="History" />
<item
android:id="@+id/nav_notifications"
android:icon="@drawable/ic_error"
android:title="Notifications" />
</group>
<item android:title="User">
<menu>
<item
android:id="@+id/nav_help"
android:icon="@drawable/ic_help"
android:title="Help" />
<item
android:id="@+id/nav_settings"
android:icon="@drawable/ic_settings"
android:title="Settings" />
</menu>
</item>
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.main.main">
<!--
The ACCESS_COARSE/FINE_LOCATION permissions are not required to use
Google Maps Android API v2, but you must specify either coarse or fine
location permissions for the 'MyLocation' functionality.
-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:name="android.support.multidex.MultiDexApplication">
<!--
The API key for Google Maps-based APIs is defined as a string resource.
(See the file "res/values/google_maps_api.xml").
Note that the API key is linked to the encryption key used to sign the APK.
You need a different API key for each encryption key, including the release key that is used to
sign the APK for publishing.
You can define the keys for the debug and release targets in src/debug/ and src/release/.
-->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_maps_key" />
<activity
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
You AsyncTask
actually is blocking the UI thread.
The AsyncTask.get()
method is a blocking call, you should just do AsyncTask.execute()
. In your case, replace new LongOperation().execute().get()
with just new LongOperation().execute()
.