1. 程式人生 > >定位與權限

定位與權限

系統版本 @override 單位 push developer toa 得到 設置 tin

就昨天,我遇到一個神奇的問題,就是android原生的GPS定位在我的魅族4上跑的順順利利,但是一到了紅米note4x或者最近的新android上就不能定位了,我把高德百度的定位接口都用過,但是就是報沒權限的錯誤,我檢查了一下AndroidManifest配置文件,ok都有,什麽高德的key,權限等等都有。

查了一下報錯,發現在android6.0版本後為了用戶安全,有幾個權限設置運行時權限,如果不加動態獲取權限的代碼,是不會提示的,沒有得到權限,當然無法定位。

正常的解析是:

對於6.0以下的權限及在安裝的時候,根據權限聲明產生一個權限列表,用戶只有在同意之後才能完成app的安裝,造成了我們想要使用某個app,就要默默忍受其一些不必要的權限(比如是個app都要訪問通訊錄、短信等)。而在6.0以後,我們可以直接安裝,當app需要我們授予不恰當的權限的時候,我們可以予以拒絕(比如:單機的象棋對戰,請求訪問任何權限,我都是不同意的)。當然你也可以在設置界面對每個app的權限進行查看,以及對單個權限進行授權或者解除授權。

新的權限機制更好的保護了用戶的隱私,Google將權限分為兩類,一類是Normal Permissions,這類權限一般不涉及用戶隱私,是不需要用戶進行授權的,比如手機震動、訪問網絡等;另一類是Dangerous Permission,一般是涉及到用戶隱私的,需要用戶進行授權,比如讀取sdcard、訪問通訊錄等。

需要動態獲取權限(Runtime Permissions)的主要有以下幾個:

動態獲取權限有:

參考大神:http://blog.csdn.net/lmj623565791/article/details/50709663

  

group:android.permission-group.CONTACTS
  permission:android.permission.WRITE_CONTACTS
  permission:android.permission.GET_ACCOUNTS
  permission:android.permission.READ_CONTACTS

group:android.permission
-group.PHONE permission:android.permission.READ_CALL_LOG permission:android.permission.READ_PHONE_STATE permission:android.permission.CALL_PHONE permission:android.permission.WRITE_CALL_LOG permission:android.permission.USE_SIP permission:android.permission.PROCESS_OUTGOING_CALLS permission:com.android.voicemail.permission.ADD_VOICEMAIL group:android.permission
-group.CALENDAR permission:android.permission.READ_CALENDAR permission:android.permission.WRITE_CALENDAR group:android.permission-group.CAMERA permission:android.permission.CAMERA group:android.permission-group.SENSORS permission:android.permission.BODY_SENSORS group:android.permission-group.LOCATION permission:android.permission.ACCESS_FINE_LOCATION permission:android.permission.ACCESS_COARSE_LOCATION group:android.permission-group.STORAGE permission:android.permission.READ_EXTERNAL_STORAGE permission:android.permission.WRITE_EXTERNAL_STORAGE group:android.permission-group.MICROPHONE permission:android.permission.RECORD_AUDIO group:android.permission-group.SMS permission:android.permission.READ_SMS permission:android.permission.RECEIVE_WAP_PUSH permission:android.permission.RECEIVE_MMS permission:android.permission.RECEIVE_SMS permission:android.permission.SEND_SMS permission:android.permission.READ_CELL_BROADCASTS

獲取動態權限步驟:

package com.tfot.hotel.yichengyiyu.Activity;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.widget.Toast;

import com.amap.api.location.AMapLocation;
import com.amap.api.location.AMapLocationClient;
import com.amap.api.location.AMapLocationClientOption;
import com.amap.api.location.AMapLocationListener;
import com.amap.api.services.core.LatLonPoint;
import com.amap.api.services.geocoder.GeocodeAddress;
import com.amap.api.services.geocoder.GeocodeResult;
import com.amap.api.services.geocoder.GeocodeSearch;
import com.amap.api.services.geocoder.RegeocodeQuery;
import com.amap.api.services.geocoder.RegeocodeResult;
import com.tfot.hotel.yichengyiyu.MainActivity;
import com.tfot.hotel.yichengyiyu.R;
import com.tfot.hotel.yichengyiyu.Util.base.BaseActivity;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import static java.lang.Math.atan2;
import static java.lang.Math.cos;
import static java.lang.Math.sin;
import static java.lang.Math.sqrt;

