第一个android结合 opencv项目(NDK、OpenCV、android人脸识别)

(2017.5.16更改,见绿色)

前提条件:已经搭建好jdk 和eclipse 开发android 和插件CDT (eclipse c++)的环境

在命令行窗口输入:java –version       如果显示相应版本 代表jdk环境搭建成功

在命令行窗口输入:android –version 如果显示相应版本 代表eclipseandroid环境搭建成功

第一步:配置eclipseNDK 环境

1)      :下载NDK

         http://www.cnblogs.com/yaotong/archive/2011/01/25/1943615.html

         我下载的版本是android-ndk-r10e

2)      :配置NDK

         先配置环境变量,我的NDK安装在E盘

        

         配置主要是执行ndk-build.cmd

谷歌改良了ndk的开发流程,对于Windows环境下NDK的开发,如果使用的NDK是r7之前的版本,必须要安装Cygwin才能使用NDK。而在NDKr7开始,Google的Windows版的NDK提供了一个ndk-build.cmd的脚本,这样,就可以直接利用这个脚本编译,而不需要使用Cygwin了。只需要为Eclipse Android工程添加一个Builders,而为Eclipse配置的builder,其实就是在执行Cygwin,然后传递ndk-build作为参数,这样就能让Eclipse自动编译NDK了。(摘自博客http://www.cnblogs.com/yejiurui/p/3476565.html)

3)      :配置Builders(其实有两种方法配置,先说第一种,个人觉得第二种更加方便

第一种

新建一个android project 我的是(Demo_NDK)

鼠标右键选中该工程选择下面的Properties

在弹出窗口选中


点击右边new按钮出现

直接单击Program 弹出

在切到Refresh 页面

并点击右侧Specify Resources

点击Finish

再去到Build Options 页面

同样点击右侧Specify Resources选中刚才新建的项目

最后点击OK,完成操作

 

第二种

选择Window下拉列表,选中Preferences,再作如下操作

然后选中本项目右键下拉选择Android Tools 选中 add native support 完成操作


以上步骤完成NDK环境搭建

 

第二步:配置进行opencvandroid运行的环境

1):到官网http://opencv.org/downloads.html下载sdk

我下载的是OpenCV-2.4.10-android-sdk

下载后在eclipse 导入android project

选中OpenCV Library -2.4.10 ----

当然你全部导入也行

导入完成后如下

再右键点击刚才新建的项目选中下方Properties

点击OK 后,IDE 会自动 builder project 如果出现以下代表环境搭建好了

 


第三步:

上面两个步骤完成后,我以官方demo opencv-sample-face-detection 来展示如何利用OPENCV来通过NDK和android相关联

建包,分别创建DetectionBasedTracker.java 和FdActity.java

结构如下


DetectionBasedTracker.java

用来写native方法(即本地方法,在C++环境运行)

代码如下

打开命令行进入这个项目的bin\classes目录(找到已经编译的.class文件)

输入命令:javah org.opencv.samples.facedetect.DetectionBasedTracker

org.opencv.samples.facedetect是类包路径

DetectionBasedTracker             是类名

将在bin\classes目录下生成org_opencv_samples_facedetect_DetectionBasedTracker.h文件,这个文件就是我们首先要拿到的文件

将其复制粘贴到项目的JNI 目录下

