Android9.0 Camera2 横屏问题修改记录
vendor\mediatek\proprietary\packages\apps 目录下有三份相机源码 分别是
Camera、 Camera1、 Camera2
通过查看 mk 发现通过 ifeq ($(MTK_CAMERA_APP_VERSION), 3) 来控制编译哪一个,
MTK_CAMERA_APP_VERSION 宏定义在 device/mediateksample/xxxxxx/ProjectConfig.mk
整体界面相关
Camera2 中适配了两套 api, 老版本的 Camera 和新版本的 Camera2, 通过 CameraApiHelper 配置
Camera2\common\src\com\mediatek\camera\common\mode\CameraApiHelper.java
public static CameraApi getCameraApiType(@Nullable String modeName) {
        return CameraApi.API2;
}
 public enum CameraApi {
        /** Use the {@link android.hardware.Camera} class. */
        API1,
        /** Use the {@link android.hardware.camera2} package. */
        API2
}
预览布局不延伸到 navigation 中,不显示 statusbar
增加 requestWindowFeature(Window.FEATURE_NO_TITLE)
Camera2\host\src\com\mediatek\camera\QuickActivity.java
@Override
    protected final void onCreate(Bundle bundle) {
        LogHelper.i(TAG, "onCreate()");
        IPerformanceProfile profile = PerformanceTracker.create(TAG, "onCreate").start();
        mStartupOnCreate = true;
        super.onCreate(bundle);
        //cczheng add for don't show statusbar
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        mMainHandler = new Handler(getMainLooper());
        onPermissionCreateTasks(bundle);
        profile.stop();
    }