/**
 * Created by ACER on 2017/1/22.
 */

public class SplashActivity extends BaseActivity implements AMapLocationListener{
    private SharedPreferences sp;
    private SharedPreferences.Editor editor;
    //聲明mLocationOption對象
    public AMapLocationClientOption mLocationOption = null;
    public AMapLocationClient mlocationClient = null;
    double lat; //維度
    double lon ;//經度
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
         sp = getSharedPreferences("sp_demo", Context.MODE_PRIVATE);
         editor = sp.edit();


        //判斷是否為android6.0系統版本,如果是,需要動態添加權限
        if (Build.VERSION.SDK_INT>=23){
            showContacts();
        }else{
            new GaoDeDingWei().aMapLocationByNet();
        }




        new Handler().postDelayed(new Runnable() {
            public void run() {
                /* Create an Intent that will start the Main WordPress Activity. */
                Intent mainIntent = new Intent(SplashActivity.this, MainActivity.class);
                SplashActivity.this.startActivity(mainIntent);
                SplashActivity.this.finish();
            }
        }, 3000);
    }




    //6.0定位需要的權限申請
     public void showContacts(){
        if (ActivityCompat.checkSelfPermission(this, "android.permission.ACCESS_COARSE_LOCATION")
                != PackageManager.PERMISSION_GRANTED){
            Toast.makeText(getApplicationContext(),"沒有權限,請手動開啟定位權限", Toast.LENGTH_SHORT).show();
            // 申請一個(或多個)權限,並提供用於回調返回的獲取碼(用戶定義)
            ActivityCompat.requestPermissions(SplashActivity.this,new String[]{"android.permission.ACCESS_COARSE_LOCATION"}, 100);
        }else{
            System.out.println("定位開始!!!!!!!!!!!!!!!!");
            new GaoDeDingWei().aMapLocationByNet();
        }
    }





    //Android6.0申請權限的回調方法
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            // requestCode即所聲明的權限獲取碼,在checkSelfPermission時傳入
            case 100:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // 獲取到權限,作相應處理(調用定位SDK應當確保相關權限均被授權,否則可能引起定位失敗)
                    new GaoDeDingWei().aMapLocationByNet();
                } else {
                    // 沒有獲取到權限,做特殊處理
                    Toast.makeText(getApplicationContext(), "獲取位置權限失敗,請手動開啟", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }


    //高德定位
    class GaoDeDingWei {
        public GaoDeDingWei(){

        }
        public  void  aMapLocationByNet(){
            //獲取網絡管理對象
            ConnectivityManager connmanager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
            //獲取當前活躍的網絡
            NetworkInfo info = connmanager.getActiveNetworkInfo();
            if (info == null) {
//                Intent intent = new Intent();
//                intent.setAction(Settings.ACTION_AIRPLANE_MODE_SETTINGS);//跳轉打開網絡的手機界面
//                startActivity(intent);
                Toast.makeText(SplashActivity.this,"請打開網絡",Toast.LENGTH_SHORT).show();
            } else {
                //city_tv.setText("定位中,請稍等");
                aMapLocation();
            }
        }
        public  void aMapLocation(){
            mlocationClient = new AMapLocationClient(SplashActivity.this);
            //初始化定位參數
            mLocationOption = new AMapLocationClientOption();
            //設置定位監聽
            mlocationClient.setLocationListener(SplashActivity.this);
            //設置定位模式為高精度模式,Battery_Saving為低功耗模式,Device_Sensors是僅設備模式
            mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);
            //設置定位間隔,單位毫秒,默認為2000ms
            mLocationOption.setInterval(2000);
            //設置定位參數
            mlocationClient.setLocationOption(mLocationOption);
            // 此方法為每隔固定時間會發起一次定位請求,為了減少電量消耗或網絡流量消耗,
            // 註意設置合適的定位時間的間隔(最小間隔支持為1000ms),並且在合適時間調用stopLocation()方法來取消定位請求
            // 在定位結束後,在合適的生命周期調用onDestroy()方法
            // 在單次定位情況下,定位無論成功與否,都無需調用stopLocation()方法移除請求,定位sdk內部會移除
            //啟動定位
            mlocationClient.startLocation();
        }

    }



    //高德地圖數據監聽改變
    @Override
    public void onLocationChanged(AMapLocation amapLocation) {
        if (amapLocation != null) {
            if (amapLocation.getErrorCode() == 0) {
                //定位成功回調信息,設置相關消息
                // amapLocation.getLocationType();//獲取當前定位結果來源,如網絡定位結果,詳見定位類型表
                lat= amapLocation.getLatitude();//獲取緯度
                lon =amapLocation.getLongitude();//獲取經度
                //amapLocation.getAccuracy();//獲取精度信息
                    /*SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    Date date = new Date(amapLocation.getTime());
                    df.format(date);//定位時間*/
                //把經緯度轉換成城市
                if (this!=null){

                    GeocodeSearch geocoderSearch = new GeocodeSearch(this);
                    geocoderSearch.setOnGeocodeSearchListener(new GeocodeSearch.OnGeocodeSearchListener(){

                        @Override
                        public void onGeocodeSearched(GeocodeResult result, int rCode) {
                            // TODO Auto-generated method stub

                        }

                        @Override
                        public void onRegeocodeSearched(RegeocodeResult result, int rCode) {
                            //兩個附近點的位置
                            String city  = result.getRegeocodeAddress().getCity();
                            editor.putString("lat", lat+"");
                            editor.putString("lon", lon+"");
                            editor.putString("city_tv",city);
                            editor.commit();
                            System.out.println("###############當前位置城市###############-->"+city);
                        }});
                    LatLonPoint lp = new LatLonPoint(lat,lon);
                    RegeocodeQuery query = new RegeocodeQuery(lp, 200,GeocodeSearch.AMAP);
                     geocoderSearch.getFromLocationAsyn(query);


                }else {
                    //  return;
                }
            } else {
                //顯示錯誤信息ErrCode是錯誤碼,errInfo是錯誤信息,詳見錯誤碼表。
                Log.e("AmapError","location Error, ErrCode:"
                        + amapLocation.getErrorCode() + ", errInfo:"
                        + amapLocation.getErrorInfo());
            }
        }
    }
}