同时创建Android.mk 文件(配置文件

LOCAL_MODULE    :=Demo_NDK        #标识你在Android.mk文件中描述的每个模板

这个标识着Java要调用的动态链接库

// Load native library after(!) OpenCV initialization

System.loadLibrary("Demo_NDK");

LOCAL_C_INCLUDES :=E:/OpenCV-2.4.10-android-sdk/sdk/native/jni/include

这将指定opencv包的路径,(这个很坑,我之前没有指定这段代码的时候,在下面.cpp文件导入openCV包总是报找不到包的错误)

再创建Application.mk 文件(配置文件

再创建org_opencv_samples_facedetect_DetectionBasedTracker.cpp文件,在里面编写openCV代码,分别对DetectionBasedTracker.java6个Native方法实现,代码如下

#include

#include

#include

#include

#include

#include

#define LOG_TAG"FaceDetection/DetectionBasedTracker"

#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__))

usingnamespace std;

usingnamespace cv;

inlinevoidvector_Rect_to_Mat(vector<Rect>& v_rect,Mat& mat)

{

    mat = Mat(v_rect, true);

}

JNIEXPORT jlongJNICALLJava_org_opencv_samples_facedetect_DetectionBasedTracker_nativeCreateObject

(JNIEnv* jenv,jclass,jstring jFileName,jint faceSize)

{

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeCreateObjectenter");

    constchar* jnamestr = jenv->GetStringUTFChars(jFileName, NULL);

    string stdFileName(jnamestr);

    jlong result = 0;

    try

    {

        DetectionBasedTracker::Parameters DetectorParams;

        if (faceSize > 0)

            DetectorParams.minObjectSize = faceSize;

        result = (jlong)newDetectionBasedTracker(stdFileName,DetectorParams);

    }

    catch(cv::Exception& e)

    {

        LOGD("nativeCreateObject caughtcv::Exception:%s", e.what());

        jclass je = jenv->FindClass("org/opencv/core/CvException");

        if(!je)

            je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, e.what());

    }

    catch (...)

    {

        LOGD("nativeCreateObject caught unknown exception");

        jclass je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, "Unknown exception in JNI code ofDetectionBasedTracker.nativeCreateObject()");

        return 0;

    }

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeCreateObjectexit");

    return result;

}

JNIEXPORT voidJNICALLJava_org_opencv_samples_facedetect_DetectionBasedTracker_nativeDestroyObject

(JNIEnv* jenv,jclass,jlong thiz)

{

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeDestroyObjectenter");

    try

    {

        if(thiz != 0)

        {

            ((DetectionBasedTracker*)thiz)->stop();

            delete (DetectionBasedTracker*)thiz;

        }

    }

    catch(cv::Exception& e)

    {

        LOGD("nativeestroyObject caughtcv::Exception:%s", e.what());

        jclass je = jenv->FindClass("org/opencv/core/CvException");

        if(!je)

            je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, e.what());

    }

    catch (...)

    {

        LOGD("nativeDestroyObject caught unknown exception");

        jclass je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, "Unknown exception in JNI code ofDetectionBasedTracker.nativeDestroyObject()");

    }

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeDestroyObjectexit");

}

JNIEXPORT voidJNICALLJava_org_opencv_samples_facedetect_DetectionBasedTracker_nativeStart

(JNIEnv* jenv,jclass,jlong thiz)

{

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeStartenter");

    try

    {

        ((DetectionBasedTracker*)thiz)->run();

    }

    catch(cv::Exception& e)

    {

        LOGD("nativeStart caughtcv::Exception: %s", e.what());

        jclass je = jenv->FindClass("org/opencv/core/CvException");

        if(!je)

            je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, e.what());

    }

    catch (...)

    {

        LOGD("nativeStart caught unknown exception");

        jclass je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, "Unknown exception in JNI code ofDetectionBasedTracker.nativeStart()");

    }

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeStartexit");

}

JNIEXPORT voidJNICALLJava_org_opencv_samples_facedetect_DetectionBasedTracker_nativeStop

(JNIEnv* jenv,jclass,jlong thiz)

{

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeStopenter");

    try

    {

        ((DetectionBasedTracker*)thiz)->stop();

    }

    catch(cv::Exception& e)

    {

        LOGD("nativeStop caughtcv::Exception: %s", e.what());

        jclass je = jenv->FindClass("org/opencv/core/CvException");

        if(!je)

            je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, e.what());

    }

    catch (...)

    {

        LOGD("nativeStop caught unknown exception");

        jclass je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, "Unknown exception in JNI code ofDetectionBasedTracker.nativeStop()");

    }

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeStopexit");

}