注释 setSystemUiVisibility(View.SYSTEM_UI_LAYOUT_FLAGS | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
Camera2\host\src\com\mediatek\camera\CameraActivity.java
@Override
    protected void onCreateTasks(Bundle savedInstanceState) {
        if (!isThirdPartyIntent(this) && !isOpenFront(this)) {
            CameraUtil.launchCamera(this);
        }
        IPerformanceProfile profile = PerformanceTracker.create(TAG, "onCreate").start();
        super.onCreateTasks(savedInstanceState);
        //cczheng annotation for layout forbbiden into navigationbar area
        /*getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_LAYOUT_FLAGS
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);*/
        setContentView(R.layout.activity_main);
        mOrientationListener = new OrientationEventListenerImpl(this);
        //create common ui module.
        mCameraAppUI = new CameraAppUI(this);
        profile.mark("CameraAppUI initialized.");
        mCameraAppUI.onCreate();
        profile.mark("CameraAppUI.onCreate done.");
        mIModeListener = new ModeManager();
        mIModeListener.create(this);
        profile.mark("ModeManager.create done.");
        profile.stop();
    }
旋转界面圆形图标 90 度, 闪光灯、HDR、拍照模式等
canvas.rotate(90)
Camera2\common\src\com\mediatek\camera\common\widget\RotateImageView.java
 @Override
    protected void onDraw(Canvas canvas) {
        Drawable drawable = getDrawable();
        if (drawable == null) {
            return;
        }
        Rect bounds = drawable.getBounds();
        int w = bounds.right - bounds.left;
        int h = bounds.bottom - bounds.top;
		....
		 // canvas.rotate(-mCurrentDegree);
        canvas.rotate(90);//cczheng change 90 for rotate all imageView
        canvas.translate(-w / 2, -h / 2);
        if (mDrawableBitmap != null) {
            canvas.drawBitmap(mDrawableBitmap, 0, 0, null);
        } else {
            drawable.draw(canvas);
        }
        canvas.restoreToCount(saveCount);
    }
拍照相关
预览旋转 90
horizontalMirrorData() 和 changePreviewDisplayOrientation() 都是从网上找的简单矩阵算法,验证了还真的能达到效果
镜像的问题底层驱动修改了,app 就不用处理了
预览旋转角度,根据实际情况我注释了 postScale(),这样导致了横屏被拉伸了,人脸变胖了,只需要单纯的 postRotate(90) 即可
Camera2\host\src\com\mediatek\camera\ui\preview\TextureViewController.java
//用于水平翻转镜像
private void horizontalMirrorData(){
        LogHelper.d(TAG, "updatePreviewSize horizontalMirrorData()");
        Matrix matrix = mTextureView.getTransform(new Matrix());
        matrix.setScale(-1, 1);
        int width = mTextureView.getWidth();
        matrix.postTranslate(width, 0);
        mTextureView.setTransform(matrix);
}
//用于旋转预览角度
private void changePreviewDisplayOrientation() {
        int mTextureViewWidth = mTextureView.getWidth();
        int mTextureViewHeight = mTextureView.getHeight();
        int rotation = mApp.getActivity().getWindowManager().getDefaultDisplay().getRotation();
        LogHelper.d(TAG,"rotation="+rotation);
        LogHelper.e(TAG,"mPreviewWidth="+mPreviewWidth+" mPreviewHeight="+mPreviewHeight);
        LogHelper.e(TAG,"textureWidth="+mTextureViewWidth+" textureHeight="+mTextureViewHeight);
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0, 0, mTextureViewWidth, mTextureViewHeight);
        RectF bufferRect = new RectF(0, 0, mPreviewHeight, mPreviewWidth);
        float centerX = viewRect.centerX();
        float centerY = viewRect.centerY();
        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            LogHelper.e(TAG,"Surface.ROTATION_90 ROTATION_270");
            /*bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
            float scale = Math.max((float) mTextureViewHeight / mPreviewHeight,
                                    (float) mTextureViewWidth / mPreviewWidth);
            LogHelper.d(TAG,"scale="+scale);
            matrix.postScale(scale, scale, centerX, centerY);*/
            matrix.postRotate((90 * (rotation - 2)) % 360, centerX, centerY);
        } else if (Surface.ROTATION_180 == rotation) {
            LogHelper.d(TAG,"Surface.ROTATION_180 =");
            matrix.postRotate(180, centerX, centerY);
        }
        mTextureView.setTransform(matrix);
}
private class SurfaceChangeCallback implements TextureView.SurfaceTextureListener {
        private ISurfaceStatusListener mListener;
        .....
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            //cczheng add for mirror preview data
            //horizontalMirrorData();
            changePreviewDisplayOrientation();
            mIsSurfaceCreated = true;
            surface.setDefaultBufferSize(mPreviewWidth, mPreviewHeight);
            if (mListener != null) {
                mListener.surfaceChanged(surface, mPreviewWidth, mPreviewHeight);
            }
            LogHelper.d(TAG, "onSurfaceTextureAvailable surface  = " + surface +
             " width " + width + " height " + height);
        }
		.....
}
人脸框位置相关
因为旋转了屏幕方向,人脸框的坐标位置就不对了,需要调整为正确的
通过分析打印日志发现
CamAp_FaceViewCtrl: [updateFacesViewByFace] new face num = 1, clear hide msg, show view right now
CamAp_FaceViewCtrl: [updateFacesViewByFace] new face num = 1, clear hide msg, send hide msg delay 1500 ms
和人脸框相关的类有以下几个
Camera2\feature\setting\facedetection\src\com\mediatek\camera\feature\setting\facedetection\FaceViewCtrl.java
Camera2\feature\setting\facedetection\src\com\mediatek\camera\feature\setting\facedetection\FaceView.java
Camera2\common\src\com\mediatek\camera\common\utils\CoordinatesTransform.java
FaceViewCtrl 控制显示隐藏, FaceView 绘制人脸框(其实是 ic_face_detection_focusing.9.png 图片),CoordinatesTransform 转换人脸坐标
看到上面打印的日志,人脸框显示 1.5 s 后会自动隐藏,这应该是 MTK 当时遗留的一个 bug
为了让人脸框一直显示,注释 updateFacesViewByFace() 中的 MSG_FACE_VIEW_HIDE 消息发送
private void updateFacesViewByFace(Face[] faces) {
        if (!mIsEnable) {
            LogHelper.e(TAG, "[updateFacesViewByFace] mIsEnable is false, ignore this time");
            return;
        }
        if (faces != null && faces.length > 0
                && mFaceViewState == FaceViewState.STATE_INIT) {
            // Check if face view has really been shown, if not , not hide view this time.
            // Why to do this check?
            // Maybe higher priority view is shown when face view wants to show, after higher
            // priority view is not shown, maybe face num is not changed too, it's time to hide
            // face view. So face view has no chance to show out.
            if (mHideViewWhenFaceCountNotChange && faces.length == mFaceNum
                    && mFaceView.hasReallyShown()) {
                // if face view is hide now, not send message, only update wait state
                if (mFaceView.getVisibility() != View.VISIBLE) {
                    mMainHandler.removeMessages(MSG_FACE_VIEW_HIDE);
                    mWaitFocusState = WaitFocusState.WAIT_NOTHING;
                } else if (!mMainHandler.hasMessages(MSG_FACE_VIEW_HIDE)) {
                    // if there is not hide msg in queue, send delay message to hide
                    //cczheng annotation don't auto hide faceview 1.5s
                    /*mMainHandler.removeMessages(MSG_FACE_VIEW_HIDE);
                    LogHelper.e(TAG, "[updateFacesViewByFace] new face num = " + faces.length +
                            ", clear hide msg, send hide msg delay "
                            + HIDE_VIEW_TIMEOUT_WAIT_AF_SCAN + " ms");
                    mMainHandler.sendEmptyMessageDelayed(MSG_FACE_VIEW_HIDE,
                            HIDE_VIEW_TIMEOUT_WAIT_AF_SCAN);*/
                }
            } else {
                LogHelper.e(TAG, "[updateFacesViewByFace] new face num = " + faces.length +
                        ", clear hide msg, show view right now");
                mMainHandler.removeMessages(MSG_FACE_VIEW_HIDE);
                mWaitFocusState = WaitFocusState.WAIT_PASSIVE_SCAN;
                showView();
                mFaceView.resetReallyShown();
            }
            mFaceView.setFaces(faces);
            mFaceNum = faces.length;
        }
    }