在fragment中獲取定位權限:

基本一樣

但是

Fragment中運行時權限的特殊處理
在Fragment中申請權限,不要使用ActivityCompat.requestPermissions, 直接使用Fragment的requestPermissions方法,否則會回調到Activity的 onRequestPermissionsResult
父子fragment中子fragment獲取權限:http://blog.csdn.net/qfanmingyiq/article/details/52561658

最後找到的hongyang幹貨:

MPermissions用法

對外的接口和PermissionGen基本一致,因為申請只需要三個參數,拋棄了使用原本類庫的單例的方式,直接一個幾個靜態方法,簡單整潔暴力。

貼一個用法:

public class MainActivity extends AppCompatActivity
{

    private Button mBtnSdcard;
    private static final int REQUECT_CODE_SDCARD = 2;

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

        mBtnSdcard = (Button) findViewById(R.id.id_btn_sdcard);
        mBtnSdcard.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                MPermissions.requestPermissions(MainActivity.this, REQUECT_CODE_SDCARD, Manifest.permission.WRITE_EXTERNAL_STORAGE);
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
    {
        MPermissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }


    @PermissionGrant(REQUECT_CODE_SDCARD)
    public void requestSdcardSuccess()
    {
        Toast.makeText(this, "GRANT ACCESS SDCARD!", Toast.LENGTH_SHORT).show();
    }

    @PermissionDenied(REQUECT_CODE_SDCARD)
    public void requestSdcardFailed()
    {
        Toast.makeText(this, "DENY ACCESS SDCARD!", Toast.LENGTH_SHORT).show();
    }
}

是不是簡單明了~~對於onRequestPermissionsResult所有的Activity都是一致的,所以可以放到BaseActivity中去。此外,在Fragment中使用的方式一致,詳見demo。

詳見庫:https://github.com/hongyangAndroid/MPermissions.

至於為什麽不直接介紹MPermission的源碼,因為主要涉及到Annotation Processor,所以以這種方式引出,後面考慮單篇博文介紹下我目前所會的編譯時註解的相關做法以及API的使用。

定位與權限