JNIEXPORT voidJNICALLJava_org_opencv_samples_facedetect_DetectionBasedTracker_nativeSetFaceSize

(JNIEnv* jenv,jclass,jlong thiz,jint faceSize)

{

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeSetFaceSizeenter");

    try

    {

        if (faceSize > 0)

        {

            DetectionBasedTracker::Parameters DetectorParams = \

            ((DetectionBasedTracker*)thiz)->getParameters();

            DetectorParams.minObjectSize = faceSize;

            ((DetectionBasedTracker*)thiz)->setParameters(DetectorParams);

        }

    }

    catch(cv::Exception& e)

    {

        LOGD("nativeStop caughtcv::Exception: %s", e.what());

        jclass je = jenv->FindClass("org/opencv/core/CvException");

        if(!je)

            je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, e.what());

    }

    catch (...)

    {

        LOGD("nativeSetFaceSize caught unknown exception");

        jclass je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, "Unknown exception in JNI code ofDetectionBasedTracker.nativeSetFaceSize()");

    }

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeSetFaceSizeexit");

}

JNIEXPORT voidJNICALLJava_org_opencv_samples_facedetect_DetectionBasedTracker_nativeDetect

(JNIEnv* jenv,jclass,jlong thiz,jlong imageGray,jlong faces)

{

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeDetectenter");

    try

    {

        vector<Rect> RectFaces;

        ((DetectionBasedTracker*)thiz)->process(*((Mat*)imageGray));

        ((DetectionBasedTracker*)thiz)->getObjects(RectFaces);

       vector_Rect_to_Mat(RectFaces, *((Mat*)faces));

    }

    catch(cv::Exception& e)

    {

        LOGD("nativeCreateObject caughtcv::Exception:%s", e.what());

        jclass je = jenv->FindClass("org/opencv/core/CvException");

        if(!je)

            je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, e.what());

    }

    catch (...)

    {

        LOGD("nativeDetect caught unknown exception");

        jclass je = jenv->FindClass("java/lang/Exception");

        jenv->ThrowNew(je, "Unknown exception in JNI code DetectionBasedTracker.nativeDetect()");

    }

    LOGD("Java_org_opencv_samples_facedetect_DetectionBasedTracker_nativeDetectexit");

}

 

然后完善DetectionBasedTracker.java 文件

packageorg.opencv.samples.facedetect;

import org.opencv.core.Mat;

importorg.opencv.core.MatOfRect;

publicclass DetectionBasedTracker

{

    public DetectionBasedTracker(String cascadeName,int minFaceSize) {

        mNativeObj =nativeCreateObject(cascadeName,minFaceSize);

    }

    publicvoid start() {

        nativeStart(mNativeObj);

    }

    publicvoid stop() {

        nativeStop(mNativeObj);

    }

    publicvoid setMinFaceSize(int size) {

        nativeSetFaceSize(mNativeObj, size);

    }

    publicvoid detect(Mat imageGray, MatOfRect faces) {

        nativeDetect(mNativeObj,imageGray.getNativeObjAddr(), faces.getNativeObjAddr());

    }

    publicvoid release() {

        nativeDestroyObject(mNativeObj);

        mNativeObj = 0;

    }

    privatelongmNativeObj = 0;

    privatestaticnativelongnativeCreateObject(String cascadeName, int minFaceSize);

    privatestaticnativevoidnativeDestroyObject(long thiz);

    privatestaticnativevoidnativeStart(longthiz);

    privatestaticnativevoidnativeStop(longthiz);

    privatestaticnativevoidnativeSetFaceSize(longthiz,intsize);

    privatestaticnativevoidnativeDetect(longthiz,longinputImage,longfaces);

}

         接下来是FdActivity.java主界面,代码如下

packageorg.opencv.samples.facedetect;

packageorg.opencv.samples.facedetect;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.InputStream;

importorg.opencv.android.BaseLoaderCallback;

