android - 跨多个 Activity 的定位服务

标签 android design-patterns fusedlocationproviderapi

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

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

相机类

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);
    }
}

蓝牙警告等级

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 时间运行一次,或者在位置发生变化时运行。

在同一个类中,您可以有一个通用的广播接收器来处理对该位置信息的请求。只要位置数据以每个请求者可用的方式返回,就允许您创建的其他应用程序/Activity 也可以利用它而言,您可以根据需要将其设为通用。

在您拍摄照片的 Activity (以及最终您想要的任何 Activity )中,当您拍照时,您可以发送请求该位置信息的广播,如果未返回任何信息,则使用一些默认信息或空信息。复杂性之一是知道在完成之前等待此位置信息的时间。在这种情况下,位置 Activity 是“通用的” Activity ,因为如果您正确构造它,多个 Activity 可以请求位置数据。

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

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

关于android - 跨多个 Activity 的定位服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38084053/

相关文章:

java - 广播接收器在创建类时注册从应用程序扩展

android - Android-卫星信息(计数,信号等)的融合位置提供程序API问题

Android FusedLocationProviderClient 突然不再使用 Location 调用回调

android - FusedLocationApi.requestLocationUpdates() 在一段时间后停止间隔请求

android - 无法在工作线程上同步获取 firebase id token

android - 应用程序在底部留出空白

java - 具有 Java 客户端和 Python 服务器的数据报套接字

c++ - 如何使用C++中的装饰器模式在一行代码中装饰对象?

c# - View 模型的 MVVM 继承

c++ - 神级的替代品