跨多个活动的定位服务

Location services across multiple activities

UPDATE - 编辑代码以显示我的广播想法。使用该方法会导致 !!!失败的 BINDER 交易 !!! toast 不显示任何位置数据。

我想知道是否有一种优雅的方法可以将 google 融合位置服务与活动分开。我的警告 class 目前实现了位置服务,但我有另一个 activity 可以拍照。拍摄照片时,我想将位置与图像相关联。我目前的想法是只在蓝牙警告 class 中使用我的广播接收器来收听我的相机将发送的广播,然后将其与某个位置相关联。我不喜欢这个想法,因为它不能很好地分离功能,所以希望得到一些建议或模式。下面 classes 的代码。

相机CLASS

import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CameraOperations extends AppCompatActivity {
    public final static String ACTION_PICTURE_RECEIVED = "ACTION_PICTURE_RECEIVED";
    public final static String TRANSFER_DATA = "bitmap";
    ImageView mImageView;
    String photoPath;
    int REQUEST_IMAGE_CAPTURE = 1;
    File photoFile = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.camera_view);
        mImageView = (ImageView) findViewById(R.id.imageView);

        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
            Toast.makeText(CameraOperations.this, "No Camera", Toast.LENGTH_SHORT).show();
        } else {
            dispatchTakePictureIntent();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK){
            if(data == null){
                Toast.makeText(this, "Data is null", Toast.LENGTH_SHORT);
                Bitmap pictureMap = BitmapFactory.decodeFile(photoFile.getAbsolutePath());
                mImageView.setImageBitmap(pictureMap);
                broadcastUpdate(ACTION_PICTURE_RECEIVED, pictureMap);
            }else {
                Toast.makeText(this, "Error Occurred", Toast.LENGTH_SHORT).show();
            }
        }
    }

    private void broadcastUpdate(final String action, Bitmap picture) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        picture.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        byte[] bytes = stream.toByteArray();

        Intent intent = new Intent(action);
        intent.putExtra(TRANSFER_DATA, bytes);
        sendBroadcast(intent);

        intent = new Intent(CameraOperations.this, Warning.class);
        startActivity(intent);
        finish();
    }

    private File createImageFile() throws IOException {
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(imageFileName, ".jpg", storageDir);

        photoPath = "file:" + image.getAbsolutePath();
        return image;
    }

    private void dispatchTakePictureIntent() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        try{
            photoFile = createImageFile();
        }catch(IOException e){
            e.printStackTrace();
        }

        if(photoFile != null){
            Uri photoUri = Uri.fromFile(photoFile);
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
        }
        startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
    }
}

蓝牙警告CLASS

import android.Manifest;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;