importorg.opencv.android.CameraBridgeViewBase;

importorg.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;

importorg.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;

importorg.opencv.android.LoaderCallbackInterface;

importorg.opencv.android.OpenCVLoader;

import org.opencv.core.Core;

import org.opencv.core.Mat;

import org.opencv.core.MatOfRect;

import org.opencv.core.Rect;

import org.opencv.core.Scalar;

import org.opencv.core.Size;

import org.opencv.objdetect.CascadeClassifier;

import android.app.Activity;

import android.content.Context;

importandroid.content.pm.ActivityInfo;

import android.os.Bundle;

import android.util.Log;

import android.view.Menu;

import android.view.MenuItem;

import android.view.WindowManager;

import com.example.demo_ndk.R;

public class FdActivity extendsActivity implements CvCameraViewListener2 {

 

   private static final String   TAG                 ="OCVSample::Activity";

   private static final Scalar   FACE_RECT_COLOR     = newScalar(0, 255, 0, 255);

   public static final int       JAVA_DETECTOR       = 0;

   public static final int        NATIVE_DETECTOR     = 1;

   private MenuItem              mItemFace50;

   private MenuItem              mItemFace40;

   private MenuItem              mItemFace30;

   private MenuItem              mItemFace20;

   private MenuItem              mItemType;

   private Mat                    mRgba;

   private Mat                   mGray;

   private File                  mCascadeFile;

   private CascadeClassifier     mJavaDetector;

   private DetectionBasedTracker mNativeDetector;

   private int                    mDetectorType       = JAVA_DETECTOR;

   private String[]              mDetectorName;

   private float                 mRelativeFaceSize   = 0.2f;

   private int                   mAbsoluteFaceSize   = 0;

   //在 mOpenCvCameraView 的回调接口onCameraFrame函数里面处理每一帧从相机获取到的图片。

   private CameraBridgeViewBase   mOpenCvCameraView;

   private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {

        @Override

        public void onManagerConnected(intstatus) {

            switch (status) {

                caseLoaderCallbackInterface.SUCCESS:

                {

                    Log.i(TAG, "OpenCVloaded successfully");

                    // Load native libraryafter(!) OpenCV initialization

                    System.loadLibrary("Demo_NDK");

                    try {

                        // load cascade filefrom application resources

                        InputStream is =getResources().openRawResource(R.raw.lbpcascade_frontalface);

//这里去加载人脸识别分类文件(lbpcascade_frontalface.XML 是XML文件,这都是利用Opencv给我们提供好的XML人脸识别分类文件,在opencv/source/data/目录下,这里把那个文件拉到了Raw资源文件里面,方便Android调用,如果要自己实现一个XML人脸识别分类文件的话,需要用到opencv_haartraining,来训练大量数据,最终生成XML人脸识别分类文件

                        File cascadeDir =getDir("cascade", Context.MODE_PRIVATE);

                        mCascadeFile = new File(cascadeDir,"lbpcascade_frontalface.xml");

                        FileOutputStream os =new FileOutputStream(mCascadeFile);

 

                        byte[] buffer = newbyte[4096];

                        int bytesRead;

                        while ((bytesRead =is.read(buffer)) != -1) {

                            os.write(buffer, 0,bytesRead);

                        }

                        is.close();

                        os.close();

 

                        mJavaDetector = newCascadeClassifier(mCascadeFile.getAbsolutePath());

                        if(mJavaDetector.empty()) {

                            Log.e(TAG,"Failed to load cascade classifier");

                            mJavaDetector =null;

                        } else

                            Log.i(TAG,"Loaded cascade classifier from " + mCascadeFile.getAbsolutePath());

 

                        mNativeDetector = newDetectionBasedTracker(mCascadeFile.getAbsolutePath(), 0);

 

                        cascadeDir.delete();

 

                    } catch (IOException e) {

                        e.printStackTrace();

                        Log.e(TAG, "Failedto load cascade. Exception thrown: " + e);

                    }

 

                   mOpenCvCameraView.enableView();

                } break;

                default:

                {

                   super.onManagerConnected(status);

                } break;

            }

        }

   };

