安卓笔记-第一行代码版(看书慢,故不打算再抠书,但笔记是心血)

安卓笔记-第一行代码版(看书慢,故不打算再抠书,但笔记是心血),第1张

概述/Users/yangyangzi/Desktop/YangZi2/android/第一行代码学习笔记.rtfd(公司电脑)第一章安卓系统架构四层架构:1、Linux内核层:为安卓设备的各种硬件提供底层驱动,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、wifi驱动、电源管理2、系统运行库层:通过c/c++库为安卓系统提供

/Users/yangyangzi/Desktop/YangZi2/androID/第一行代码学习笔记.rtfd (公司电脑)

第一章

安卓系统架构

四层架构:

1、linux内核层:

为安卓设备的各种硬件提供底层驱动,如显示驱动、音频驱动、照相机驱动、蓝牙驱动、wifi驱动、电源管理

2、系统运行库层:

通过c/c++库为安卓系统提供主要特性支持。如sqlite库提供数据库支持,openGL|ES库提供3D绘图支持,Webkit库提供浏览器内核支持等。另外,安卓运行时库还包含了Dalvik虚拟机,5.0之后改为ART运行环境,它使得每一个安卓应用都能运行在独立进程中,并且拥有自己的Dalvik虚拟机实例。相较于java虚拟机,Dalvik是专门为移动设备定制的,它针对手机内存、cpu性能有限等情况做了优化处理

3、应用框架层:

 提供了构建应用程序用到的各种API,AndroID自带的一些核心应用就是使用这些API完成的

4、应用层:

 所有安装在手机上的应用程序都属于这一层

 

安卓开发特色:

1、四大组件:

1》、活动Activity:

所有AndiroID应用程序的门面,凡是在应用中能看到的东西,都放在活动中

2》、服务Service:

无法看到,只能在后台默默运行,即使用户退出应用,服务任然可以继续运行

3》、广播接收器broadcast Receiver:

允许应用接收来自各处的广播,比如电话、短信;应用同样也可以向外发出广播消息。

4》、内容提供器Content ProvIDer:

使应用程序之间共享数据提供了可能,比如想要读取系统电话簿中的联系人,就需要内容提供器

2、丰富的系统控件:

3、sqlite数据库:

4、强大多媒体:

5、地理位置定位:

 

搭建开发环境:

Mac电脑安装jdk

Mac JDK安装配置,AndroID studio安装及adb配置:https://www.jianshu.com/p/7848dd4f1d67

安装AndroID Studio

第一行代码pdf下载:https://download.csdn.net/download/qq_26973089/10655348

java jdk 下载地址:https://download.csdn.net/download/zxy1211786817/10727768?utm_source=bbsSEO   csdn

java jdk 官网下载:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.HTML

完整卸载AndroID Studio:https://blog.csdn.net/elonspace/article/details/51800949

Mac下AndroID Studio 安装:https://blog.csdn.net/CrazyZhang1990/article/details/52909404

 

搜索:cmd + r

 

AS运行按钮灰色无法点击:

Mac 配置adb环境变量:

1、启动终端 

2、echo $HOME(进入用户home目录) 

3、touch .bash_profile (创建.bash_profile文件)

4、open -e .bash_profile(打开.bash_profile文件)

5、在.bash_profile文件中输入如下内容:

ANDROID_HOME=/Users/yangyangzi/library/AndroID/sdk  (cd到此目录查看下sdk是否是此路径,如果是,创建的文件才可能有效)

export PATH=$PATH:$ANDROID_HOME/tools

export PATH=$PATH:$ANDROID_HOME/platform-tools

6、保存并关闭.bash_profile文件

7、更新配置的环境变量:命令行执行 source .bash_profile

8、在命令行输入 adb 命令,验证配置是否成功,如果出现-bash:command not found 则失败

http://www.cnblogs.com/lijIEjoy/p/9685005.HTML

http://www.cnblogs.com/IamasoldIEr6/p/5026389.HTML

https://blog.csdn.net/u012209506/article/details/52775330

有博客说mac环境不需要配环境,后发现运行按钮灰色,不知咋回事,就把adb环境配了,adb配成功后发现按钮依旧灰色

Mac JDK安装配置,AndroID studio安装及adb配置 https://www.jianshu.com/p/7848dd4f1d67

 

adb环境配置之后,当下验证配置成功,当关掉终端后,再次进入终端,执行adb命令 报 command not found:在/Users/yangyangzi 目录下找到.zshrc 文件,在此文件最后加上#Enable my profile

source ~/.bash_profile

这两行,保存并关闭,后即使关闭终端再打开,不再报 command not found

Mac配置adb环境变量https://blog.csdn.net/lihongxiangleo/article/details/52598233d

分析程序:

将项目的结果模式切换成Project,这是项目的真实目录结构

结构介绍:

1、.gradle 和 .IDea

 是AS自动生成的一些文件,无需关心,不要手动编辑

2、app

项目的代码资源都放在这个目录下,后面的开发也在这个目录下

    app/build 这个目录不要管,包含编译时自动生成的文件

3、gradle 

这个目录包含了gradle wrapper的配置文件,使用gradle wrapper的方式不需要提前将gradle下载好,而是自动根据本地的缓存情况决定是否要联网下载gradle。AS默认没有启用gradle wrapper 方式

5、.gitignore

6、build.gradle

 项目全局的gradle构建脚本,通常这个文件中的内容时不需要修改的

7、.gradle.propertIEs

是项目全局的gradle配置文件,此处配置的属性会影响到项目中所有的gradle编译脚本

8、gradlew和gradlew.bat

二者是在命令行界面执行gradle命令的,其中gradlew是在linux或Mac系统使用的,gradlew.bat是在windows系统中使用的

9、HelloWorld.iml

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

10、local.propertIEs

用于指定本机中的AndroID SDK路径,通常内容都是自动生成的,不需要修改,除非本机中的AndroID SDK位置发生了变化,将此路径改成新的位置即可

11、setting.gradle

用于指定项目中所有引入的模块,由于Hello World项目只有一个app模块,因此该文件中也就只引入了app这一个模块。通常这个模块是自动完成的,几乎不用改

 

除了app文件夹,其他文件几乎不用改

App文件夹结构:

1、build:

 与外层build相似,包含了编译时生成的文件,不过其内容更复杂

2、libs:

 放第三方jar,此路径下的jar包会被自动添加到构建路径去

3、src/androIDTest:

 编写安卓测试用例,可以对项目进行自动化测试

4、src/main/java:

编写代码地方

5、src/main/res:

图片、布局、字符串等资源

    src/main/res/drawable: 图片

    src/main/res/layout: 布局

    src/main/res/values: 字符串

 

6、src/main/res/AndroIDManifest.xml

 整个安卓项目的配置文件,在程序定义的四大组件都需要在这个文件里注册,可以在此文件中给应用程序添加权限声明。经常用

7、src/test

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

8、.ignore

  将app模块内指定的目录或问价排除在版本控制之外

9、app.iml

  IntelJ IDEA自动生成的文件

10、build.gradle

App模块的gradle构建脚本,此文件会指定很多项目构建的相关配置

11、proguard-rules.pro

 指明项目混淆规则

 

src/main/res目录详解:

Drawable:放图片,drawable文件也应该有多个,需要自己创建

Mipmap:放图标,多个mipmap文件为适配不同的机型

Values:放字符串;样式;颜色等配置

layout:放布局文件

 

src/main/res/values/strings.xml文件详解:

 R.string.app_name: 在代码中获得该字符串的引用

 @string.app_name:在XML中获得字符串的引用

其他res文件夹的文件也遵守R及@调用规则

mipmap文件: @mipmap/ic_launcher

                      R.mipmap/ic_launcher

style文件:      @style/Apptheme

                      R.style/Apptheme

 

详解build.gradle文件:

AS通过Gradle来构建项目,Gradle一个非常先进的项目构建工具,它使用了基于Groovy的领域特定语言(DSL)来声明项目设置,摒弃了传统基于XML的各种繁琐配置

 

build.gradle文件详解:

helloworld工程中有两个build.gradle文件:

外层build.gradle文件:

   buildscript {

    

    repositorIEs {

        Google()

        jcenter()//开源库托管仓库

    }

    dependencIEs {//此闭包使用classpath声明了一个Gradle插件

        classpath 'com.androID.tools.build:gradle:3.2.0'

 

    }

}

 

allprojects {

    repositorIEs {

        Google()

        jcenter()//开源库托管仓库

    }

}

ReposorIEs闭包的jcenter():是一个代码托管仓库,很多androID开源项目都会将代码托管到jcenter上,声明这行配置后,可以在项目中轻松引用任何jcenter上的开源项目了。

dependencIEs:声明一个Gradle插件,此插件版本就是AS的版本,因为Gradle不只是用于AS也可以用于java或C++,因此此处要声明为安卓使用

外层build.gradle文件并不需要修改,除非想添加一些全局的项目构建配置

 

内层build.gradle文件:

apply plugin: 'com.androID.application'

 

androID {

    compileSdkVersion 28//项目指定编译版本

    defaultConfig {//

        applicationID “com.example.yangyangzi.helloworld”//指定项目包名,创建时改过包名了,如果想修改在此修改即可

        minSdkVersion 15 //指定最低兼容AndroID系统兼容版本

        targetSdkVersion 28//表示在该版本已经做过充分测试,系统会会应用启用一些最新的功能和特性

        versionCode 1 //项目版本号

        versionname “1.0”//项目版本名

        testInstrumentationRunner “androID.support.test.runner.AndroIDJUnitRunner”

    }

    buildTypes {

        release {

            MinifyEnabled false

            proguardfiles getDefaultProguardfile('proguard-androID.txt'), 'proguard-rules.pro'

        }

    }

}

 

dependencIEs {//指定当前项目所有依赖关系

    implementation filetree(dir: 'libs', include: ['*.jar'])

    implementation 'com.androID.support:appcompat-v7:28.0.0'

    implementation 'com.androID.support.constraint:constraint-layout:1.1.3'

    testImplementation 'junit:junit:4.12'

    androIDTestImplementation 'com.androID.support.test:runner:1.0.2'

    androIDTestImplementation 'com.androID.support.test.espresso:espresso-core:3.0.2'

}

 

分析:

1、第一行:应用了一个插件;

        此处有两种可选值:

             1》com.androID.application 表明这是一个应用程序模块

             2》com.androID.library 表明这是一个库模块

           两种可选值的区别:一个可以直接运行,一个只能作为代码库依附于别的应用程序模块运行

 

2、二行:androID闭包,可以配置项目构建的各种属性

2.1》defaultConfig闭包======

compileSdkVersion:指定项目编译版本,例如24表示使用Android7.0系统的sdk编译。

buildToolsversion:项目构建工具的版本。现版本已经没有这一项了

defaultConfig:对项目的更多细节配置。

versionname与versionCode在生成安装文件的时候非常重要

2.2》buildTypes闭包======指定生成安装文件的相关配置

   通常只有两种配置:deBUG 和 release

DeBUG闭包:指定生成测试版安装文件的配置,另外deBUG版闭包是可以忽略不写的。

Release闭包:指定生成正式版安装文件配置

Release闭包的具体内容:

    MinifyEnabled:指定是否对项目代码进行混淆

    proguardfiles:指定混淆时使用的规则文件,

                          其中proguard-androID.txt 在AndroID SDK目录下,里面是所有项目通用的混淆规则

                                proguard-rules.pro 在当前项目文件根目录下,里边可以编写当前项目特有的混淆规则,通过AS直接运行项目生成的都是测试版安装文件

 

3、dependencIEs:指定当前项目所有依赖关系。

通常AS项目有三种依赖方式:

本地依赖:对本地jar包或目录添加依赖关系

库依赖:对项目中的库模块添加依赖关系

远程依赖:对jcenter库上的开源项目添加依赖关系

(AndroID6.0引入了运行时)

filetree:是一个本地依赖声明:将libs目录下所有.jar文件都添加到项目的构建路径中去。

com.androID.support:appcompat-v7:28.0.0:远程依赖声明

,此处是一个标准远程依赖库格式,com.androID.support是域名,用于和其他公司的库作区分;appcompat-v7 组名称,用于和同一个公司中不同的库做区分;28.0.0是版本号,用于和同一个库的不同版本做区分。加上这句声明后,Gradle在构建项目时会先检查本地是否有此库,没有则联网下载,然后再添加到项目构建路径中