FaceView 中的 onDraw() 通过遍历人脸集合,绘制人脸框,mFaceIndicator 就是上面说的 .9 图片,来看下坐标的计算方法
@Override
    protected void onDraw(Canvas canvas) {
        LogHelper.i(TAG, "[FaceView onDraw]");
        mReallyShown = true;
        if (mFaces != null && mFaces.length > 0) {
            for (int i = 0; i < mFaces.length; i++) {
                Rect rect = CoordinatesTransform.normalizedPreviewToUi(mFaces[i].rect,
                        mPreviewWidth, mPreviewHeight,
                        mDisplayOrientation, mMirror);
                mFaceIndicator.setBounds(rect.left, rect.top,
                        rect.right, rect.bottom);
                mFaceIndicator.draw(canvas);
            }
        }
        super.onDraw(canvas);
    }
通过传递原始的人脸坐标,和当前实际预览的画布宽高,是否镜像进行计算,
最终通过修改 displayOrientation 为 90,viewWidth 和 viewHeight 由原来的 / 2000f 修改为 /2200f 和 /1500f
当然也可能需要根据你的屏幕实际尺寸调整
public static Rect normalizedPreviewToUi(Rect rect, int w, int h,
                                      int displayOrientation, boolean isMirror) {
        int previewHeight = 0;
        int previewWidth = 0;
        if (displayOrientation == 0 || displayOrientation == 180) {
            previewHeight = h > w ? w : h;//740
            previewWidth = h > w ? h : w;//986
        } else if (displayOrientation == 90 || displayOrientation == 270) {
            previewHeight = h > w ? h : w;//986
            previewWidth = h > w ? w : h;//740
        }
        coordinatesLog(TAG, "normalizedPreviewToUi, w = " + w + ", h = " + h
                + ", orientation = " + displayOrientation
                + ", mirror = " + isMirror);
        coordinatesLog(TAG, "normalizedPreviewToUi, previewWidth = " + previewWidth
            + ", previewHeight = " + previewHeight);
        coordinatesLog(TAG, "normalizedPreviewToUi, rect = (" + rect.left + ", " + rect.top + ", "
                + rect.right + ", " + rect.bottom + ")");
        Matrix matrix = new Matrix();
        prepareMatrix(matrix, isMirror, displayOrientation, previewWidth, previewHeight);
        RectF rectf = new RectF(rect);
        matrix.mapRect(rectf);
        Rect resultRect = new Rect();
        rectf.round(resultRect);
        coordinatesLog(TAG, "normalizedPreviewToUi, result_rect = (" + resultRect.left + ", "
                + resultRect.top + ", "
                + resultRect.right + ", " + resultRect.bottom + ")");
        return resultRect;
    }