   public FdActivity() {

       mDetectorName = newString[2];

        mDetectorName[JAVA_DETECTOR] ="Java";

        mDetectorName[NATIVE_DETECTOR] ="Native (tracking)";

        Log.i(TAG, "Instantiated new" + this.getClass());

   }

   /** Called when the activity is first created. */

   @Override

   public void onCreate(Bundle savedInstanceState) {

        Log.i(TAG, "calledonCreate");

        super.onCreate(savedInstanceState);

       getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        //设置拍摄方向   

        /*

                       系统默认

                       ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED

                       锁定直式

                       ActivityInfo.SCREEN_ORIENTATION_PORTRAIT

                       锁定横式

                       ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE

                       随使用者当下

                       ActivityInfo.SCREEN_ORIENTATION_USER

                       与活动线程下相同的设定

                       ActivityInfo.SCREEN_ORIENTATION_BEHIND

                       不随SENSOR改变

                       ActivityInfo.SCREEN_ORIENTATION_NOSENSOR

                       随SENSOR改变

                       ActivityInfo.SCREEN_ORIENTATION_SENSOR

 

         */

        this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 

       setContentView(R.layout.face_detect_surface_view);

        mOpenCvCameraView =(CameraBridgeViewBase) findViewById(R.id.fd_activity_surface_view);

       mOpenCvCameraView.setCvCameraViewListener(this);

   }

 

   @Override

   public void onPause()

   {

        super.onPause();

        if (mOpenCvCameraView != null)

            mOpenCvCameraView.disableView();

   }

 

   @Override

   public void onResume()

