Android Geofences (Location Services) - 如何触发应用内事件
Posted
技术标签:
【中文标题】Android Geofences (Location Services) - 如何触发应用内事件【英文标题】:Android Geofences (Location Services) - How to trigger in-app events 【发布时间】:2016-08-01 17:09:52 【问题描述】:我一直在使用带有地理围栏的 Google 位置服务 API。我可以成功捕获地理围栏转换,并且在进入和退出地理围栏时会收到通知。但是,我不知道如何使地理围栏转换对应用程序进行 GUI 和数据更改。
例如,如果应用程序已打开,我希望出现一个小吐司。我还需要我的应用生成新的地理围栏并丢弃旧的(但到目前为止我只使用一组硬编码的地理围栏)。
但到目前为止,我不明白如何从 GeofenceTransitionsEventService.java(我“捕捉”意图并创建通知的地方)与我的应用程序交互。
以下是我的代码(现已编辑,但仍无法正常工作):
GPS 活动
public class GPSActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener, ResultCallback<Status>
private GoogleApiClient mGoogleApiClient;
private LocationRequest mLocationRequest;
private String mLastUpdateTime;
private TextView mLatitudeTextView;
private TextView mLongitudeTextView;
private final int MY_PERMISSION_ACCESS_FINE_LOCATION = 1;
private boolean locationPermissionGoodToGo = false;
public double latitude, longitude = 50;
protected static final String TAG = "MainActivity";
/**
* The list of geofences used in this sample.
*/
protected ArrayList<Geofence> mGeofenceList;
/**
* Used to keep track of whether geofences were added.
*/
private boolean mGeofencesAdded;
/**
* Used when requesting to add or remove geofences.
*/
private PendingIntent mGeofencePendingIntent;
/**
* Used to persist application state about whether geofences were added.
*/
private SharedPreferences mSharedPreferences;
// Buttons for kicking off the process of adding or removing geofences.
private Button mAddGeofencesButton;
private Button mRemoveGeofencesButton;
protected ResultReceiver mResultReceiver;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gps);
mLatitudeTextView = (TextView) findViewById((R.id.latitude_textview));
mLongitudeTextView = (TextView) findViewById((R.id.longitude_textview));
// Get the UI widgets.
mAddGeofencesButton = (Button) findViewById(R.id.add_geofences_button);
mRemoveGeofencesButton = (Button) findViewById(R.id.remove_geofences_button);
// Empty list for storing geofences.
mGeofenceList = new ArrayList<Geofence>();
// Initially set the PendingIntent used in addGeofences() and removeGeofences() to null.
mGeofencePendingIntent = null;
// Retrieve an instance of the SharedPreferences object.
mSharedPreferences = getSharedPreferences(Constants.SHARED_PREFERENCES_NAME,
MODE_PRIVATE);
// Get the value of mGeofencesAdded from SharedPreferences. Set to false as a default.
mGeofencesAdded = mSharedPreferences.getBoolean(Constants.GEOFENCES_ADDED_KEY, false);
setButtonsEnabledState();
// Get the geofences used. Geofence data is hard coded in this sample.
populateGeofenceList();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
initReceiver();
public void initReceiver()
mResultReceiver = new AddressResultReceiver(new Handler())
@Override
protected void onReceiveResult(int resultCode, Bundle resultData)
// Check result code and/or resultData and take necessary action
if(resultCode == 0)
makeToast(resultData.getString("FROM_GEOFENCE_KEY"));
mRemoveGeofencesButton.setText("Remove TESTT");
;
// if(mResultReceiver != null)
// mAddGeofencesButton.setText("Add Geofences NOT NULL TEST");
//
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
intent.putExtra(Constants.RECEIVER, mResultReceiver);
public void makeToast(String string)
Toast.makeText(this, string, Toast.LENGTH_LONG).show();
@Override
protected void onStart()
super.onStart();
mGoogleApiClient.connect();
@Override
protected void onStop()
super.onStop();
if (mGoogleApiClient.isConnected())
mGoogleApiClient.disconnect();
@Override
public void onConnected(Bundle bundle)
mLocationRequest = LocationRequest.create();
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setInterval(5000);
mLocationRequest.setFastestInterval(3000);
if ( ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED )
ActivityCompat.requestPermissions(this, new String[]android.Manifest.permission.ACCESS_FINE_LOCATION, MY_PERMISSION_ACCESS_FINE_LOCATION);
// setCoordinates();
else
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
@Override
public void onConnectionSuspended(int i)
Log.i(TAG, "Connection Suspended");
mGoogleApiClient.connect();
@Override
public void onLocationChanged(Location location)
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
mLatitudeTextView.setText(String.valueOf(location.getLatitude()));
mLongitudeTextView.setText(String.valueOf(location.getLongitude()));
latitude = location.getLatitude();
longitude = location.getLongitude();
// Toast.makeText(this, "Updated: " + mLastUpdateTime, Toast.LENGTH_SHORT).show();
@Override
public void onConnectionFailed(ConnectionResult connectionResult)
Log.i(TAG, "Connection failed. Error: " + connectionResult.getErrorCode());
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults)
switch (requestCode)
case MY_PERMISSION_ACCESS_FINE_LOCATION:
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
// permission was granted, yay! Do the
// contacts-related task you need to do.
locationPermissionGoodToGo = true;
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
//setCoordinates();
else
// permission denied, boo! Disable the
// functionality that depends on this permission.
locationPermissionGoodToGo = false;
return;
// other 'case' lines to check for other
// permissions this app might request
public void startGame()
//mGeofenceList.add(new Geofence.Builder()
Geofence fence = new Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this geofence.
.setRequestId("hej")
.setCircularRegion(latitude, longitude, 150) //radius in meters
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT)
.build();//;
/**
* Builds and returns a GeofencingRequest. Specifies the list of geofences to be monitored.
* Also specifies how the geofence notifications are initially triggered.
*/
private GeofencingRequest getGeofencingRequest()
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
// The INITIAL_TRIGGER_ENTER flag indicates that geofencing service should trigger a
// GEOFENCE_TRANSITION_ENTER notification when the geofence is added and if the device
// is already inside that geofence.
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
// Add the geofences to be monitored by geofencing service.
builder.addGeofences(mGeofenceList);
// Return a GeofencingRequest.
return builder.build();
/**
* Adds geofences, which sets alerts to be notified when the device enters or exits one of the
* specified geofences. Handles the success or failure results returned by addGeofences().
*/
public void addGeofencesButtonHandler(View view)
if (!mGoogleApiClient.isConnected())
Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show();
return;
try
LocationServices.GeofencingApi.addGeofences(
mGoogleApiClient,
// The GeofenceRequest object.
getGeofencingRequest(),
// A pending intent that that is reused when calling removeGeofences(). This
// pending intent is used to generate an intent when a matched geofence
// transition is observed.
getGeofencePendingIntent()
).setResultCallback(this); // Result processed in onResult().
catch (SecurityException securityException)
// Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
logSecurityException(securityException);
/**
* Removes geofences, which stops further notifications when the device enters or exits
* previously registered geofences.
*/
public void removeGeofencesButtonHandler(View view)
if (!mGoogleApiClient.isConnected())
Toast.makeText(this, getString(R.string.not_connected), Toast.LENGTH_SHORT).show();
return;
try
// Remove geofences.
LocationServices.GeofencingApi.removeGeofences(
mGoogleApiClient,
// This is the same pending intent that was used in addGeofences().
getGeofencePendingIntent()
).setResultCallback(this); // Result processed in onResult().
catch (SecurityException securityException)
// Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
logSecurityException(securityException);
private void logSecurityException(SecurityException securityException)
Log.e(TAG, "Invalid location permission. " +
"You need to use ACCESS_FINE_LOCATION with geofences", securityException);
/**
* Runs when the result of calling addGeofences() and removeGeofences() becomes available.
* Either method can complete successfully or with an error.
*
* Since this activity implements the @link ResultCallback interface, we are required to
* define this method.
*
* @param status The Status returned through a PendingIntent when addGeofences() or
* removeGeofences() get called.
*/
public void onResult(Status status)
if (status.isSuccess())
// Update state and save in shared preferences.
mGeofencesAdded = !mGeofencesAdded;
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(Constants.GEOFENCES_ADDED_KEY, mGeofencesAdded);
editor.apply();
// Update the UI. Adding geofences enables the Remove Geofences button, and removing
// geofences enables the Add Geofences button.
setButtonsEnabledState();
Toast.makeText(
this,
getString(mGeofencesAdded ? R.string.geofences_added :
R.string.geofences_removed),
Toast.LENGTH_SHORT
).show();
else
// Get the status code for the error and log it using a user-friendly message.
String errorMessage = GeofenceErrorMessages.getErrorString(this,
status.getStatusCode());
Log.e(TAG, errorMessage);
/**
* Gets a PendingIntent to send with the request to add or remove Geofences. Location Services
* issues the Intent inside this PendingIntent whenever a geofence transition occurs for the
* current list of geofences.
*
* @return A PendingIntent for the IntentService that handles geofence transitions.
*/
private PendingIntent getGeofencePendingIntent()
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null)
return mGeofencePendingIntent;
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
//DESTROYS EVERYTHING---> intent.putExtra("RECEIVER_KEY", mResultReceiver);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences().
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
/**
* This sample hard codes geofence data. A real app might dynamically create geofences based on
* the user's location.
*/
public void populateGeofenceList()
for (Map.Entry<String, LatLng> entry : Constants.BAY_AREA_LANDMARKS.entrySet())
mGeofenceList.add(new Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId(entry.getKey())
// Set the circular region of this geofence.
.setCircularRegion(
entry.getValue().latitude,
entry.getValue().longitude,
Constants.GEOFENCE_RADIUS_IN_METERS
)
// Set the expiration duration of the geofence. This geofence gets automatically
// removed after this period of time.
.setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)
// Set the transition types of interest. Alerts are only generated for these
// transition. We track entry and exit transitions in this sample.
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT)
// Create the geofence.
.build());
/**
* Ensures that only one button is enabled at any time. The Add Geofences button is enabled
* if the user hasn't yet added geofences. The Remove Geofences button is enabled if the
* user has added geofences.
*/
private void setButtonsEnabledState()
if (mGeofencesAdded)
mAddGeofencesButton.setEnabled(false);
mRemoveGeofencesButton.setEnabled(true);
else
mAddGeofencesButton.setEnabled(true);
mRemoveGeofencesButton.setEnabled(false);
GeofenceTransisitionsIntentService
public class GeofenceTransitionsIntentService extends IntentService
protected static final String TAG = "GeofenceTransitionsIS";
private ResultReceiver mResultReceiver;
/**
* This constructor is required, and calls the super IntentService(String)
* constructor with the name for a worker thread.
*/
public GeofenceTransitionsIntentService()
// Use the TAG to name the worker thread.
super(TAG);
@Override
public void onCreate()
super.onCreate();
//Intent i = getIntent();
/**
* Handles incoming intents.
* @param intent sent by Location Services. This Intent is provided to Location
* Services (inside a PendingIntent) when addGeofences() is called.
*/
@Override
protected void onHandleIntent(Intent intent)
mResultReceiver = intent.getParcelableExtra(Constants.RECEIVER);
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError())
String errorMessage = GeofenceErrorMessages.getErrorString(this,
geofencingEvent.getErrorCode());
Log.e(TAG, errorMessage);
return;
// Get the transition type.
int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT)
// Get the geofences that were triggered. A single event can trigger multiple geofences.
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Get the transition details as a String.
String geofenceTransitionDetails = getGeofenceTransitionDetails(
this,
geofenceTransition,
triggeringGeofences
);
// Send notification and log the transition details.
sendNotification(geofenceTransitionDetails);
sendToActivity("Sending to activity test");
//Toast.makeText(this, geofenceTransitionDetails, Toast.LENGTH_SHORT).show();
Log.i(TAG, geofenceTransitionDetails);
else
// Log the error.
Log.e(TAG, "R.string.geofence_transition_invalid_type");
/**
* Gets transition details and returns them as a formatted string.
*
* @param context The app context.
* @param geofenceTransition The ID of the geofence transition.
* @param triggeringGeofences The geofence(s) triggered.
* @return The transition details formatted as String.
*/
private String getGeofenceTransitionDetails(
Context context,
int geofenceTransition,
List<Geofence> triggeringGeofences)
String geofenceTransitionString = getTransitionString(geofenceTransition);
// Get the Ids of each geofence that was triggered.
ArrayList triggeringGeofencesIdsList = new ArrayList();
for (Geofence geofence : triggeringGeofences)
triggeringGeofencesIdsList.add(geofence.getRequestId());
String triggeringGeofencesIdsString = TextUtils.join(", ", triggeringGeofencesIdsList);
return geofenceTransitionString + ": " + triggeringGeofencesIdsString;
/**
* Posts a notification in the notification bar when a transition is detected.
* If the user clicks the notification, control goes to the MainActivity.
*/
private void sendNotification(String notificationDetails)
// Create an explicit content Intent that starts the main Activity.
Intent notificationIntent = new Intent(getApplicationContext(), GPSActivity.class);
// Construct a task stack.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Add the main Activity to the task stack as the parent.
stackBuilder.addParentStack(GPSActivity.class);
// Push the content Intent onto the stack.
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack.
PendingIntent notificationPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// Define the notification settings.
builder.setSmallIcon(R.drawable.ic_launcher)
// In a real app, you may want to use a library like Volley
// to decode the Bitmap.
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher))
.setColor(Color.RED)
.setContentTitle(notificationDetails)
.setContentText(getString(R.string.geofence_transition_notification_text))
.setContentIntent(notificationPendingIntent);
// Dismiss notification once the user touches it.
builder.setAutoCancel(true);
// Get an instance of the Notification manager
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
/**
* Maps geofence transition types to their human-readable equivalents.
*
* @param transitionType A transition type constant defined in Geofence
* @return A String indicating the type of transition
*/
private String getTransitionString(int transitionType)
switch (transitionType)
case Geofence.GEOFENCE_TRANSITION_ENTER:
return getString(R.string.geofence_transition_entered);
case Geofence.GEOFENCE_TRANSITION_EXIT:
return getString(R.string.geofence_transition_exited);
default:
return getString(R.string.unknown_geofence_transition);
public void sendToActivity(String string)
Bundle b=new Bundle();
b.putString("FROM_GEOFENCE_KEY", string);
mResultReceiver.send(0, b);
这是我当前导致应用崩溃的错误消息
04-11 17:21:21.952 9528-9528/blueinteraction.mamn01blueinteraction W/System: ClassLoader referenced unknown path: /data/app/blueinteraction.mamn01blueinteraction-1/lib/arm
04-11 17:21:21.974 9528-9528/blueinteraction.mamn01blueinteraction I/GMPM: App measurement is starting up, version: 8487
04-11 17:21:21.974 9528-9528/blueinteraction.mamn01blueinteraction I/GMPM: To enable debug logging run: adb shell setprop log.tag.GMPM VERBOSE
04-11 17:21:21.983 9528-9528/blueinteraction.mamn01blueinteraction E/GMPM: GoogleService failed to initialize, status: 10, Missing an expected resource: 'R.string.google_app_id' for initializing Google services. Possible causes are missing google-services.json or com.google.gms.google-services gradle plugin.
04-11 17:21:21.984 9528-9528/blueinteraction.mamn01blueinteraction E/GMPM: Scheduler not set. Not logging error/warn.
04-11 17:21:22.008 9528-9528/blueinteraction.mamn01blueinteraction I/InstantRun: Starting server socket listening for package blueinteraction.mamn01blueinteraction on android.net.LocalSocketAddress@e9aec24
04-11 17:21:22.010 9528-9528/blueinteraction.mamn01blueinteraction I/InstantRun: Started server for package blueinteraction.mamn01blueinteraction
04-11 17:21:22.012 9528-9567/blueinteraction.mamn01blueinteraction E/GMPM: Uploading is not possible. App measurement disabled
04-11 17:21:22.138 9528-9572/blueinteraction.mamn01blueinteraction D/OpenGLRenderer: Use EGL_SWAP_BEHAVIOR_PRESERVED: true
04-11 17:21:22.196 9528-9572/blueinteraction.mamn01blueinteraction I/Adreno-EGL: <qeglDrvAPI_eglInitialize:379>: QUALCOMM Build: 10/21/15, 369a2ea, I96aee987eb
04-11 17:21:22.199 9528-9572/blueinteraction.mamn01blueinteraction I/OpenGLRenderer: Initialized EGL, version 1.4
04-11 17:21:23.868 9528-9572/blueinteraction.mamn01blueinteraction D/OpenGLRenderer: endAllStagingAnimators on 0xb40e3280 (RippleDrawable) with handle 0x9f7bf6f0
04-11 17:21:29.767 9528-9572/blueinteraction.mamn01blueinteraction V/RenderScript: 0xaeca2000 Launching thread(s), CPUs 4
04-11 17:21:29.793 9528-9769/blueinteraction.mamn01blueinteraction E/AndroidRuntime: FATAL EXCEPTION: IntentService[GeofenceTransitionsIS]
Process: blueinteraction.mamn01blueinteraction, PID: 9528
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.os.ResultReceiver.send(int, android.os.Bundle)' on a null object reference
at blueinteraction.mamn01blueinteraction.GeofenceTransitionsIntentService.sendToActivity(GeofenceTransitionsIntentService.java:187)
at blueinteraction.mamn01blueinteraction.GeofenceTransitionsIntentService.onHandleIntent(GeofenceTransitionsIntentService.java:87)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:66)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.os.HandlerThread.run(HandlerThread.java:61)
04-11 17:21:29.898 9528-9572/blueinteraction.mamn01blueinteraction D/OpenGLRenderer: endAllStagingAnimators on 0x9eee6280 (RippleDrawable) with handle 0x9f7bf670
我认为这些是我需要在这里向您展示的唯一课程,以便您能够提供帮助。否则告诉我还包括什么。
【问题讨论】:
您需要在Activity
或Fragment
中使用ResultReceiver
@Jabbar_Jigariyo 感谢您的回复!你能详细说明一下这到底是什么意思吗?我已经有 GeofencesTransitionsIntentService 成功检测到地理围栏转换(并且这个代码和类的设置基本上只是取自谷歌自己的教程和示例代码。)
【参考方案1】:
您可以使用ResultReceiver
将数据发送回Activity
或Fragment
https://developer.android.com/reference/android/os/ResultReceiver.html
它的send
方法将允许添加resultCode
和Bundle
以将数据发送回实现接收器的类。
从您的Activity
或Fragment
,您会将receiver
的实例传递给IntentService
。比如,
Intent intent = new Intent(context, GeofenceTransitionsIntentService.class);
intent.putExtra(Constants.RECEIVER, receiver);
activity.startService(intent);
现在在您的 IntentService
类中,您将通过 bungle 参数获取接收者并在类中保留一个实例。
在您的IntentService
的onHandleIntent
方法中,可能在您想要触发事件的任何其他地方,您将使用接收器将数据发送回调用类。像这样的,
protected void onHandleIntent(Intent intent)
ResultReceiver resultReceiver = intent.getParcelableExtra(Constants.RECEIVER);
ResultReceiver
是用于接收来自某人的回调结果的通用接口。通过创建一个子类并实现 onReceiveResult(int, Bundle) 来使用它,然后您可以将其传递给其他人并通过 IPC 发送,并接收他们通过 send(int, Bundle) 提供的结果。
因此扩展该类并像这样在您的应用中创建一个新类,
public abstract class AddressResultReceiver extends ResultReceiver
public AddressResultReceiver(Handler handler)
super(handler);
那么AddressResultReceiver
在您的Activity
中可能看起来像这样,
protected AddressResultReceiver mResultReceiver = new AddressResultReceiver(new Handler())
@Override
protected void onReceiveResult(int resultCode, Bundle resultData)
// Check result code and/or resultData and take necessary action
;
我希望这会有所帮助。
【讨论】:
谢谢,我会试试这个。看来我必须使我的对象可序列化或可打包才能发送它。一个快速的问题:我应该在我的 GeofenceTransitionsIntentService 类中的哪个位置接收接收器?在 onHandleIntent 方法内部? 实际上它已经实现了Parcelable
。我在答案中添加了更多代码和解释
@AdrianHansson 是的。您可以在onHandleIntent
方法中接收接收者。我也在答案中添加了代码。看看吧!
到目前为止,我只收到此错误消息:“java.lang.NullPointerException: Attempt to invoke virtual method 'void android.os.ResultReceiver.send(int, android.os.Bundle)'在一个空对象上“在我将其作为包裹发送之前,该对象不为空,我检查了。有什么问题?我应该将上面的代码更新为最新的吗?
是的。请更新代码。不看就很难说出任何事情以上是关于Android Geofences (Location Services) - 如何触发应用内事件的主要内容,如果未能解决你的问题,请参考以下文章
地理围栏(Android 示例应用程序)java.lang.ClassNotFoundException MainActivity
Eclipse 报错The method xxx of type must override a superclass methodDescription Resource Path Locati