private static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
                                      int viewWidth, int viewHeight) {
        // Need mirror for front camera.
        matrix.setScale(mirror ? -1 : 1, 1);
        // This is the value for android.hardware.Camera.setDisplayOrientation.
        matrix.postRotate(90 /*displayOrientation*/);
        // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
        // UI coordinates range from (0, 0) to (width, height).
        // matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
        //cczheng change displayOrientation 0 to 90, scale 2000->2200 2000->1500
        matrix.postScale(viewWidth / 2200f, viewHeight / 1500f);
        matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
    }
录像相关
经过上面的调整,录像预览时方向是对的,但保存的视频播放时依旧是竖屏的,这么说我们还需要进一步修改。
通过搜索发现设置录像参数时 mMediaRecorder.setOrientationHint() 就是控制保存视频的成像方向。
整个工程搜索找到
./common/src/com/mediatek/camera/common/mode/video/recorder/NormalRecorder.java:        mMediaRecorder.setOrientationHint(spec.orientationHint);
通过打印日志发现 orientationHint 果然为 0,竖屏,那么我们只需将 orientationHint 改为 90 应该就能为横屏
2019-11-21 08:30:09.601 3937-3937/com.mediatek.camera D/CamAp_VideoHelper: [getVideoTempPath] mTempPath = /storage/emulated/0/DCIM/Camera/.videorecorder.3gp.tmp
2019-11-21 08:30:09.648 3937-3937/com.mediatek.camera D/CamAp_NormalRecorder: [init]   filePath = /storage/emulated/0/DCIM/Camera/.videorecorder.3gp.tmp  spec.captureRate = 0  spec.videoFrameRate = 0  spec.orientationHint = 0  spec.profile.videoFrameRate = 30  spec.profile.videoFrameWidth = 1280  spec.profile.videoFrameHeight = 720
接下来简单跟踪下初始化配置参数的过程
common\src\com\mediatek\camera\common\mode\video\VideoMode.java
initRecorder() 创建 NormalRecorder 对象,并开始初始化 init,需要传递 RecorderSpec 对象(包含很多录像相关参数的 bean)
通过自身 configRecorderSpec() 创建,最终调用到 VideoHelper 的 configRecorderSpec()
protected boolean initRecorder(boolean isStartRecording) {
        LogHelper.d(TAG, "[initRecorder]");
        releaseRecorder();
        mRecorder = new NormalRecorder();
        try {
            mRecorder.init(configRecorderSpec(isStartRecording));
            setMediaRecorderParameters();
            initForHal3(isStartRecording);
        } catch (RuntimeException e) {
            e.printStackTrace();
            releaseRecorder();
            return false;
        }
        return true;
    }
private IRecorder.RecorderSpec configRecorderSpec(boolean isStartRecording) {
        IRecorder.RecorderSpec recorderSpec = mVideoHelper.configRecorderSpec(
                getProfile(), mCameraId, mCameraApi, mSettingManager);
        mOrientationHint = recorderSpec.orientationHint;
        recorderSpec.infoListener = mOnInfoListener;
        recorderSpec.errorListener = mOnErrorListener;
        recorderSpec.releaseListener = mOnInfoListener;
        recorderSpec = modifyRecorderSpec(recorderSpec, isStartRecording);
        return recorderSpec;
    }