   {

        super.onResume();

       OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_3, this,mLoaderCallback);

   }

 

   public void onDestroy() {

        super.onDestroy();

        mOpenCvCameraView.disableView();

   }

 

   public void onCameraViewStarted(int width, int height) {

        mGray = new Mat();

        mRgba = new Mat();

   }

 

   public void onCameraViewStopped() {

        mGray.release();

        mRgba.release();

   }

 

   public Mat onCameraFrame(CvCameraViewFrame inputFrame) {

  //这里获取相机拍摄到的原图,彩色图

        mRgba = inputFrame.rgba();

//这里获取相机拍摄到的灰度图,用来给下面检测人脸使用。

        mGray = inputFrame.gray();

 

        if (mAbsoluteFaceSize == 0) {

            int height = mGray.rows();

            if (Math.round(height * mRelativeFaceSize)> 0) {

                mAbsoluteFaceSize =Math.round(height * mRelativeFaceSize);

            }

           mNativeDetector.setMinFaceSize(mAbsoluteFaceSize);

        }

 

        MatOfRect faces = new MatOfRect();

 

        if (mDetectorType == JAVA_DETECTOR) {

            if (mJavaDetector != null)

//调用opencv的detectMultiScale()检测函数,参数意义如下

/*

        mGray表示的是要检测的输入图像,faces表示检测到的目标序列,存储检测结果(坐标位置,长,宽),1.1表示  
            每次图像尺寸减小的比例为1.1,2表示每一个目标至少要被检测到3次才算是真的目标(因为周围的像素和不同的窗口大  
            小都可以检测到目标),2(其实是一个常量:CV_HAAR_SCALE_IMAGE)表示不是缩放分类器来检测,而是缩放图像,最后两个size()为检测目标的  
            最小最大尺寸  

*/

               mJavaDetector.detectMultiScale(mGray, faces, 1.1, 2, 2, // TODO:objdetect.CV_HAAR_SCALE_IMAGE

                        newSize(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size());

        }

        else if (mDetectorType ==NATIVE_DETECTOR) {

            if (mNativeDetector != null)

                mNativeDetector.detect(mGray,faces);

        }

        else {

            Log.e(TAG, "Detection methodis not selected!");

        }

 

        Rect[] facesArray = faces.toArray();

        for (int i = 0; i

//在原图mRgba上为每个检测到的人脸画一个绿色矩形

            Core.rectangle(mRgba,facesArray[i].tl(), facesArray[i].br(), FACE_RECT_COLOR, 3);

  //返回处理好的图像,返回后会直接显示在JavaCameraView上。

        return mRgba;

   }

 

   @Override

   public boolean onCreateOptionsMenu(Menu menu) {

        Log.i(TAG, "calledonCreateOptionsMenu");

        mItemFace50 = menu.add("Face size50%");

        mItemFace40 = menu.add("Face size40%");

        mItemFace30 = menu.add("Face size30%");

        mItemFace20 = menu.add("Face size20%");

        mItemType   = menu.add(mDetectorName[mDetectorType]);

        return true;

   }

 

   @Override

   public boolean onOptionsItemSelected(MenuItem item) {

        Log.i(TAG, "calledonOptionsItemSelected; selected item: " + item);

        if (item == mItemFace50)

            setMinFaceSize(0.5f);

        else if (item == mItemFace40)

            setMinFaceSize(0.4f);

        else if (item == mItemFace30)

            setMinFaceSize(0.3f);

        else if (item == mItemFace20)

            setMinFaceSize(0.2f);

        else if (item == mItemType) {

            int tmpDetectorType =(mDetectorType + 1) % mDetectorName.length;

           item.setTitle(mDetectorName[tmpDetectorType]);

            setDetectorType(tmpDetectorType);

        }

        return true;

   }

 

   private void setMinFaceSize(float faceSize) {

        mRelativeFaceSize = faceSize;

        mAbsoluteFaceSize = 0;

   }

 

   private void setDetectorType(int type) {

        if (mDetectorType != type) {

            mDetectorType = type;

 

            if (type == NATIVE_DETECTOR) {

                Log.i(TAG, "DetectionBased Tracker enabled");

                mNativeDetector.start();

            } else {

                Log.i(TAG, "Cascadedetector enabled");

                mNativeDetector.stop();

            }

        }

   }

}

最后配置AndroidManifest.xml文件

<supports-screensandroid:resizeable="true"

                      android:smallScreens="true"

                      android:normalScreens="true"

                      android:largeScreens="true"

                      android:anyDensity="true"

                      />

    <uses-permissionandroid:name="android.permission.CAMERA"/>

    <uses-featureandroid:name="android.hardware.camera"android:required="false"/>

    <uses-featureandroid:name="android.hardware.camera.autofocus"android:required="false"/>

    <uses-featureandroid:name="android.hardware.camera.front"android:required="false"/>

 <uses-featureandroid:name="android.hardware.camera.front.autofocus"android:required="false"/>

到此大功告成

运行结果如下

        

源代码已经上传至:

http://download.csdn.net/detail/wjb820728252/9725892

至此,也是我这段时间走过的坑,希望能帮助到刚入门这门槛的朋友,谢谢!

更多相关文章

  1. Android开发进阶:如何读写Android文件
  2. Android(安卓)常用开发术语
  3. Android漏洞——将Android恶意代码隐藏在图片中
  4. 第三课:android数据相关---文件
  5. 【Android布局】在程序中设置android:gravity 和 android:layout
  6. 【Android每周专题】触摸屏手势
  7. Android模拟SD卡实现方法解析
  8. Android(安卓)Studio导入Project的方法
  9. Android(安卓)常用开发术语

随机推荐

  1. android 混淆时出现的一些问题
  2. android常用的方法
  3. Android之设置横屏竖屏
  4. [RK3399][Android7.1] 调试笔记 --- 默认
  5. Android学习笔记---使用HttpClient发送PO
  6. Android(安卓)丢失R.java文件处理方法
  7. Android(安卓)studio如何运行java程序代
  8. Android实现显示电量的控件代码
  9. Android(安卓)Retrofit源码解析
  10. android textview 文本在代码中设置粗体