zoukankan      html  css  js  c++  java
  • Android编程初涉,以控制摄像头为例

    国庆假期期间王老师给我们一天的时间去熟悉Android编程,时间非常的短,当然内心其实并不想做的,不过本着既然做就尽量做好的原则呢,忙了几个小时把王老师要求的简单功能实现了一下,当然因为是只是熟悉一下就上网查了些资料粘贴复制了一下,设计思想和库并不熟悉,下面就实现控制摄像头的功能代码和《第一行代码——Android》和《操作系统概念》两本书的内容简单的展开分析一下Android编程,思考的应该会比较发散,我尽量保持相对完整的原则去写。

    1. 前言

    说到Android就不得不讨论一下它与Linux的关系,这两个操作系统都是著名的开源项目,那他们有什么关系呢?这里先补充一下开源许可证的相关知识:

    Android 是 Apache License 2.0 许可证,这是怎么回事呢?

    Linux kernel 的版权是 GPL。 这下问题来了:如果你是硬件厂商,希望你的硬件能在 Linux kernel 下运作,那么就必须要有驱动程序。如果驱动程序的程序代码公开,等于硬件规格也公开的差不多了。许多厂商不愿意这么做,所以就提供编好的驱动程序,但不提供原始码。 Android 的重点就是商业应用,Google 采用了一些手法来绕过这问题。他们把驱动程序移到 "userspace",也就是说,把驱动程序变成在 Linux kernel 上层跑,而不是一起跑的东西,这样就可以避过 GPL。然后,在 kernel 这边开个小门,让本来不能直接控制到硬件的 "userspace" 程序也可以碰得到,这样只要把"开个小门"的程序代码公布就行啦。

     Android系统架构

    Android代码包括三部分:
    1. Android 开源系统(Android Open Source Project,简称 AOSP ) 提供了 Android 系统的框架,包括修改后的 Android 专用 Linux 内核,Dalvik 虚拟机(5.0系统之后改为ART运行时环境)和 Android 应用层框架等。AOSP的大部分源码采用 Apache 2.0 授权模式发布,另外 Android 专用 Linux 内核部分采用 GPL 授权。Apache 2.0 授权规定,可以任意使用源码,不需要开源。GPL授权规定,对源码的任何修改都必须开源。
    2. Google 移动服务(Google Mobile Service,GMS)GMS 是由 Google 提供的一系列提高用户移动体验的应用和服务,包括各种服务和内购功能,还有一些 Google 的应用:Play 市场,GMail,Chrome,Google 地图,Google+ 等。GMS 提供了很好的特性,但是 GMS 是闭源的。为了获取 GMS 的使用,必须通过 Google 的授权。这部分源代码是不可见的。
    3. 基于 AOSP 的源码开发独立的 Android 系统 AOSP 是开源的,任何人都可以修改 AOSP 的代码开发独立于 AOSP 的 Android 系统。因为 AOSP 采用 Apache 2.0 授权,所以修改 AOSP 后的代码不需要开源。Amazon 的 Kindle Fire 和众多国产手机厂商的系统都属于这一类。

    Android应用开发特色:

    1. 四大组件

      Android系统四大组件分别是活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)和内容提供器(Content Provider)。其中活动是所有Android应用程序的门面,凡是在应用中你看得到的东西,都是放在活动中的。而服务就比较低调了,你无法看到它,但它会一直在后台默默地运行,即使用户退出了应用,服务仍然是可以继续运行的。广播接收器允许你的应用接收来自各处的广播消息,比如电话、短信等,当然你的应用同样也可以向外发出广播消息。内容提供器则为应用程序之间共享数据提供了可能,比如你想要读取系统电话簿中的联系人,就需要通过内容提供器来实现。

    2. 丰富的系统控件

      Android系统为开发者提供了丰富的系统控件,使得我们可以很轻松地编写出漂亮的界面。当然如果你品位比较高,不满足于系统自带的控件效果,也完全可以定制属于自己的控件。

    3. SQLite数据库

      Android系统还自带了这种轻量级、运算速度极快的嵌入式关系型数据库。它不仅支持标准的SQL语法,还可以通过Android封装好的API(Application Programming Interface)进行操作,让存储和读取数据变得非常方便。

    4. 强大的多媒体

      Android系统还提供了丰富的多媒体服务,如音乐、视频、录音、拍照、闹铃,等等,这一切你都可以在程序中通过代码进行控制,让你的应用变得更加丰富多彩。

    5. 地理位置定位

      移动设备和PC相比起来,地理位置定位功能应该可以算是很大的一个亮点。现在的Android手机都内置有GPS(Global Position System),走到哪儿都可以定位到自己的位置,发挥你的想象就可以做出创意十足的应用,如果再结合功能强大的地图功能,LBS(Location Based Service)这一领域潜力无限。

    Android开发环境搭建——Android Studio:https://blog.csdn.net/hju22/article/details/89041268

    回到Android Studio当中,首先展开HelloWorld项目,你会看到如图所示的项目结构。

    Android模式的项目结构

    任何一个新建的项目都会默认使用Android模式的项目结构,但这并不是项目真实的目录结构,而是被Android Studio转换过的。这种项目结构简洁明了,适合进行快速开发,但是对于新手来说可能并不易于理解。点击图当中的Android区域可以切换项目结构模式,如图所示。

    切换项目结构模式

    这里我们将项目结构模式切换成Project,这就是项目真实的目录结构了,如图所示。

    Project模式的项目结构

    一开始看到这么多陌生的东西,你一定会感到有点头晕吧。别担心,我现在就对图中的内容进行一一讲解,之后你再看这张图就不会感到那么吃力了。

    1. .gradle和.idea

      这两个目录下放置的都是Android Studio自动生成的一些文件,我们无须关心,也不要去手动编辑。

    2. app

      项目中的代码、资源等内容几乎都是放置在这个目录下的,我们后面的开发工作也基本都是在这个目录下进行的,待会儿还会对这个目录单独展开进行讲解。

    3. build

      这个目录你也不需要过多关心,它主要包含了一些在编译时自动生成的文件。

    4. gradle

      这个目录下包含了gradle wrapper的配置文件,使用gradle wrapper的方式不需要提前将gradle下载好,而是会自动根据本地的缓存情况决定是否需要联网下载gradle。Android Studio默认没有启用gradle wrapper的方式,如果需要打开,可以点击Android Studio导航栏→File→Settings→Build, Execution, Deployment→Gradle,进行配置更改。

    5. .gitignore

      这个文件是用来将指定的目录或文件排除在版本控制之外的。

    6. build.gradle

      这是项目全局的gradle构建脚本,通常这个文件中的内容是不需要修改的。稍后我们将会详细分析gradle构建脚本中的具体内容。

    7. gradle.properties

      这个文件是全局的gradle配置文件,在这里配置的属性将会影响到项目中所有的gradle编译脚本。

    8. gradlew和gradlew.bat

      这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew是在Linux或Mac系统中使用的,gradlew.bat是在Windows系统中使用的。

    9. HelloWorld.iml

      iml文件是所有IntelliJ IDEA项目都会自动生成的一个文件(Android Studio是基于IntelliJ IDEA开发的),用于标识这是一个IntelliJ IDEA项目,我们不需要修改这个文件中的任何内容。

    10. local.properties

      这个文件用于指定本机中的Android SDK路径,通常内容都是自动生成的,我们并不需要修改。除非你本机中的Android SDK位置发生了变化,那么就将这个文件中的路径改成新的位置即可。

    11. settings.gradle

      这个文件用于指定项目中所有引入的模块。由于HelloWorld项目中就只有一个app模块,因此该文件中也就只引入了app这一个模块。通常情况下模块的引入都是自动完成的,需要我们手动去修改这个文件的场景可能比较少。

    现在整个项目的外层目录结构已经介绍完了。你会发现,除了app目录之外,大多数的文件和目录都是自动生成的,我们并不需要进行修改。想必你已经猜到了,app目录下的内容才是我们以后的工作重点,展开之后结构如图所示。

    app目录下的结构

    那么下面我们就来对app目录下的内容进行更为详细的分析。

    1. build

      这个目录和外层的build目录类似,主要也是包含了一些在编译时自动生成的文件,不过它里面的内容会更多更杂,我们不需要过多关心。

    2. libs

      如果你的项目中使用到了第三方jar包,就需要把这些jar包都放在libs目录下,放在这个目录下的jar包都会被自动添加到构建路径里去。

    3. androidTest

      此处是用来编写Android Test测试用例的,可以对项目进行一些自动化测试。

    4. java

      毫无疑问,java目录是放置我们所有Java代码的地方,展开该目录,你将看到我们刚才创建的HelloWorldActivity文件就在里面。

    5. res

      这个目录下的内容就有点多了。简单点说,就是你在项目中使用到的所有图片、布局、字符串等资源都要存放在这个目录下。当然这个目录下还有很多子目录,图片放在drawable目录下,布局放在layout目录下,字符串放在values目录下,所以你不用担心会把整个res目录弄得乱糟糟的。

    6. AndroidManifest.xml

      这是你整个Android项目的配置文件,你在程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明。由于这个文件以后会经常用到,我们用到的时候再做详细说明。

    7. test

      此处是用来编写Unit Test测试用例的,是对项目进行自动化测试的另一种方式。

    8. .gitignore

      这个文件用于将app模块内的指定的目录或文件排除在版本控制之外,作用和外层的.gitignore文件类似。

    9. app.iml

      IntelliJ IDEA项目自动生成的文件,我们不需要关心或修改这个文件中的内容。

    10. build.gradle

      这是app模块的gradle构建脚本,这个文件中会指定很多项目构建相关的配置,我们稍后将会详细分析gradle构建脚本中的具体内容。

    11. proguard-rules.pro

      这个文件用于指定项目代码的混淆规则,当代码开发完成后打成安装包文件,如果不希望代码被别人破解,通常会将代码进行混淆,从而让破解者难以阅读。

     

    接下来我们一起分析一下HelloWorld项目究竟是怎么运行起来的吧。首先打开AndroidManifest.xml文件,从中可以找到如下代码:

    <activity android:name=".HelloWorldActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    

    这段代码表示对HelloWorldActivity这个活动进行注册,没有在AndroidManifest.xml里注册的活动是不能使用的。其中intent-filter 里的两行代码非常重要,<action android:name= "android.intent.action.MAIN" /> 和<category android:name="android.intent.category.LAUNCHER" /> 表示HelloWorldActivity是这个项目的主活动,在手机上点击应用图标,首先启动的就是这个活动。

    那HelloWorldActivity具体又有什么作用呢?我在介绍Android四大组件的时候说过,活动是Android应用程序的门面,凡是在应用中你看得到的东西,都是放在活动中的。因此你在运行看到的界面,其实就是HelloWorldActivity这个活动。那我们快去看一下它的代码吧,打开HelloWorldActivity,代码如下所示:

    public class HelloWorldActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.hello_world_layout);
        }
    
    }
    

    首先我们可以看到,HelloWorldActivity是继承自AppCompatActivity的,这是一种向下兼容的Activity,可以将Activity在各个系统版本中增加的特性和功能最低兼容到Android 2.1系统。Activity是Android系统提供的一个活动基类,我们项目中所有的活动都必须继承它或者它的子类才能拥有活动的特性(AppCompatActivity 是Activity的子类)。然后可以看到HelloWorldActivity中有一个onCreate() 方法,这个方法是一个活动被创建时必定要执行的方法,其中只有两行代码,并且没有Hello World!的字样。那么上图显示的Hello World!是在哪里定义的呢?

    其实Android程序的设计讲究逻辑和视图分离,因此是不推荐在活动中直接编写界面的,更加通用的一种做法是,在布局文件中编写界面,然后在活动中引入进来。可以看到,在onCreate() 方法的第二行调用了setContentView() 方法,就是这个方法给当前的活动引入了一个hello_world_layout布局,那Hello World!一定就是在这里定义的了!我们快打开这个文件看一看。

    布局文件都是定义在res/layout目录下的,当你展开layout目录,你会看到hello_world_layout.xml这个文件。打开该文件并切换到Text视图,代码如下所示:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/hello_world_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.example.helloworld.HelloWorldActivity">
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!" />
    </RelativeLayout>
    

    现在还看不懂?没关系,后面我会对布局进行详细讲解的,你现在只需要看到上面代码中有一个TextView,这是Android系统提供的一个控件,用于在布局中显示文字的。然后你终于在TextView中看到了Hello World!的字样!哈哈!终于找到了,原来就是通过android:text="Hello World!" 这句代码定义的。

    这样我们就将HelloWorld项目的目录结构以及基本的执行过程都分析完了,相信你对Android项目已经有了一个初步的认识,下一小节中我们就来学习一下项目中所包含的资源。

     

    如果你展开res目录看一下,其实里面的东西还是挺多的,很容易让人看得眼花缭乱,如图所示。

    res目录下的结构

    看到这么多的文件夹也不用害怕,其实归纳一下,res目录就变得非常简单了。所有以drawable开头的文件夹都是用来放图片的,所有以mipmap开头的文件夹都是用来放应用图标的,所有以values开头的文件夹都是用来放字符串、样式、颜色等配置的,layout文件夹是用来放布局文件的。怎么样,是不是突然感觉清晰了很多?

    之所以有这么多mipmap开头的文件夹,其实主要是为了让程序能够更好地兼容各种设备。drawable文件夹也是相同的道理,虽然Android Studio没有帮我们自动生成,但是我们应该自己创建drawable-hdpi、drawable-xhdpi、drawable-xxhdpi等文件夹。在制作程序的时候最好能够给同一张图片提供几个不同分辨率的版本,分别放在这些文件夹下,然后当程序运行的时候,会自动根据当前运行设备分辨率的高低选择加载哪个文件夹下的图片。当然这只是理想情况,更多的时候美工只会提供给我们一份图片,这时你就把所有图片都放在drawable-xxhdpi文件夹下就好了。

    知道了res目录下每个文件夹的含义,我们再来看一下如何去使用这些资源吧。打开res/ values/strings.xml文件,内容如下所示:

    <resources>
        <string name="app_name">HelloWorld</string>
    </resources>
    

    可以看到,这里定义了一个应用程序名的字符串,我们有以下两种方式来引用它。

    • 在代码中通过R.string.app_name 可以获得该字符串的引用。

    • 在XML中通过@string/app_name 可以获得该字符串的引用。

    基本的语法就是上面这两种方式,其中string 部分是可以替换的,如果是引用的图片资源就可以替换成drawable ,如果是引用的应用图标就可以替换成mipmap ,如果是引用的布局文件就可以替换成layout,以此类推。

    下面举一个简单的例子来帮助你理解,打开AndroidManifest.xml文件,找到如下代码:

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        ...
    </application>
    

    其中,HelloWorld项目的应用图标就是通过android:icon 属性来指定的,应用的名称则是通过android:label 属性指定的。可以看到,这里对资源引用的方式正是我们刚刚学过的在XML中引用资源的语法。

    经过本小节的学习,如果你想修改应用的图标或者名称,相信已经知道该怎么办了吧。

     

    不同于Eclipse,Android Studio是采用Gradle来构建项目的。Gradle是一个非常先进的项目构建工具,它使用了一种基于Groovy的领域特定语言(Domain-specific Language,DSL)来声明项目设置,摒弃了传统基于XML(如Ant和Maven)的各种烦琐配置。

    在前面我们已经看到,HelloWorld项目中有两个build.gradle文件,一个是在最外层目录下的,一个是在app目录下的。这两个文件对构建Android Studio项目都起到了至关重要的作用,下面我们就来对这两个文件中的内容进行详细的分析。

    先来看一下最外层目录下的build.gradle文件,代码如下所示:

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.2.0'
        }
    }
    
    allprojects {
        repositories {
            jcenter()
        }
    }
    

    这些代码都是自动生成的,虽然语法结构看上去可能有点难以理解,但是如果我们忽略语法结构,只看最关键的部分,其实还是很好懂的。

    首先,两处repositories 的闭包中都声明了jcenter() 这行配置,那么这个jcenter是什么意思呢?其实它是一个代码托管仓库,很多Android开源项目都会选择将代码托管到jcenter上,声明了这行配置之后,我们就可以在项目中轻松引用任何jcenter上的开源项目了。

    接下来,dependencies 闭包中使用classpath 声明了一个Gradle插件。为什么要声明这个插件呢?因为Gradle并不是专门为构建Android项目而开发的,Java、C++等很多种项目都可以使用Gradle来构建。因此如果我们要想使用它来构建Android项目,则需要声明com.android.tools.build:gradle:2.2.0 这个插件。其中,最后面的部分是插件的版本号,我在写作本书时最新的插件版本是2.2.0。

    这样我们就将最外层目录下的build.gradle文件分析完了,通常情况下你并不需要修改这个文件中的内容,除非你想添加一些全局的项目构建配置。

    下面我们再来看一下app目录下的build.gradle文件,代码如下所示:

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 24
        buildToolsVersion "24.0.2"
        defaultConfig {
            applicationId "com.example.helloworld"
            minSdkVersion 15
            targetSdkVersion 24
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'),
                    'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:24.2.1'
        testCompile 'junit:junit:4.12'
    }
    

    这个文件中的内容就要相对复杂一些了,下面我们一行行地进行分析。首先第一行应用了一个插件,一般有两种值可选:com.android.application 表示这是一个应用程序模块,com.android.library 表示这是一个库模块。应用程序模块和库模块的最大区别在于,一个是可以直接运行的,一个只能作为代码库依附于别的应用程序模块来运行。

    接下来是一个大的Android闭包 ,在这个闭包中我们可以配置项目构建的各种属性。其中,compileSdkVersion 用于指定项目的编译版本,这里指定成24表示使用Android 7.0系统的SDK编译。buildToolsVersion 用于指定项目构建工具的版本,目前最新的版本就是24.0.2,如果有更新的版本时,Android Studio会进行提示。

    然后我们看到,这里在android闭包中又嵌套了一个defaultConfig闭包 ,defaultConfig闭包中可以对项目的更多细节进行配置。其中,applicationId 用于指定项目的包名,前面我们在创建项目的时候其实已经指定过包名了,如果你想在后面对其进行修改,那么就是在这里修改的。minSdkVersion 用于指定项目最低兼容的Android系统版本,这里指定成15表示最低兼容到Android 4.0系统。targetSdkVersion 指定的值表示你在该目标版本上已经做过了充分的测试,系统将会为你的应用程序启用一些最新的功能和特性。比如说Android 6.0系统中引入了运行时权限这个功能,如果你将targetSdkVersion 指定成23或者更高,那么系统就会为你的程序启用运行时权限功能,而如果你将targetSdkVersion 指定成22,那么就说明你的程序最高只在Android 5.1系统上做过充分的测试,Android 6.0系统中引入的新功能自然就不会启用了。剩下的两个属性都比较简单,versionCode 用于指定项目的版本号,versionName 用于指定项目的版本名,这两个属性在生成安装文件的时候非常重要,我们在后面都会学到。

    分析完了defaultConfig闭包,接下来我们看一下buildTypes闭包 。buildTypes闭包中用于指定生成安装文件的相关配置,通常只会有两个子闭包,一个是debug,一个是release。debug闭包用于指定生成测试版安装文件的配置,release闭包用于指定生成正式版安装文件的配置。另外,debug闭包是可以忽略不写的,因此我们看到上面的代码中就只有一个release闭包。下面来看一下release闭包中的具体内容吧,minifyEnabled 用于指定是否对项目的代码进行混淆,true 表示混淆,false 表示不混淆。proguardFiles 用于指定混淆时使用的规则文件,这里指定了两个文件,第一个proguard-android.txt 是在Android SDK目录下的,里面是所有项目通用的混淆规则,第二个proguard-rules.pro 是在当前项目的根目录下的,里面可以编写当前项目特有的混淆规则。需要注意的是,通过Android Studio直接运行项目生成的都是测试版安装文件。

    这样整个android闭包中的内容就都分析完了,接下来还剩一个dependencies闭包 。这个闭包的功能非常强大,它可以指定当前项目所有的依赖关系。通常Android Studio项目一共有3种依赖方式:本地依赖、库依赖和远程依赖。本地依赖可以对本地的Jar包或目录添加依赖关系,库依赖可以对项目中的库模块添加依赖关系,远程依赖则可以对jcenter库上的开源项目添加依赖关系。观察一下dependencies闭包中的配置,第一行的compile fileTree 就是一个本地依赖声明,它表示将libs目录下所有.jar后缀的文件都添加到项目的构建路径当中。而第二行的compile 则是远程依赖声明,com.android.support:appcompat-v7:24.2.1 就是一个标准的远程依赖库格式,其中com.android.support 是域名部分,用于和其他公司的库做区分;appcompat-v7 是组名称,用于和同一个公司中不同的库做区分;24.2.1是版本号,用于和同一个库不同的版本做区分。加上这句声明后,Gradle在构建项目时会首先检查一下本地是否已经有这个库的缓存,如果没有的话则会去自动联网下载,然后再添加到项目的构建路径当中。至于库依赖声明这里没有用到,它的基本格式是compile project 后面加上要依赖的库名称,比如说有一个库模块的名字叫helper,那么添加这个库的依赖关系只需要加入compile project(':helper') 这句声明即可。另外剩下的一句testCompile 是用于声明测试用例库的,这个我们暂时用不到,先忽略它就可以了。

     1. activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    
        <Button
            android:id="@+id/take_photo"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Take Photo" />
    
        <ImageView
            android:id="@+id/picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal" />
    
    </LinearLayout>

    布局文件中只有两个控件,一个Button和一个ImageView。Button是用于打开摄像头进行拍照的,而ImageView则是用于将拍到的照片显示出来。

    2. AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="top.rqfreefly.cameraalbumtest">
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    
            <provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="com.example.cameraalbumtest.fileprovider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_paths" />
            </provider>
        </application>
    
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    </manifest>

    注册用到的组件,这里引用到一个xml/file_paths资源,需要我们去创建。

    3. file_paths.xml

    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-path name="my_images" path="/" />
    </paths>

     external-path 就是用来指定Uri 共享的,name 属性的值可以随便填,path 属性的值表示共享的具体路径。这里设置空值就表示将整个SD卡进行共享,当然你也可以仅共享我们存放output_image.jpg这张图片的路径。

    4. MainActivity.java

    package top.rqfreefly.cameraalbumtest;
    
    import android.content.Intent;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.net.Uri;
    import android.os.Build;
    import android.provider.MediaStore;
    import androidx.core.content.FileProvider;
    import androidx.appcompat.app.AppCompatActivity;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.ImageView;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    
    public class MainActivity extends AppCompatActivity {
    
        public static final int TAKE_PHOTO = 1;
    
        private ImageView picture;
    
        private Uri imageUri;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Button takePhoto = (Button) findViewById(R.id.take_photo);
            picture = (ImageView) findViewById(R.id.picture);
            takePhoto.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    /**
                     * 创建File对象,用于存储拍照后的图片,并把它存放在手机SD卡的应用关联目录下,这里我们把图片命名为output_image.jpg,
                     * 并将它存放在手机SD卡的应用关联缓存目录下。什么叫作应用关联缓存目录呢?就是指SD卡中专门用于存放当前应用缓存数据的位置,
                     * 调用getExternalCacheDir() 方法可以得到这个目录,具体的路径是/sdcard/Android/data/<package name>/cache。
                     * 那么为什么要使用应用关联缓目录来存放图片呢?因为从Android 6.0系统开始,读写SD卡被列为了危险权限,如果将图片存放在SD卡的任何其他目录,
                     * 都要进行运行时权限处理才行,而使用应用关联目录则可以跳过这一步。
                     */
                    File outputImage = new File(getExternalCacheDir(),
                            "output_image.jpg");
                    try {
                        //如果存在与“output_image.jpg”同名的照片存在就删掉
                        if (outputImage.exists()) {
                            outputImage.delete();
                        }
                        //创建“output_image.jpg”文件
                        outputImage.createNewFile();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    /**
                     * 接着就会进行一个判断,如果运行设备的系统版本低于Android 7.0,就调用Uri的fromFile() 方法将File 对象转换成Uri 对象,
                     * 这个Uri对象标识着output_image.jpg这张图片的本地真实路径。否则,就调用FileProvider的getUriForFile()
                     * 方法将File 对象转换成一个封装过的Uri 对象。getUriForFile() 方法接收3个参数,第一个参数要求传入Context 对象,
                     * 第二个参数可以是任意唯一的字符串,第三个参数则是我们刚刚创建的File 对象。之所以要进行这样一层转换,是因为从Android 7.0系统开始,
                     * 直接使用本地真实路径的Uri被认为是不安全的,会抛出一个FileUriExposedException异常。而FileProvider则是一种特殊的内容提供器,
                     * 它使用了和内容提供器类似的机制来对数据进行保护,可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。
                     *
                     * Context是什么呢?一个Activity就是一个Context,一个Service也是一个Context。Android程序员把“场景”抽象为Context类,
                     * 他们认为用户和操作系统的每一次交互都是一个场景,比如打电话、发短信,这些都是一个有界面的场景,还有一些没有界面的场景,比如后台运行的服务(Service)。
                     * <code>
                     *     public abstract class Context {
                     *
                     *     public static final int MODE_PRIVATE = 0x0000;
                     *     public static final int MODE_WORLD_WRITEABLE = 0x0002;
                     *     public static final int MODE_APPEND = 0x8000;
                     *     public static final int MODE_MULTI_PROCESS = 0x0004;
                     *      ...
                     *   }
                     * </code>
                     */
                    if (Build.VERSION.SDK_INT >= 24) {
                        imageUri = FileProvider.getUriForFile(MainActivity.this,
                                "com.example.cameraalbumtest.fileprovider", outputImage);
                    } else {
                        imageUri = Uri.fromFile(outputImage);
                    }
                    /**
                     * 启动相机程序
                     * 接下来构建出了一个Intent 对象,并将这个Intent 的action 指定为android.media.action.IMAGE_CAPTURE ,再调用Intent 的putExtra() 方法指定图片的输出地址,
                     * 这里填入刚刚得到的Uri对象,最后调用startActivityForResult() 来启动活动。由于我们使用的是一个隐式Intent,系统会找出能够响应这个Intent的活动去启动,
                     * 这样照相机程序就会被打开,拍下的照片将会输出到output_image.jpg中。
                     *
                     * 注意,刚才我们是使用startActivityForResult() 来启动活动的,因此拍完照后会有结果返回到onActivityResult() 方法中。如果发现拍照成功,
                     * 就可以调用BitmapFactory的decodeStream() 方法将output_image.jpg这张照片解析成Bitmap 对象,然后把它设置到ImageView中显示出来。
                     */
                    Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                    startActivityForResult(intent, TAKE_PHOTO);
                }
            });
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            switch (requestCode) {
                case TAKE_PHOTO:
                    if (resultCode == RESULT_OK) {
                        try {
                            // 将拍摄的照片显示出来
                            Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                            picture.setImageBitmap(bitmap);
                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                default:
                    break;
            }
        }
    
    }

    给出两层的继承结构:

    重载(Override)的方法:

     

     官方对AppCompatActivity这个类给出的说明: Base class for activities that wish to use some of the newer platform features on older Android devices。

  • 相关阅读:
    SQL中sum(),avg()等统计结果为null的解决方法 dodo
    DRM内容数据版权加密保护技术学习(上):视频文件打包实现(转) dodo
    如何使用VS2008打开VS2010的解决方案 dodo
    winform程序读取和改写配置文件App.config元素的值 dodo
    Gamification:互联网产品的游戏化设计思路 dodo
    打包发布WinForm应用程序 dodo
    使用HttpHandler做文件过滤器,验证下载文件权限 dodo
    c# cookie使用 dodo
    Balsamiq Mockups 小技巧 dodo
    Snacktools:一套基于Web应用的富媒体编辑器 dodo
  • 原文地址:https://www.cnblogs.com/RQfreefly/p/13768472.html
Copyright © 2011-2022 走看看