此处暂时没有库依赖声明:库依赖基本格式 compile project + 依赖库名称:例如库名称为helper 则此库依赖声明为:compile project(‘:helper’)

testCompile:用于声明测试用例库

 

1.4日志工具的使用

Log.v():打印琐碎、意义小的日志。 verbose 现在没有log.v 有log.m和log.r

Log.d() 打印调试信息 deBUG

Log.i() 打印比较重要的数据 info

Log.w() 打印警告 warn

Log.e() 打印error error

//Log.r()

//Log.m()

日志级别是向下兼容的

 

快捷键:control + R 运行

创建日志过滤器 打印日志过滤器:例如只打印tag为data的日志

 

第二章 活动

 

1、活动Activity

1》手动创建Activity:右键-》New-》Activity-》Empty Activity 在此页如果

勾选Generate Layout file会自动生成布局文件   

勾选Backwards Compatibility会向下兼容

勾选Launcher Activity会将此活动设置为当前项目的主活动

项目的任何Activity都要重写Activity的onCreate()方法

2》创建布局文件first_layout.xml,在此文件中添加一个button

  PrevIEw布局:VIEw->Tool windows->PrevIEw

3》onCreate方法中的setContentVIEw方法中,一般都会传入一个布局文件的ID

项目中添加任何资源都会在R文件(res)中生成一个相应的资源ID,创建first_layout.xml布局时的ID已经添加到R文件中了

4》在AndroIDManifest.xml文件中注册此Activity,活动的注册声明要放在<application>标签内,通过<Activity>标签来对活动进行注册,AS自动完成了。

5》为程序配置主活动,在AndroIDManifest.xml的Activity标签中加入如下内容:

<intent-filter>

    <action androID:name="androID.intent.action.MAIN"/>

<category androID:name="androID.intent.category.LAUNCHER"/>

</intent-filter>

主活动:点击桌面程序图标时首先打开就是此活动,如果没有主活动,程序依旧可以运行,只是无法在启动器中看到或打开此程序。这种程序一般是作为第三方服务供其他应用在内部进行调用,如支付宝快捷支付服务。

 

 

控件=====

2控件button:

 》2.1、在布局文件声明及初始化 ID;layout_wIDth; layout_height;text

 》2.2、在Activity文件(.java)中调用 

    通过ID获得:button button1 = (button) findVIEwByID(R.ID.button_1);

    直接调用此按钮的点击事件:

    button1.setonClickListener(new VIEw.OnClickListener() {

    @OverrIDe

    public voID onClick(VIEw v) {

 

 

    }

  });

 

3、控件Toast:提示,类似MBProgressHUD

Toast.makeText(FirstActivity.this,"You clicked button 1",Toast.LENGTH_SHORT).show();

 

maketoast方法:

参数一:Context,也就是Toast要求的上下文,活动本身就是一个Context对象,所以直接传FirstActivity.this

参数二:显示的文本内容

参数三:显示时长 有两个内置常量可选 Toast.LENGTH_SHORT和Toast.LENGTH_LONG

 

控件Menu:导航栏右侧的三个点d出的小列表

 》3.1、在新建的menu文件夹下创建新的Menu resource file文件(xml结尾)

在此xml的<menu>标签内声明两个<item/>标签

<item

    androID:ID="@+ID/add_item"

    androID:title="Add"

    />

》3.2、在Activity文件中调用展示menu

在与conCreate方法同级的方法onCreateOptionsMenu中获取并返回menu

@OverrIDe

public boolean onCreateOptionsMenu(Menu menu) {

    getMenuInflater().inflate(R.menu.main,menu);

    return true;

}

获取在main.xml中创建的menu对象:getMenuInflater()

调用menu的inflate(参数一,参数二) 方法:inflate(通过哪个资源文件创建菜单,指明菜单项将添加到哪一个Menu对象当中)

返回值:true 允许创建的菜单显示出来

》3.3、menu的点击响应事件onoptionsItemSelected

与onCreate同级的方法

@OverrIDe

public boolean onoptionsItemSelected(MenuItem item) {

    switch (item.getItemID()){

        case R.ID.add_item:

            Toast.makeText(this,"You click Add",Toast.LENGTH_SHORT).show();

            break;

        case R.ID.remove_item:

            Toast.makeText(this,"click remove item",Toast.LENGTH_SHORT).show();

            break;

         default:

             break;

    }

    return true;

}

 

4、(2.2.6 )销毁一个活动

  1》点击Back键

 2》调用finish()方法

 

5、Intent:使用Intent在Activity之间穿梭

》5.1显式intent:

创建新的Activity,由于不是主活动,所以在AndroIDManifest.xml中注册的时候不用使用<intent-filter>标签

作用:启动活动;启动服务;发送广播;

Intent:显示Intent 隐式Intent

Intent intent = new Intent(FirstActivity.this,SecondActivity.class);

startActivity(intent);

构造函数:参数一 Context:提供启动活动的上下文。参数二:目标活动的Activity

跳转方法:startActivity

 

》5.2隐式Intent:

不明确指明要启动哪个活动,而是指定一系列更为抽象的action和category等信息,然后让系统去分析这个Intent,并找出合适的Intent去启动

只有<action>和<category>中的内容同时能够匹配上Intent指定的action和category时,这个活动才能响应该intent

 》5.2.1、AndroIDManifest.xml的SecondActivity内容中添加<intent-filter>标签

<activity androID:name=".SecondActivity">

    <intent-filter>

        <action androID:name="com.example.activitytest.ACTION_START"/>

//指明当前活动可以响应com.example.activitytest.ACTION_START这个action

        <category androID:name="androID.intent.category.DEFAulT"/>

//精确指明当前活动能响应intent中还可能带有category

    </intent-filter>

</activity>

 》5.2.2、

Intent intent = new Intent("com.example.activitytest.ACTION_START");

startActivity(intent);

构造函数:直接将action字符串传进去

跳转方法:startActivity

 

》5.3.添加自定义category的隐式intent跳转

 》5.3.1、

<activity androID:name=".SecondActivity">

       <intent-filter>

       <action androID:name="com.example.activitytest.ACTION_START"/>

    //<category androID:name="androID.intent.category.DEFAulT"/>

       <category androID:name="com.example.activitytest.MY_category”/>*

    </intent-filter>