configRecorderSpec() 中新建一个内部类对象 RecorderSpec,依次给各个 public 字段赋值,默认指定使用 CameraApi.API2
所以获取 orientationHint 走的如下带 CameraCharacteristics 参数的 getRecordingRotation() 方法
由于我们的设备没有重力传感器,mApp.getGSensorOrientation() 一直是 -1,也就是 ORIENTATION_UNKNOWN
所以最终 rotation = sensorOrientation,打印 sensorOrientation 为 0,也就符合上面说的 orientationHint 果然为 0
当然你也可以在这里修改 getRecordingRotation() 返回值也能达到一样的效果
common\src\com\mediatek\camera\common\mode\video\VideoHelper.java
public IRecorder.RecorderSpec configRecorderSpec(CamcorderProfile profile, String cameraId,
                CameraDeviceManagerFactory.CameraApi api, ISettingManager settingManager) {
        sProfile = profile;
        IRecorder.RecorderSpec recorderSpec = new IRecorder.RecorderSpec();
        if (mCameraDevice.getCamera() != null) {
            mCameraDevice.unLockCamera();
            recorderSpec.camera = mCameraDevice.getCamera().getCamera();
        }
        if (api == CameraDeviceManagerFactory.CameraApi.API1) {
            recorderSpec.videoSource = MediaRecorder.VideoSource.CAMERA;
            recorderSpec.orientationHint = getRecordingRotation(mApp.getGSensorOrientation(),
                    mCameraDevice.getCameraInfo(Integer.parseInt(cameraId)));
        } else {
            recorderSpec.videoSource = MediaRecorder.VideoSource.SURFACE;
            recorderSpec.orientationHint = getRecordingRotation(mApp.getGSensorOrientation(),
                    getCameraCharacteristics(mApp.getActivity(), cameraId));
        }
        if (VALUE_ON.equals(settingManager.getSettingController().queryValue("key_microphone"))) {
            recorderSpec.isRecordAudio = true;
            recorderSpec.audioSource = MediaRecorder.AudioSource.CAMCORDER;
        } else {
            recorderSpec.isRecordAudio = false;
        }
        recorderSpec.profile = sProfile;
        recorderSpec.maxDurationMs = 0;
        recorderSpec.maxFileSizeBytes = getRecorderMaxSize();
        recorderSpec.location = mCameraContext.getLocation();
        recorderSpec.outFilePath = getVideoTempPath();
        return recorderSpec;
    }
public static int getRecordingRotation(int orientation, CameraCharacteristics characteristics) {
        int rotation = -1;
        int sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        boolean facingFront = characteristics.get(CameraCharacteristics.LENS_FACING)
                == CameraCharacteristics.LENS_FACING_FRONT;
        if (orientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
            if (facingFront) {
                rotation = (sensorOrientation - orientation + 360) % 360;
            } else {
                rotation = (sensorOrientation + orientation) % 360;
            }
        } else {
            rotation = sensorOrientation;
        }
        LogHelper.e(TAG, "[getRecordingRotation] orientation = " +
                orientation + " sensorOrientation = " + sensorOrientation + " rotation = "  + rotation);
        return rotation;
    }
APP 应用参考文章
Android Camera2教程之打开相机、开启预览、实现PreviewCallback、拍照
Android9.0 Camera2 横屏问题修改记录的更多相关文章
- SpUtil多样加密存储,兼容android9.0
		代码地址如下:http://www.demodashi.com/demo/15058.html 前言 在android系统不断升级的过程中,Sharepreferences存储出现多中问题,其中有些是 ... 
- android9.0适配HTTPS:not permitted by network security policy'
		app功能接口正常,其他手机运行OK,但是在Android9.0的手机上报错 CLEARTEXT communication to 192.168.1.xx not permitted by netw ... 
- Android9.0新特性曝光,你准备好了吗
		Android9.0最早出现在2018年1月25日的谷歌官网上,初步代号已经确定为“Pistachio Ice Cream”(开心果冰淇淋),不过按照Google的惯例,如此长的三个单词代号,通常都只 ... 
- 使用appium在android9.0真机上测试程序时报错command failed shell “ps ‘uiautomator’”的解决办法
		appium目前最新的windows版本是1.4.16,在android9.0真机上测试程序时会报错:command failed shell “ps ‘uiautomator’”. 网上大多数人的解 ... 