public class Warning extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
    private final static String TAG = Warning.class.getSimpleName();
    private String mDeviceAddress;
    private HandleConnectionService mBluetoothLeService;
    private boolean quitService;
    private final int PLAY_SERVICES_REQUEST_TIME = 1000;
    private Location mLastLocation;
    private GoogleApiClient mGoogleClient;
    private boolean requestingLocationUpdates = false;
    private LocationRequest locationRequest;

    private int UPDATE_INTERVAL = 10000; // 10 seconds
    private int FASTEST_INTERVAL = 5000; // 5 seconds
    private int DISTANCEMOVED = 10; //In meters


    private final ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder service) {
            mBluetoothLeService = ((HandleConnectionService.LocalBinder) service).getService();
            if (!mBluetoothLeService.initialize()) {
                Log.e(TAG, "Unable to initialize Bluetooth");
                finish();
            }
            mBluetoothLeService.connect(mDeviceAddress);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mBluetoothLeService = null;
        }
    };

    private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();

            if (HandleConnectionService.ACTION_GATT_DISCONNECTED.equals(action)) {
                if (!quitService) {
                    mBluetoothLeService.connect(mDeviceAddress);
                    Log.w(TAG, "Attempting to reconnect");
                }
                Log.w(TAG, "Disconnected, activity closing");
            } else if (HandleConnectionService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
                getGattService(mBluetoothLeService.getSupportedGattService());
            } else if (HandleConnectionService.ACTION_DATA_AVAILABLE.equals(action)) {
                checkWarning(intent.getByteArrayExtra(HandleConnectionService.EXTRA_DATA));
            }else if(CameraOperations.ACTION_PICTURE_RECEIVED.equals(action)){
                byte[] bytes = intent.getByteArrayExtra("bitmap");
                Bitmap picture = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                findLocation();
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second);

        Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
        setSupportActionBar(myToolbar);

        if (checkGoogleServices()) {
            buildGoogleApiClient();
        }

        quitService = false;
        Intent intent = getIntent();
        mDeviceAddress = intent.getStringExtra(Device.EXTRA_DEVICE_ADDRESS);

        Intent gattServiceIntent = new Intent(this, HandleConnectionService.class);
        bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
    }

    @Override
    protected void onResume() {
        super.onResume();
        registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.mainmenu, menu);
        MenuItem connect = menu.findItem(R.id.connect);
        connect.setVisible(false);
        MenuItem disconnect = menu.findItem(R.id.disconnect);
        disconnect.setVisible(true);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Intent intent;
        switch (item.getItemId()) {
            case R.id.home:
                new AlertDialog.Builder(this)
                        .setIcon(android.R.drawable.ic_dialog_alert)
                        .setTitle("Return Home")
                        .setMessage("Returning home will disconnect you, continue?")
                        .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                quitService = true;
                                mBluetoothLeService.disconnect();
                                mBluetoothLeService.close();

                                Intent intent = new Intent(Warning.this, Main.class);
                                startActivity(intent);
                            }
                        })
                        .setNegativeButton("No", null)
                        .show();

                return true;
            case R.id.connect:
                Toast.makeText(this, "Connect Pressed", Toast.LENGTH_SHORT).show();
                return true;

            case R.id.disconnect:
                disconnectOperation();
                intent = new Intent(Warning.this, Main.class);
                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                startActivity(intent);
                return true;

            case R.id.profiles:
                Toast.makeText(this, "Profiles Pressed", Toast.LENGTH_SHORT).show();
                return true;


            case R.id.camera:
                Toast.makeText(this, "Camera Pressed", Toast.LENGTH_SHORT).show();
                intent = new Intent(Warning.this, CameraOperations.class);
                startActivity(intent);
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            quitService = true;
            mBluetoothLeService.disconnect();
            mBluetoothLeService.close();

            Intent intent = new Intent(Warning.this, Main.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(intent);
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBluetoothLeService.disconnect();
        mBluetoothLeService.close();

        System.exit(0);
    }

    public boolean disconnectOperation(){
        this.quitService = true;
        mBluetoothLeService.disconnect();
        mBluetoothLeService.close();
        return true;
    }

    private void checkWarning(byte[] byteArray) {
        if (byteArray != null) {
            for (int i = 0; i < byteArray.length; i++) {
                if (byteArray[i] == 48) {
                   findLocation();
                }
            }
        }
    }

    private void getGattService(BluetoothGattService gattService) {
        if (gattService == null) {
            return;
        }

        BluetoothGattCharacteristic characteristicRx = gattService.getCharacteristic(HandleConnectionService.UUID_BLE_SHIELD_RX);
        mBluetoothLeService.setCharacteristicNotification(characteristicRx, true);
        mBluetoothLeService.readCharacteristic(characteristicRx);
    }

    private static IntentFilter makeGattUpdateIntentFilter() {
        final IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(HandleConnectionService.ACTION_GATT_CONNECTED);
        intentFilter.addAction(HandleConnectionService.ACTION_GATT_DISCONNECTED);
        intentFilter.addAction(HandleConnectionService.ACTION_GATT_SERVICES_DISCOVERED);
        intentFilter.addAction(HandleConnectionService.ACTION_DATA_AVAILABLE);
        intentFilter.addAction(CameraOperations.ACTION_PICTURE_RECEIVED);

        return intentFilter;
    }

    private boolean checkGoogleServices() {
        GoogleApiAvailability googleAPI = GoogleApiAvailability.getInstance();
        int available = googleAPI.isGooglePlayServicesAvailable(this);
        if (available != ConnectionResult.SUCCESS) {
            if (googleAPI.isUserResolvableError(available)) {
                googleAPI.getErrorDialog(this, available, PLAY_SERVICES_REQUEST_TIME).show();
            }
            return false;
        }

        return true;
    }

    public void findLocation() {
        int REQUEST_CODE_ASK_PERMISSIONS = 123;
        double latitude = 0.0;
        double longitude = 0.0;

        if(Build.VERSION.SDK_INT >= 23){
            boolean fineLocationAccess = ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
            boolean courseLocationAccess = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
            boolean accessGranted = fineLocationAccess && courseLocationAccess;

            if (accessGranted) {
                mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleClient);
                if(mLastLocation != null) {
                    latitude = mLastLocation.getLatitude();
                    longitude = mLastLocation.getLongitude();

                    Toast.makeText(this, "latitude: " + latitude + " longitude: " + longitude, Toast.LENGTH_LONG).show();
                }else{
                    Log.i("Warning", "Unable to get Location");
                }
            }else{
                Log.i("Connection", "Request permission");
            }
        }else{
            mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleClient);
            if(mLastLocation != null) {
                latitude = mLastLocation.getLatitude();
                longitude = mLastLocation.getLongitude();

                Toast.makeText(this, "latitude: " + latitude + " longitude: " + longitude, Toast.LENGTH_LONG).show();
            }else{
                Log.i("Warning", "Unable to get Location");
            }
        }
    }

    public void buildGoogleApiClient() {
        mGoogleClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API).build();
    }

    @Override
    public void onConnected(Bundle bundle) {
        Log.w("Connected", " : " + mGoogleClient.isConnected());
    }

    @Override
    public void onConnectionSuspended(int i) {
        Toast.makeText(this, "Location Services Stopped", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Toast.makeText(this, "Location Services Connection Fail", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onStart() {
        super.onStart();

        if (mGoogleClient != null) {
            mGoogleClient.connect();
        }
    }
}

我认为你最初的想法实际上很合理,只要你以某种方式构建它。了解位置信息的 activity 可以独立于任何其他 activity 更新和存储该位置。这可能是理想的服务,它实际上每 x 时间运行一次,或者在位置更改时运行。

在同一个 class 中,您可以有一个通用的广播接收器来处理对该位置信息的请求。您可以根据需要使它成为通用的,允许其他 applications/activities 您也可以利用它,只要位置数据以每个请求者可用的方式返回。

在您拍照的 activity 中(最终是您想要的任何 activity),当您拍照时,您可以发送请求该位置信息的广播,一些默认或 null返回 none 时的信息。复杂性之一是知道在完成之前等待此位置信息的时间。在这种情况下,位置 activity 是 'generalized' 位置,因为如果您正确构造它,多个活动可以请求位置数据。

另一种可能有效的方法是使照片拍摄 activity 成为通用照片。一种方法是当您拍照时,您可以让您的照片 activity 将广播发送到包含照片 filepath/uri/identifying 信息的位置 activity,但您不必等待以获得回应。相反,您让位置 activity 监听此广播,并使用提供的识别信息将位置写入照片元数据以查找所述照片。通过这种方式,您的照片拍摄应用程序不会卡在等待位置 activity 完成(或事件开始,如果有的话)。

祝你好运,听起来是个有趣的项目。