</activity>

 

 

 》5.3.2、调用intent的addcategory方法添加一个category,可以指定自定义category com.example.activityTest.MY_category

 

Intent intent = new Intent("com.example.activitytest.ACTION_START");

intent.addcategory("com.example.activitytest.MY_category”);*

startActivity(intent);

 

注:每个intent只能指定一个action,却能指定多个category

 

》5.4.更多隐式Intent的用法

 启动本程序的活动外,还能启动其他程序的活动,使安卓多个应用程序之间功能共享成为可能:例如需要展示网页,不用自己去实现浏览器(也不可能),只需要调用系统浏览器即可(我理解这应该是app间的跳转)

 

跳转系统自带浏览器,并打开百度

button button3 = findVIEwByID(R.ID.button_3);

button3.setonClickListener(new VIEw.OnClickListener() {

    @OverrIDe

    public voID onClick(VIEw v) {

        Intent intent = new Intent(Intent.ACTION_VIEW);

        intent.setData(Uri.parse("http://www.baIDu.com"));

        startActivity(intent);

    }

});

 

setData(参数)方法:Uri对象,指定当前intent正在 *** 作的数据,数据通常以字符串的形式传入到Uri.parse()方法中解析产生的

 

Pp57 p47 明天目标30页 上午10,下午15,晚上5

 

12.6

通过 intent.setData(Uri.parse("http://www.baIDu.com”));方法传参数

与此对应,可以在<intent-filter>标签中,再配一个<data>标签,指定可以响应的数据类型;只有data标签中内容与Intent中data完全一致,当前活动才会响应此Intent,一般data标签不会指定太多内容

之前FirstActivity的按钮三响应跳转到系统浏览器百度页

在新创建了一个Action为androID.intent.action.VIEW data 为http后的活动ThirdActivity,此时点击firstActivity的button_3时,跳转就有了选择,androID.intent.action.VIEW(Intent.ACTION_VIEW)是系统内置动作,ThirdActivity的活动设为此内置动作,相当于重写了此内置动作

 》5.4.1 

   创建ThirdActivity,在布局文件添加控件button_4

 》5.4.2

   在AndroIDManifest.xml中为ThirdActivity添加intent-filter标签,添加活动为系统内置活动androID.intent.action.VIEW,添加data.scheme为http

 

Data标签可以配置如下内容:

<data androID:scheme="http"/>//协议

<data androID:host="www.baIDu.com"/>//域名

<data androID:port="5003"/>//端口号

<data androID:path="主机名和端口之后的部分,一般紧随主机名之后"/>

<data androID:mimeType="指定可以处理的数据类型,允许用通配符方式指定"/>

 

2.3.4向下一个活动传递数据

1、FirstActivity:跳转并传数据

Intent intent = new Intent(FirstActivity.this,SecondActivity.class);

String data = "hello secondactivity";

intent.putExtra("extedata",data);//extedata是键

startActivity(intent);

2、SecondActivity:接受数据

Intent intent = getIntent();

String data = intent.getStringExtra("extedata");

Log.d("secondactivity",data);

 

2.3.5 返回数据给上一个活动

通过按钮返回

1、通过startActivityForResult(intent,请求码)方法启动下一个活动,并设置请求码及intent

button button6 = findVIEwByID(R.ID.button_6);

button6.setonClickListener(new VIEw.OnClickListener() {

    @OverrIDe

    public voID onClick(VIEw v) {

        Intent intent = new Intent(FirstActivity.this,SecondActivity.class);

        startActivityForResult(intent,1);

 

    }

});

2、返回FirstActivity,并返回处理结果,及intent

button button3 = findVIEwByID(R.ID.button_3);

button3.setonClickListener(new VIEw.OnClickListener() {

    @OverrIDe

    public voID onClick(VIEw v) {

        Intent intent = new Intent();

        intent.putExtra("datareturn","hello firstactivity");

        setResult(RESulT_OK,intent);//参一:向上一个活动返回处理结果,一般是RESulT_CANCELEDRESulT_OK 参二:把带有数据的intent传递回去

        finish();

    }

});

3、在onActivityResult 方法中根据请求码和返回码接收返回intent

//    requestCode 启动活动时的请求码; startActivityForResult(intent,1);确定是否是SecondActiviy

//    resultCode  返回数据时传入的处理结果 RESulT_OK RESulT_CANCELED,判断处理结果是否成功

//    data  带着返回数据的intent

    protected voID onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {

        switch (requestCode){

            case 1:

                if (resultCode == RESulT_OK){

                    String returnedData = data.getStringExtra("datareturn");

                    Log.d("FirstActivity",returnedData);

                }

                break;

 

                default:

        }

    }

 

通过返回键返回

2、返回FirstActivity,并返回处理结果,及intent;

将上边的按钮点击事件的步骤2换成在SecondActivity重写onBackpressed方法

//    通过返回键返回FirstActivity,并传值,重新onBackpressed方法

    @OverrIDe

    public voID onBackpressed() {

       Intent intent = new Intent();

       intent.putExtra("datareturn","Hello FirstActivity Back");

       setResult(RESulT_OK,intent);

        finish();

    }

 

2.4 活动的生命周期

 

2.4.1返回栈

安卓使用任务Task管理活动的,一个任务就是一组存放在栈里的活动集合,称作返回栈

 

2.4.2 活动状态

每个活动有4种状态:

1、运行状态

 处于栈顶,活动处于运行状态

2、暂停状态

不处于栈顶,仍然可见,因为不是所有活动都占满一整屏,如对话框,暂停状态的活动仍然完全存活着,除非内存极地的情况,否则系统不会考虑回收这种活动

3、停止状态

活动不在处于栈顶位置,并完全不可见,系统仍然为这种活动保存相应的状态和成员变量,但不一定,当别的地方需要内存时,就被回收了

4、销毁状态

活动从返回栈中移除就变成了销毁状态。系统倾向于回收此种状态的活动的内存

 

2.4.3活动的生存期

7个回调方法

onCreate

 每个活动都要重写此活动,在活动第一次被创建时调用。在此完成活动的初始化 *** 作,比如加载布局、绑定时间等。

onStart

 活动由不可见到可见

onResume

  与用户交互式调用,此时,活动一定位于栈顶,并处于运行状态

onPause

 系统准备去启动或恢复另一个活动时调用。在此释放消耗cpu的资源,及保存一些关键数据,但这个方法的执行速度一定要快,不然那会影响到新的栈顶活动的使用

onStop

 活动完全不可见时调用,与onPause区别是,如果启动的新活动是对话框,onPause会调用而方法onStop方法不会调用

onDestroy

  在活动被销毁前调用,之后活动变为销毁转态。

onRestart

  活动从停止状态变为运行状态之前调用,即活动被重新启动了

 

以上7个方法除onRestart方法,其他都是成对存在,从而又将活动氛围3中生存期:

完整生存期:

onCreate和onDestroy

在onCreate完成初始化 *** 作;onDestroy完成释放内存

可见生存期:

onStart和onStop

在可见生存期,活动对于客户总是可见的,即便有可能无法和用户进行交互,通过这一对可以合理管理对用户可见的资源,在onStart对资源加载,在onStop对资源进行释放,保证处于停止状态的活动不被占用过多内存

前台生存期:

onResume和onPause

在前台生存期活动总是处于运行状态的,此时的活动可以和用户进行交互

注:活动从完全不可见到可见之间差一个onRestart

 

 

2.4.4 体验活动的生命周期 ActivitylifeCycleTest工程

三个活动

1、主活动:

MainActivity  (launchActivity)

 

2、dialog活动:对话框活动

DialogActivity注册代码:

<activity androID:name=".DialogActivity"

    androID:theme="@androID:style/theme.Dialog">

    <!--给当前活动指定主题-->

</activity>

androID:theme属性,指定当前活动的主题,AndroID系统内置很多主题可选择,也可以制定自己的主题,@androID:style/theme.Dialog让使用DialogActivity对话框式主题

 

3、normal活动

 

坑(p57):androID:theme="@style/theme.ActivityDialogStyle”导致崩溃改成androID:style/theme.Dialog

https://blog.csdn.net/chendorID/article/details/53616742

 

三个活动的关系:

主活动main上两按钮:分别推出normal活动 和 dialog活动

当main活动推出normal活动后,由于nrmal活动可以覆盖整个界面,main活动会处于停止状态,会走onPause方法会走onStop方法;

而mian活动推出dialog活动后,dialog活动只能覆盖局部界面,mian活动不会处于停止状态,不会走onPause方法会走onStop方法;

 

*** 作流程 

运行工程-》点击normal按钮进入normal活动-》点返回退到main活动-》点dialog按钮进入dialog活动-》点返回退到mian活动-》回到mian后点击返回

以上流程对应mian活动的执行方法顺序如下

onCreate;onStart;onResume-》onPause;onStop-》onRestart;onStart;onResume-》onPause-》onResume-》onPause;onStop;onDestory

 

2.4.5 活动被回收后

Main活动推出normal活动后,main进入停止状态,如果这个时候内存不足,则系统会回收main活动,但是当按normal的返回按钮时依旧可以正常展示main,此时是不会执行main活动的onRestart方法,而是会执行main活动的onCreate方法,这种情况main活动会重新创建一次。

如果mian活动中有数据,此时返回这些数据就会消失,这样不行。

所以Activity提供了onSaveInstanceState()回调方法,此方法保证活动呗回收前被调用,此方法解决了活动被回收时临时数据得不到保存的问题

onSaveInstanceState()方法的参数是Bundle类型的,

Bundle提供了一系列的方法用于保存数据:

putString():保存字符串类型

putInt():保存int类型

保存到bundle的数据,通过onCreate的Bundle类型参数来取:onCreate(Bundle savedInstanceState),然后将值在赋值给各个控件

一般onCreate的bundle参数为null

1、onSaveInstanceState方法存储数据,此方法在活动被销毁时肯定会调用

public voID onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {

    super.onSaveInstanceState(outState, outPersistentState);

    String tempData = "something you just typed should be saved";

    outState.putString("data_key",tempData);

}

2、在onCreate方法中获取上边保存的bundle数据

protected voID onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentVIEw(R.layout.activity_main);

 

    if (savedInstanceState != null){

        String tempData = savedInstanceState.getString("data_key");

        Log.d("tempDataMainActivity", tempData);

    }

 

注:使用Intent传数据也是类似方法;Intent还可以结合Bundle一起传数据,将数据存到bundle对象,然后再将bundle存在Intent中。到了目标活动后先从Intent中取出Bundle,再从bundle中取出数据

 

2.5 活动的启动模式:通过AndroIDManifIEst.xml中通过<activity>标签指定androID:launchMode属性来选择启动模式:ActivityTest工程

 

2.5.1 standard:

  standard的模式下,每启动一个新的活动,就会被添加到返回栈中,并处于栈顶

系统不在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。

FirstActivity中点按钮跳转FirstActivity。点该按钮三次,产生三个新的实例,要点三次返回才可

三次创建的FirstActivity的onCreate方法打印的 this.toString()值不同(地址值)

FirstActivity: com.example.yangyangzi.activitytest.FirstActivity@2da4e00

FirstActivity: com.example.yangyangzi.activitytest.FirstActivity@982d4b2

FirstActivity: com.example.yangyangzi.activitytest.FirstActivity@ed96c21

说明创建了三个实例

 

2.5.2 singletop:

启动活动时如果发现返回的栈顶已经是该活动,则认为可以直接使用,不用再创建

如果FirstActivity没有在栈顶,这时启动,还是会创建新的实例

》1、修改AndroIDManifest.xml中FirstActivity的launchMode为singletop

》2、FirstActivity的按钮跳到SecondActivity

》3、SecondActivity的按钮跳到FirstActivity

在两个activity的onCreate方法打印this.toString()

singletopsingletop: com.example.yangyangzi.activitytest.FirstActivity@2da4e00

singletopsingletop: com.example.yangyangzi.activitytest.SecondActivity@cdc6ef1

singletopsingletop: com.example.yangyangzi.activitytest.FirstActivity@700ac27

因为跳到secondActivity后FirstActivity不在栈顶,所以会重新创建

 

2.5.3 singleTask:

无论活动是否在栈顶只要存在于返回栈就不用在创建新的活动,每次启动活动时,系统首先会在返回栈中检查是否有此实例,并把这个活动之上的所有活动统统出栈,如果没有就会创建一个新的活动实例

*** 作:

FirstActivity上按钮跳到SecondActivity,SecondA按钮再跳回FirstActivity

》1、FirstActivity的onCreate方法打印:testSingleTasksingletopsingletop: com.example.yangyangzi.activitytest.FirstActivity@2da4e00

》2、SecondActivity的onCreate方法打印:testSingleTasksingletopsingletop: com.example.yangyangzi.activitytest.SecondActivity@cdc6ef1

》3、FirstActivity的onRestart方法打印:testSingleTaskFirstActivity: onRestart

》4、SecondActivity的onDestroy方法打印:testSingleTaskSecondActivity: onDestroy

说明singleTask模式下,点返回,返回到的目标Activity上的活动都会被移除返回栈

 

2.5.4 singleInstance:

与以上三者不同,指定为singleInstance会生成一个新的返回栈来管理这个活动,而且这个返回栈是可以多个程序公用的(实践发现不同的活动的返回栈的taskID不一样)。只要活动声明为singleInstance模式,生成的实例就会加入到这个公共栈中。

例如,A程序中的一个活动M创建的实例m,而B程序也希望用这个实例m,一般情况每个程序都有单独的返回栈,所以,如果有公共的返回栈(此返回栈不唯一)则不同程序可以共用一个活动的实例

 

演示:

》1、将SecondActivity在AndroIDManifest.xml的launchMode换成singleInstance

》2、FirstActivity跳到SecondActivity;SecondActivity跳到ThirdActivity

》3、在三个活动的onCreate方法中打印当前活动的taskID(返回栈的ID)

》4、从ThirdActivity点返回,返回到FirstActivity,再点返回,返回到SecondActivity

三个活动的taskID为:

FirstActivity: Task ID is151

SecondActivity: Task ID is152

ThirdActivity: Task ID is151

在此基础加个FourthActivity,打印taskID,模式也设置成singleInstance

FirstActivity: Task ID is165

SecondActivity: Task ID is166

ThirdActivity: Task ID is165

FourthActivity: Task ID is167

 

分析:

因为singleInstance模式的活动在另一个公共的返回栈中,由third点返回,肯定是同一返回栈的活动,再点返回则出此返回栈,因为还有另一个返回栈,所以到此返回栈,再点返回此返回栈中么有其他活动了,所以直接返回到桌面上了。

打印连接符+:Log.d("FirstActivity", "Task ID is"+getTaskID());

 

2.6 活动的最佳实践

2.6.1 知道在哪个活动中:ActivityTest

为所有活动创建父类BaseActivity,在BaseActivity的onCreate方法中打印log.d(“BaseActivity”,getClass().getSimplename())

》1、创建一个javaClass,不用在AndroIDManifest.xml中注册,即创建一个普通的@R_301_5680@就可以了,并继承自AppCompatActivity,并重写onCreate()方法

》2、让工程中所有Activity都继承自此BaseActivity

 

2.6.2 随时退出程序ActivityTest

1》用一个集合类管理所有活动,销毁所有活动,创建ActivityCollector类不用继承

2》销毁所有活动后,最好把进程也杀掉:androID.os.Press.killPress(androID.os.Process.myPID());

killPress(参数)方法杀掉进程,参数值取进程ID,只能杀死当前进程,不能杀死其他app

正常退出程序需要连续按back键,按home只是把程序挂起,并没有退出程序。

 

2.6.3启动活动的最佳写法:ActivityTest

为活动添加启动方法,尤其是有活动间有数据传输时

 

第三章 UI

 

3.1 如何编写界面UIWidgetTest

3.2 常用控件的使用方法UIWidgetTest

 

今天完成目标 Pp87 p7720页 上午10页 下午10页  Pp107

快捷键:

格式化代码:option+cmd+L 

查源码:cmd+点击 

安装包到手机:adb install -r /Users/yangyangzi/Desktop/Task/AndroID/mobile_app_androID/target/aibank_v1.3.6_2018-12-07_ceshi.apk (-r 当手机上有旧包时,覆盖旧包打新包)

3.2.1 TextVIEw

所有控件都有的属性:

layout_wIDth

layout_height

取值:

match_parent;当前控件的大小和父布局大小一样,由父布局决定当前控件的大小

fill_parent;与match_parent相同,不过基本常用match_parent

wrap_content;当前控件大小能够刚好包含里面内容,有内容决定大小

文字对齐方式:gravity

取值:用|来同时指定多值

center;等同于center_vertical|center_horizontal

top;

bottom;

left;

right

textSize 字体大小单位sp

textcolor

 

3.2.2 Buttton

textAllCaps 按钮上的text如果是英文,默认是大写,此属性为false取消此默认

注册按钮的点击事件

》1、常规方法

//        button.setonClickListener(new VIEw.OnClickListener() {

//            @OverrIDe

//            public voID onClick(VIEw v) {

//

//            }

//        });

》2、实现VIEw.OnClickListener的方式

》2.1  button.setonClickListener(this);

    public voID onClick(VIEw v){//单独注册按钮监听事件

        switch (v.getID()){

                case R.ID.button_1:

                    Log.d("MainActivity", "点击了按钮");

//加逻辑

                break;

                default:

                    break;

        }

 

    }

》2.1 public class MainActivity extends AppCompatActivity implements VIEw.OnClickListener  {}

实现VIEw.OnClickListener

 

3.2.3 EditText:可输入和编辑内容

hint 相当于placeholder

maxlines = 2 超过两行就开始滚动

 

3.2.4 ImageVIEw:

(拖拽图片进drawable,却不可用???)

3.2.5 Progressbar:

控件的三种可见状态:VIEw.GONE(隐藏不占地);VIEw.INVISIBLE(隐藏依旧占地);VIEw.VISIBLE(可见)

圆形:转圈loading

条形:展示进度

等其他形式

3.2.6 AlertDialog d框

Pp101 =》131

AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);

dialog.setTitle("This is Dialog");

dialog.setMessage("Something import");

dialog.setCancelable(false);

dialog.setPositivebutton("OK", new DialogInterface.OnClickListener() {

    @OverrIDe

    public voID onClick(DialogInterface dialog, int which) {

 

    }

 

 

});

dialog.setNegativebutton("Cancel", new DialogInterface.OnClickListener() {

    @OverrIDe

    public voID onClick(DialogInterface dialog, int which) {

 

    }

});

dialog.show();

 

3.2.7 ProgressDialog

与AlertDialog类似,也是对话框,能屏蔽掉其它控件的交互能力,ProgressDialog显示的是进度条,用于表示当前 *** 作比较耗时

ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);

progressDialog.setTitle("this is progressDialog");

progressDialog.setMessage("Loading...");

progressDialog.setCancelable(false);

progressDialog.show();

 

setCancelable(false)方法传的如果是false,则点返回按钮,此d框不消失;需要在适当的地方调dismiss()方法关闭对话框

 

3.3 详解四种布局:UIWidgetTest

布局:一种可以放置很多控件的容器,可以按一定规律调整内部控件位置

3.3.1 线性布局linearLayout  activity_main.xml  activity_first.xml

androID:orIEntation="vertical”竖直方向

androID:orIEntation="horizontal”水平方向

layout_gravity  与 gravity 相似 都是指定对齐方式,前者指定控件在布局中的对齐方式,后者指定文字在控件中的对齐方式;二者的取值差不多,但需注意:当linearLayout的排列方向是horizontal时,只有垂直方向上对齐才会生效;同理,当linearLayout的排列方向是vertical时,只有水平方向的对齐方式才生效

layout_weight 允许使用比例的方式指定控件大小,适配时很关键;当此属性存在时,控件宽度就不应该由layout_wIDth来决定,此处高度设为0dp是一种规范写法,示例中button和EditText属性指数都为1,表示二者在水平方向平分宽度;二者layout_weight值的总和即为比例总份数;如果示例代码中button的代码把layout_weight=1改为layout_wIDth=“wrap_content”后,此时button的宽度按layout_wIDth计算,而EditText占满剩下的屏幕宽度。

dp:指定控件大小、间距等长度属性的单位

 

3.3.2 相对布局relativeLayout  

更加随意,通过相对定位的方式让控件出现在布局的任何位置。

相对于父布局定位:activity_second.xml

左上角

androID:layout_alignParentleft="true"

androID:layout_alignParenttop="true"

右上

androID:layout_alignParenttop="true"

androID:layout_alignParentRight="true"

中间

androID:layout_centerInParent="true"

左下

androID:layout_alignParentleft="true"

androID:layout_alignParentBottom="true"

右下

androID:layout_alignParentRight="true"

androID:layout_alignParentBottom="true"

以上都是相对于父布局进行定位的

 

相对于控件进行定位:activity_third.xml

以下宽高都是wrap_content

button3相对父控件居中:androID:layout_centerInParent="true"

button1相对于button3左上:

androID:layout_above="@ID/button3"

androID:layout_toleftOf="@+ID/button3"

button2相对于button3右上:

androID:layout_above="@ID/button3"

androID:layout_toRightOf="@ID/button3"

button4相对于button3左下:

androID:layout_below="@ID/button3"

androID:layout_toleftOf="@ID/button3"

button5相对于button3右下:

androID:layout_below="@ID/button3"

androID:layout_toRightOf="@ID/button3"

button6与button4的左边缘对齐

androID:layout_alignleft="@ID/button4"

button7与button5的右边缘对齐

androID:layout_alignRight="@ID/button5"

button8与button1的上边缘对齐

androID:layout_aligntop="@ID/button1"

button9与button2的下边缘对齐

androID:layout_alignBottom="@ID/button2"

 

3.3.3 帧布局:FrameLayout 所有的控件默认摆在左上角 FiveActivity.xml

用的比较少

 

3.3.4  百分比布局 PercentFrameLayout 和 PercentrelativeLayout 是对FrameLayout与relativeLayout的扩展

:不同于以上布局,百分比布局被定义在support库中,在build.gradle中添加百分比布局库的依赖即可保证百分比布局在安卓所有系统的兼容性

implementation 'com.androID.support:percent:28.0.0’此库的版本和库implementation 'com.androID.support:appcompat-v7:28.0.0'的版本一致

可以不用wrap_content和match_parent决定控件大小

左上

androID:layout_gravity="left|top"

app:layout_wIDthPercent = "50%"

app:layout_heightPercent ="50%"

右上

androID:layout_gravity="right|top"

app:layout_wIDthPercent = "50%"

app:layout_heightPercent = "50%"

左下

androID:layout_gravity="left|bottom"

app:layout_wIDthPercent = "50%"

app:layout_heightPercent = "50%"

右下

androID:layout_gravity="right|bottom"

app:layout_wIDthPercent = "50%"

app:layout_heightPercent = "50%"

PercentrelativeLayout

继承了所有reletivelayout的所有属性,app:layout_wIDthPercent = "50%”与app:layout_heightPercent = "50%”依旧可用

 

3.4 自定义控件 UICustomVIEws工程

控件和布局的继承结构

                                  VIEw

        TextVIEw           ImageVIEw             VIEwGroup

EditVIEw  button                          linearLayout  relativeLayout …

 

3.4.1 

layout_margin指定控件在上下左右方向偏移的距离

指定控件在某方向的偏移距离

androID:layout_marginleft="3dp"

androID:layout_marginRight="3dp"

androID:layout_margintop="3dp"

androID:layout_marginBottom="3dp"

单独指定控件在某方向上偏移的距离

在activity_main.xml中<include layout="@layout/Title"/>

在mainActivity中隐藏系统自带的导航栏Actionbar

Actionbar actionbar = getSupportActionbar();

if (actionbar != null){

    actionbar.hIDe();

}

 

3.4.2 创建自定义控件

1》创建TitleLayout.java文件并继承自linearLayout

2》重写linearLayout的构造函数,只要在布局中引入了TitleLayout控件就会调用这个函数

public class TitleLayout extends linearLayout {

    public TitleLayout(Context context, AttributeSet attrs){

        super(context,attrs);

        LayoutInflater.from(context).inflate(R.layout.Title,this);

//LayoutInflater.from创建layoutInflater对象;inflate方法动态加载布局文件

    }

}

3》对标题栏布局动态加载LayoutInflater.from(context).inflate(R.layout.Title,this);加载布局文件(Title.xml)的ID;给加载好的布局再添加一个父布局

4》在activity_main中引入布局

<com.example.yangyangzi.uicustomvIEws.TitleLayout

      androID:layout_wIDth="match_parent"

      androID:layout_height="wrap_content"

    />

此效果和上边3.4.1的<include layout="@layout/Title"/>效果一样

注:添加自定义布局和添加普通控件的方式基本一样,不过在添加自定义控件的时候,需要指明控件的完整类名,包名在此不能忽略

5》在TitleLayout.java文件中写Title.xml按钮的点击事件

//  为按钮添加事件

        button tittleback = findVIEwByID(R.ID.tittle_back);

        tittleback.setonClickListener(new OnClickListener() {

            @OverrIDe

            public voID onClick(VIEw v) {

                ((Activity)getContext()).finish();

            }

        });

        button tittleEdit = findVIEwByID(R.ID.tittle_edit);

        tittleEdit.setonClickListener(new OnClickListener() {

            @OverrIDe

            public voID onClick(VIEw v) {

                Toast.makeText(getContext(),"you clicked edit button",Toast.LENGTH_SHORT).show();

            }

        });

3.5 ListVIEw  ListVIEwTest项目

3.5.1 ListVIEw的简单用法

数组的数据无法直接传给ListVIEw,需要借助适配器来完成,安卓有很多适配器,通过泛型指定要适配的数据类型,然后在构造函数中把要适配的数据传入。例如 ArrayAdapter有多个构造函数的重载

ArrayAdapter<String> adapter = new ArrayAdapter<String>(

//             ArrayAdapter构造函数中传入当前上下文;ListvIEw子布局的IDandroID.R.layout.simple_List_item_1ListvIEw子项布局的ID,里边只有一个ListVIEw子项布局的ID,这是一个安卓内置布局文件,里边只有一个TestVIEw);数组数据

                MainActivity.this,androID.R.layout.simple_List_item_1,data);

        ListVIEw ListVIEw = findVIEwByID(R.ID.List_vIEw);

        ListVIEw.setAdapter(adapter);

 

3.5.2 定制ListVIEw界面

1》单独写个fruit_item.xml布局文件

2》创建继承自ArrayAdapter的FruitAdapter

3》重写FruitAdapter的getVIEw方法,并返回布局

3.5.3 提升ListVIEw运行效率

1》判断不让重复加载布局

getVIEw方法中的covertVIEw参数,是对以前旧布局的缓存,对此缓存进行判断

if (convertVIEw == null){

   vIEw = LayoutInflater.from(getContext()).inflate(resourceID,parent,false);

}else {

   vIEw = convertVIEw;

}

2》每次调用getVIEw方法都需要调每个控件的findVIEwByID方法,造成对象实例的多次创建,消耗内存。创建一个VIEwHolder类对item中的控件进行管理

class VIEwHolder{

    ImageVIEw fruitimage;

    TextVIEw  fruitname;

}

在FruitAdapter类里对vIEwHolder进行判断,没有则创建,有则直接调用

if (convertVIEw == null){

   vIEw = LayoutInflater.from(getContext()).inflate(resourceID,parent,false);

   vIEwHolder = new VIEwHolder();

   vIEwHolder.fruitimage = vIEw.findVIEwByID(R.ID.fruit_image);

   vIEwHolder.fruitname = vIEw.findVIEwByID(R.ID.fruit_name);

   vIEw.setTag(vIEwHolder);//vIEwHolder存储在vIEw

}else {

   vIEw = convertVIEw;

   vIEwHolder = (VIEwHolder) vIEw.getTag();//重新获取vIEwHolder

}

vIEwHolder.fruitimage.setimageResource(fruit.getimageID());

vIEwHolder.fruitname.setText(fruit.getname());

新增内容类vIEwHolder,对于控件的实例进行缓存。当convertVIEw为null,创建VIEwHolder对象,并将控件的实例都放在VIEwHolder中,然后用vIEw的setTag()方法将VIEwHolder对象存在VIEw里,当convertVIEw不为null时,调用VIEw的getTag方法,把VIEwHolder取出。这样所有控件的实例都缓存在了VIEwHolder里,不用再通过findVIEwByID()方法获取实例了

优化完毕

 

3.5.4 ListVIEw的点击事件

//  3ListVIEw的点击事件

//     setonItemClickListener()方法为ListVIEw注册监听器,当用户点击了ListVIEw的任何子项时,会调用onItemClick()方法,通过onItemClickposition参数判断出点击的子项

        ListVIEw.setonItemClickListener(new AdapterVIEw.OnItemClickListener() {

            @OverrIDe

            public voID onItemClick(AdapterVIEw<?> parent, VIEw vIEw, int position, long ID) {

                Fruit fruit = fruitList.get(position);

                Toast.makeText(MainActivity.this,fruit.getname(),Toast.LENGTH_SHORT).show();

 

            }

        });

 

3.6 更强大的滚动控件—RecycleVIEw(升级版的RecycleVIEw)

基于ListVIEw需要用技巧提升效率

ListVIEw扩展性不好,只能实现数据纵向滚动效果

3.6.1 RecycleVIEw的基本用法

和百分比布局类似,新增的控件,为了让其在所有安卓手机上都能使用,采取相同的方式,将RecycleVIEw定义在了support库中。需要到app/build.gradle文件中添加相应依赖库

在dependencIEs下加一项implementation 'com.androID.support:recyclervIEw-v7:28.0.0’添加完之后要sync同步

 

因为RecycleVIEw不是内置在系统sdk中的,所以要把完整的包路径写出了。

<androID.support.v7.Widget.RecyclerVIEw

    androID:ID="@+ID/recycler_vIEw"

    androID:layout_wIDth="match_parent"

    androID:layout_height="match_parent"

    />

Pp133

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

总结

以上是内存溢出为你收集整理的安卓笔记-第一行代码版(看书慢,故不打算再抠书,但笔记是心血)全部内容,希望文章能够帮你解决安卓笔记-第一行代码版(看书慢,故不打算再抠书,但笔记是心血)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1111872.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-29
下一篇 2022-05-29

发表评论

登录后才能评论

评论列表(0条)

保存