- Android9.0 如何区分SDK接口和非 SDK接口
		刚刚有同学问我,不太了解 "非SDK接口" 是什么意思?android9.0有什么限制 ?apache的http也有限制 ? 而且现在的大部分系统都升级上来了,黑名单.灰名单和白名 ... 
- 很带劲,Android9.0可以在i.MX8开发板上这样跑
		米尔MYD-JX8MX开发板移植了Android9.0操作系统,现阶段最高版本的Android9.0操作系统将给您的产品在安全与稳定性方面带来更大的提升.可惜了,这里不能上传视频在i.MX8开发板跑A ... 
- Android9.0 MTK 平板横屏方案修改(强制app横屏 + 开机logo/动画+关机充电横屏 + RecoveryUI 横屏)
		文章较长建议先收藏再看 拆解步骤 1.app 强制横屏显示,无视 android:screenOrientation="portrait" 属性 2.屏幕触摸坐标修改为横屏 3.开 ... 
- cocos2dx 2.0+ 版本,IOS6.0+设置横屏
		使用cocos2dx 自带的xcode模板,是不能正常的设置为横屏的. 一共修改了三个地方: 在项目属性中:Deployment Info中,勾选上 Landscape left,以及 Landsca ... 
- Android4.0强制横屏竖屏
		Android的启动默认是横屏或者竖屏我们的TV本来是横屏显示,但是有客户竟然要竖屏显示,昨天快下班收到的需求,竟然说7.19就要搞定.思路有2个,一个就是修改LCD的默认输出,但是这个不是我这个水平 ... 
随机推荐
- 每日一练_PAT_B_真题
			A+B和C (15) 时间限制 1000 ms 内存限制 32768 KB 代码长度限制 100 KB 判断程序 Standard (来自 小小) 题目描述 给定区间[-2的31次方, 2的31次方] ... 
- (四)mybatis逆向工程
			构建 逆向工程就是说通过数据库当中的表生成class,mapper,接口,不需要自己编写那些,很方便.跟symfony里面的自动生成是一样的:视频里的人说用的不多,但我觉得很方便呀 具体步骤,首先导入 ... 
- LeetCode#26 | Remove Duplicates from Sorted Array 删除有序数组中的重复元素
			一.题目 Description Given a sorted array, remove the duplicates in-place such that each element appear ... 
- Python单引号、双引号、三个双引号的区别
			单引号与双引号是作用是一样的,都是字符串定界符. 如果字符串里面包含的与边界一样的符号,需要转义符来将该符号转成普通字符,不然编译器会将字符串中的那个单引号或双引号当成字符串的边界. 例如: ‘I d ... 
- 编译原理实验之SLR1文法分析
			---内容开始--- 这是一份编译原理实验报告,分析表是手动造的,可以作为借鉴. 基于 SLR(1) 分析法的语法制导翻译及中间代码生成程序设计原理与实现1 .理论传授语法制导的基本概念,目标代码结 ... 
- php 对象示例
			<?php header("content-type:text/html; charset=utf-8"); error_reporting(E_ALL); class My ... 
- k8s系列---StorageClass
			介绍这个概念前,需要提前知道存储卷pv/pvc之类的概念. 之前的文章有关于EFK日志系统的介绍,里面的环境是测试环境,完全按照教程一步步的操作,甚至注释掉了持久化存储,当真正线上部署时,又抓虾,打开 ... 
- yum 升级php版本
			centos默认安装的php都是 5.3的 ,现在需要 5.6以上的版本 手动安装比较麻烦,直接用yum升级了. 一.准备工作 首先检查当前php版本 #php -v 查看安装的php扩展包 #yu ... 
- 动态获取bind dns日志IP脚本
			#!/usr/bin/env python #_*_coding:utf-8_*_ ''' python deny_dns_allip.py your_filelog_name 动态获取dns日志的I ... 
- CoreLocation在iOS8上用法的变化
			1.在使用CoreLocation前需要调用如下函数[iOS8专用]: iOS8对定位进行了一些修改,其中包括定位授权的方法,CLLocationManager增加了下面的两个方法: (1)始终允许访 ... 
