《第一行代码》读书笔记

本文记录Android 第一行代码(第2版)的读书笔记,同时也会记录学习中的其他非本书知识.基本按照章节分段,方便其他使用

第一章 初识安卓

安卓简介

安卓系统有四大组件: 活动(Activity),服务(Service),广播接收器(Broadcast Receiver),内容提供器(Content Provider)
这四个东西还是挺熟悉的,以前折腾安卓手机,控制唤醒就是在控制应用的这四个东西.

开发环境搭建

参看上一篇搭建时遇到的问题以及解决方案\

AS目录结构

默认结构为AS转换过的,实际目录结构应该是和eclios差不多的,将目录结构切换为Project,这样的目录结构就是项目实际目录了.

大体目录结构

  1. .gradle和.idea
    本目录下是AS自动生成文件,无需关心,不用编辑.
  2. app
    项目的代码,资源几乎都在这里,开发工作基本在这里进行
  3. build
    包含编译时自动生成的文件
  4. gradle
    包含gradle wrapper的配置文件
  5. .gitignore
    这个文件是将指定目录或文件排除在版本控制之外的.
  6. build.gradle
    这个文件是醒目全局的gradle构建脚本
  7. gradle.properties
    全局gradle配置文件
  8. gradlew和gradlew.bat
    用来在命令行界面执行gradle命令
  9. Helloworld.iml
    iml文件是所有intelij IDEA项目都会自动生成的一个文件(Android Studio是基于IntelliJIDEA开发的)用于标识这是一个IntelliJIDEA项目
  10. local.properties
    这个文件用于指定本机中的Android SDK路径 ,自动生成,除非项目开发中sdk发生了更改才需要手动更改
  11. settings.gradle
    这个文件用于指定项目中所有引入的模块.

app下的目录分析

实际上除了app目录外,大多数的文件都是自动生成的,app才是工作的重点

  1. build
    和外层build相似
  2. libs
    项目中的第三方jar包,都在本目录下,本目录下的jar包都会被自动添加到构建路径里.
  3. androidTest
    用于编写android test测试用例,可以对项目做一些自动化测试
  4. main下java
    放置所有java代码的目录
  5. main下res
    所有资源文件放置这里,图片,布局,字符串等资源都放在这里,每种资源放置在不同目录下
    1. drawable开头用来放图片
    2. mipmap开头放应用图标
    3. values开头放字符串,样式,颜色等配置
    4. layout文件夹用来放布局文件
  6. androidmanifes.xml
    整个项目的配置文件,程序中定义的所有四大组建都在本文件内注册,还可以给程序添加权限声明.
  7. test
    用来编写unit test测试用例.
  8. .gitignore
    和外层.gitignore作用类似
  9. app.iml
    IntelliJIDEA自动生成文件
  10. build.gradle
    app模块的gradle构建脚本.
  11. proguard-rules.pro
    指定代码混淆规则

HelloWorld程序简单分析

逻辑和视图分离

先查看java下的mainactivity

1
2
3
4
5
6
7
8
public class MainActivity extends AppCompatActivity {

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

mainActivity这个活动中实际上是没有”HelloWorld”字符的,android程序设计讲究逻辑和视图分离的,因此活动中一般不会直接写界面.
onCreate方法的第二行,使用setContentView给这个活动设置了一个布局activity_main,我们用ctrl进入这个文件可以看到文件位于layout/activity_main.xml文件切换为text视图,可以看
到有

1
android:text="Hello World!"

所以本程序中的文字是在这个文件内定义的.

res下内容引用

打开values/strings.xml

1
2
3
<resources>
<string name="app_name">HelloWorld3</string>
</resources>

这里定义了”app_name”的字符串,而我们再打开androidmanifest.xml

1
2
3
4
5
6
7
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

这里android:label="@string/app_name"对于app名称做了引用.

我们引用这些资源时有两种方式

  • 在代码中通过R.string.name获得该字符串的引用
  • 在XML中通过@string/name获得该字符串的引用

基本语法是上面的这两种形式,其中string是可以被替换的,例如引用图片资源替换为drawable,引用图标变为mupmap,布局替换为layout,以此类推.
lable也是可以替换的,对应后面的string.

详解build.gradle文件

AS采用Gradle来构建项目,build.gradle一个在外层一个在app目录下.

外层

两处repositories闭包中申明了jcenter():这是一个代码托管仓库,声明后就可以在醒目中引用jcenter上的开源项目.
dependencies闭包中用classpath声明了一个Gradle插件,要想使用Gradle构建安卓项目就必须声明这个插件.(Gradle可以支持多种项目构建java,c++等都有专门的插件)
通常不需要修改外层build.gradle,除非需要修改这个,除非像添加一些全局的项目构建配置

app下的

1
apply plugin: 'com.android.application' //第一行

应用了一个插件com.android.application,代表这是一个应用程序模块,
还有一个值可选com.android.library,代表这是一个库模块.
这两个模块区别为,一个可以直接运行,一个只能作为代码库依附于别的应用程序模块运行.
之后的android闭包,可以配置项目构建的各种属性.

compileSdkVersion 指定编译版本,这里28是8.1系统的sdk

android闭包内嵌套了个defaultConfig闭包

applicationID用于指定项目包名,之前创建项目已经指定过包名,如果要修改就在这里修改.
minSdkVersion 用于指定项目最低兼容的android系统版本
targetSdkVersion 指定的值表示已经在该版本上做了充分的测试,系统将会为这个应用启动吸血最新的特性和功能.
versionCode用于指定项目版本号
versionName用于指定醒目的版本名

buildTypes闭包,指定当前项目所有的依赖关系.
通常as项目有三种依赖:

本地依赖,对本地的jar包或者目录添加依赖关系
库依赖,对项目中的库模块添加依赖关系
远程依赖,对jcenter库上的开源项目添加依赖关系.

buildTypes闭包第一行implementation fileTree就是本地声明,他的参数dir: 'libs', include: ['*.jar']将libs下所有.jar后缀的文件都添加到项目的构建路径当中.
第二行是远程依赖声明,参数com.android.support:appcompat-v7:28.0.0-rc02是远程依赖库格式,com.android.support是域名部分,appcompat-v7是组名,28.0.0-rc02是版本号.加入这局声明后,Gradle构建项目时会首先检查本地时候又这个库的缓存,没有就会自动下载.
库依赖声明这里(HelloWorld程序)没有基本格式为,implementation project后面加上要依赖的库名称.

后面的testImplementation是用于声明测试用例的库,忽略

使用日志工具Log

简介

这个日志工具类是Log(android.util.Log),在这个类中提供五个方法打印日志.

  • Log.v() 用于打印琐碎的日志.对应级别为verbose,是级别最低的一种
  • Log.d() 打印调试信息.对应级别debug.比verbose高一级
  • Log.i() 打印比较重要的数据,这些是可以分析用户行为的数据.对应级别info,比debug高一级.
  • Log.w() 打印警告信息,提示程序在这个地方可能会存在风险,最好去修复下警告出现的地方.对应级别warn,比info高一级
  • Log.e() 用于打印程序错误信息,当有错误信息打印时代表程序出现严重问题必须修复.对应级别error比warn高一级.

总共五个方法可以被重载.

习惯使用Log

Log可以发挥和System.out相同的作用,而且拥有分级,过滤器….很多优点.
Log可以使用快捷输入:如Log.d就可以使用logd然后按下TAB.
另外,由于Log的所有打印方法都要求传入一个tag参数,我们可以在onCreate()方法外部输入一个logt,按下TAB,就会以当前的类名自动生成一个TAG常量,之后使用快捷方法生产Log()就不用再输入tag参数了.

Logcat中的过滤器

在logcat工具中可以添加过滤器

  • show only selected aplication 只显示当前选中程序的日志(默认)
  • Firebase 谷歌提供的分析工具
  • No Filters 没有过滤器
  • Edit Filter Configuration 自定义过滤器

自定义过滤器中Fiter Name是过滤器的名称,Log Tag是过滤器tag的值,例如我们Log Tag的值为data那么我们选定这个自定义过滤器后logcat就只会显示tag值为data的日志.后面的Log Message就对应的msg参数….之后都是同理

Logcat中的日志级别

就是上文说的五个方法作为五个级别
当选中级别为verbose(最低等级),这意味这不管是用什么方法打印日志一定会显示,如使用Log.d()也会显示打印;当选中debug级别时,只显示debug及以上的日志,例如使用Log.v就不会显示打印.

关键字过滤

Logcat中间的搜索框就是关键字过滤,全日制过滤你想要的关键字,支持正则表达式.

第二章 活动

活动的基本用法

手动创建活动

右击Project下java下的com.example.activitytest包→new→activity-Empty Activity,弹出创建活动的对话,名称我们创建为FirstActivity
会话中Generate Layout File代表勾选后自动为这个活动创建一个对应的布局文件.
Launcher Activity代表勾选后会将这个活动设置为主活动.
为了更好的学习活动,这两个都不勾选.
Backwards Compatibility勾选后表示会为项目启用向下兼容模式,勾选.
点击Finish完成创建.

这是一个几乎空的活动,但是里面仍然有onCreate()方法,因为任何活动中都应该重写这个方法,所以AS自动帮我们完成了.

1
2
3
4
@Override  
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}

小知识点:@Override 这个声明告诉编译器下面的方法是重写而不是一个新的方法,如果我们声明了一个洗的方法,编译器就会报错.
可以看到这个方法非常简单.

创建和加载布局

android设计讲究逻辑视图分离,最好每一个活动对应一个布局,布局用来显示界面内容

创建布局

右击app/src/main/res目录-New-Directory,弹出新建目录窗口,这里创建一个layout目录,对layout右键-new-Layout resource file,出现新建布局资源文件的窗口.命名为first_layout,root element(根元素)默认选择LinearLayout.
点击ok就进入了布局编辑器(Design/Text)默认是Design可视化视图,点击text切换到文本视图.并且创建一个按钮

1
2
3
4
5
6
7
8
9
10
11
<?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/button_1" //定义元素唯一标识符
android:text="Button 1" //指定元素显示内容
android:layout_width="match_parent" //指定元素宽度,和父元素一样宽
android:layout_height="wrap_content" //指定元素高度,和内容一样高
/>
</LinearLayout> //刚才选择的LinearLayout作为根元素

android:id是定义当前元素的标识符,它的参数@+id/button_1
上文说到@string/name是在xml中引用string资源的语法
这里@+id/name是定义id的语法

我们可以看到右侧的preview已经显示按钮了

活动中加载布局

回到FirstActivity,在onCreate()方法中加入代码

1
2
3
4
5
6
7
8
public class FirstActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout); //加入的代码
}
}

这里调用了setContentView()方法,R.layout.first_layout是一个在代码中对资源的引用.
项目中添加任何资源都会在R文件中生成一个相应的资源id,使用R.layout.first_layout引用就会得到一个first_layout.xml布局的id,这个id传入了setContentView()

注册活动

所有活动要在AndroidManifest.xml中注册才生效,我们使用右键new活动的时候,这个活动已经被AS注册了,如果手动创建一个活动就需要自己去注册.
打开AndroidManifest.xml查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.elsewhere997.activitytest">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".FirstActivity"></activity> //注册的活动
</application>

</manifest>

活动注册声明要放在application标签内,活动通过activity标签注册,其中android:name用来指定注册哪个活动,后面的.FirstActivitycom.example.activetytest.FirstActivity的缩写,因为这个xml文档的最外层标签已经有了这样的代码package="com.example.elsewhere997.activitytest这通过package属性指定了程序包名com.example.elsewhere997.activitytest所以前半部分可以不写,直接写缩写了.

主活动

主活动是程序运行时最先启动的活动,程序必须有主活动才能运行,所以必须配置一个主活动.
<activity>标签内部加入一个<intent-filter>标签并在这个标签的内部添加<action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" />这两句声明.
此外在<activity>标签头内还可以加入android:label用来指定活动中标题栏的内容,这个内容还会成为启动器中程序显示的名称.

活动中的Toast

定义一个Toast触发点,直接使用FirstActivity这个活动的按钮作为出发点,点击后弹出Toast.在onCreate()中添加如下代码(自己导入相关类):

1
2
3
4
5
6
7
Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Toast.makeText(FirstActivity.this, "You clicked Button 1", Toast.LENGTH_LONG).show();
}
});

活动中通过findViewById()获取布局中定义的元素返回一个对象,R.id.button_1我们已经很熟悉了,其中button_1是我们再first_lay_out.xml中通过android:id定义了的.
这个方法返回一个view对象,我们通过(Button)向下强转为button对象来得到按钮的实例,然后 通过声明的button1来调用setOnClickListener()方法为按钮注册一个监听器.
点击按钮就会执行OnClickListener()监听器中的Onclick()方法
Tosat方法,通过静态方法makeText()创建Toast对象,其中三个参数,第一个是上下文(Context),活动就是一个上下文(Context)对象,直接传入FirstActivity.this;第二个是文本内容;第三个是显示时常.然后调用show()显示

活动中的Menu

创建布局

在res目录下新建一个menu文件夹,再在menu下新建一个名为main菜单文件(Menu resource file),创建出了一个main.xml
<menu>标签加入如下代码:

1
2
3
4
5
6
<item
android:id="@+id/add_item"
android:title="Add" />
<item
android:id="@id/remove_item"
android:title="Remove" />

这里我们创建了两个菜单项,<item>标签用来创建菜单项,其中,我们通过android:id来指定这个菜单项的唯一标识符,通过android:title给菜单项指定一个名称.

显示menu

接着在活动中重写onCreateOptionsMenu方法

1
2
3
4
5
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

通过getMenuInflater()获得MenuInflater对象,调用它的inflate()方法创建当前活动的菜单,inflate()有两个参数,一个指定通过哪个资源文件创建菜单,这个参数值的形式我们已经很熟悉了,另一个是指定菜单添加到哪个Menu对象中,这个值直接由外部的onCreateOptionsMenu(Menu menu)方法传入所以我们直接填入menu
onCreateOptionsMenu(Menu menu)方法返回值为布尔型.返回ture代表菜单允许显示,false表示创建的菜单无法显示.

添加菜单响应事件

依然是活动中重写方法,重写onOptionsItemSelected()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.add_item:
Toast.makeText(this,"You clicked add", Toast.LENGTH_LONG).show();
break;
case R.id.remove_item:
Toast.makeText(this, "clicked remove", Toast.LENGTH_LONG).show();
break;
default:
}
return true;
}

通过调用item.getItemId()判断点击的是哪一个菜单项(传入了一个菜单项的id),然后在下面的case中判断,执行case下的语句

销毁活动

在安卓设备中可以通过back键销毁当前活动.
通过代码的方式:
调用活动类的finish()方法,销毁当前活动.
修改按钮监听器中的代码,把Toast显示代码改成销毁活动的方法,这个按钮就和back按键的功能相同了

活动间的跳转

创建一个新的活动SecondActivity这次创建时勾选Generate Layout File系统会自动创建一个名为activity_second.xml的布局文件,布局文件过于复杂我们修改其中代码,使用之前的<LinearLayout>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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/button_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 2"
/>

</LinearLayout>

上述代码中还定义了一个按钮id为button_2.
我们回到活动去加载这个布局,但是惊奇的发现!这个布局文件已经被AS在活动中加载了.
我们同样没有忘记在AndroidManifest.xml中注册活动,又惊奇的发现后动已经被注册好了.

使用显示Intent

Intent是安卓程序中各个组件之间进行交互的重要方式,它致命当前组件想要执行的动作,还可以在不同组件之间传递数据.一般可用于启动活动,启动服务,发送广播.这里学习启动活动.
Intent分为显示和隐式,这里学习显示
code1

首先构建出了一个Intent传入了FirstActivity.this作为上下文,传入SecondActivity.class作为目标活动,这个Intent的意思就是在FirstActivity这个活动的基础上打开目标活动.
然后把intent传入startActivity()方法,执行Intent启动活动
这时运行程序,点击firstactivity的按钮就打开了第二个活动.

隐式Intent

隐式活动不明确指出要启动哪一个活动,而是指定了一些列抽象的actioncategory等信息,然后由系统去分析这个Intent,并帮我们找出适合的活动去启动.

通过在<activity>标签下配置<intent-filter>的内容,可以指定当前活动能响应的action和categroy.
我们再AndroidManifest.xml的SecondActivity的注册activity标签中添加如下代码

1
2
3
4
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

<action>标签中我们指明了当前活动可以响应ACTION_START这个action,而<category>包含一些附加信息,更精确的指明了当前的活动能够响应的Intent中还可能带有的category.只有<action><category>中的内容能够同时匹配上Intent中指定的action和category时,这个活动才能响应该Inent.

现在我们为Intent添加匹配条件,其他部分和显示是相同的
代码如下:

1
2
3
4
public void onClick(View v){
Intent intent= new Intent("com.example.activitytest.ACTION_START");
startActivity(intent);
}

我们使用了Intent的另一个构造函数Intent(action),直接把action的字符串"com.example.activitytest.ACTION_START"传了进去,表明这个Intent能够响应"com.example.activitytest.ACTION_START"这个action的活动.
action和category同时匹配才能响应,但是这个Intent没有指明category.但是前面SecondActivity注册的<action>标签中指明了category的信息"android.intent.category.DEFAULT"这是一种默认的category,在调用startActivity()方法时这个category会自动添加到Intent中.

每个Intent中只能指定一个action,但能够指定多个category
现在添加一个category
修改FirstActivity中的点击事件

1
2
3
4
5
public void onClick(View v){
Intent intent= new Intent("com.example.activitytest.ACTION_START");
intent.addCategory("com.example.activitytest.MY_CATEGORY");
startActivity(intent);
}

添加了一个,调用intent中的addCategory()方法来添加一个category,值为"com.example.activitytest.MY_CATEGORY"

直接运行程序,点击主活动的按钮,程序闪退了,Logcat也报错了.intent匹配不到对应的活动当然会出问题,但是是什么问题了,现在看看Logcat

1
2
Process: com.example.elsewhere997.activitytest, PID: 24662
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.example.activitytest.ACTION_START cat=[com.example.activitytest.MY_CATEGORY] }

上述错误信息意思是没有一个活动可以响应我们的Intent,和我们的推测一致.

修复bug,我们再添加一个可以响应category的声明就好了,回到AndroidManifest,为SecondActivity添加一个.

1
2
3
4
5
<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>

再次运行点击按钮,成功了

只要intent里的要求目标活动都能达到那么就能调用这个活动.所以在<intent-filter>添加几个其他的category一样可以运行.

更多隐式Intent用法

使用隐式Intent我们还能够启动其他程序的活动.
修改FirstActivity中按钮点击事件的代码

1
2
3
4
5
6
@Override
public void onClick(View v){
Intent intent= new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}

这里首先指定了Intent的action为Intent.ACTION_VIEW这是一个安卓系统内置的动作,其常量值为android.Intent.action.VIEW.
然后通过Uri.parse()方法,把一个网址字符串解析为Uri对象再调用Intent的setData()方法把Uri对象传递进去.
这个setData()没有讲过,它接收一个Uri对象,用于指定当前Intent正在操作的数据,这些数据通常以字符串的形式传入到Uri.parse()方法中解析为Uri对象.
与此对应的我们还可以在<intent-filter>标签中再配置一个<data>标签用来指定当前活动能够响应什么类型的数据.<data>标签主要可以配置以下内容.
intent1

除了http协议外,还能指定许多其他协议,比如geo表示地理位置,tel表示电话….

下面展示如何调用系统拨号界面.
修改按钮点击事件:

1
2
3
4
5
6
@Override
public void onClick(View v){
Intent intent= new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10000"));
startActivity(intent);
}

首先指定Intent的action为Intent.ACTION_DIAL.这也是一个android内置action,然后在data指定了协议是tel
启动,点击按钮,成功了.

向下个活动传递数据

使用Intent
Intent中提供了putExtra()方法的重载,可以把想要的数据暂存在Intent中,启动另一个活动后把数据从Intent中取出.
例如FirstActivity中的字符串传递到SecondActivity中:

1
2
3
4
5
6
7
@Override
public void onClick(View v){
String data="Hello SecondActivity";
Intent intent= new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);
startActivity(intent);
}

这里我们使用了显示Intent启动SecondActivity,通过putExtra()方法传递了一个字符串,它的第一个参数是键,用于后面Intent中取值,第二个参数是值,真正要传递的数据.

然后在SecondActivity中将数据取并打印:

1
2
3
4
5
6
7
8
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Intent intent= getIntent();
String data= intent.getStringExtra("extra_data");
Log.d("SecondActivity",data);
}

通过getIntent()方法获取到了用于启动SecondActivity的Intent,然后调用intent的getStringExtra()方法,传入相应的键值,得到传递的数据,赋值给我们声明的data.
然后使用Log打印,d是debug级.

运行,点击活动的按钮,产看Logcat我们传入的字符串打印出来了.

返回数据给上一个活动

我们之前启动活动使用的方法startActivity(),Activity中还有一个startActivityForResult()方法也是用于启动活动的,这个方法期望在活动销毁时能够返回一个结果给上个活动.
startActivityForResult()方法接收两个参数,一个Intent,另一个是请求码,用于在之后的回调中判断数据的来源.
修改FirstActivity中的按钮点击事件:

1
2
3
4
5
6
7
8
            @Override
public void onClick(View v){
String data="Hello SecondActivity";
Intent intent= new Intent(FirstActivity.this,SecondActivity.class);
// intent.putExtra("extra_data",data);
// startActivity(intent);
startActivityForResult(intent,1);
}

这里请求码只要是唯一的就可以了,我们随便传入一个数字1,接下来在SecondActivity中给按钮注册点击事件,并在点击事件中添加返回数据的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Button button2=(Button) findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent =new Intent();
intent.putExtra("data_return","Hello FirstActivity");
setResult(RESULT_OK,intent);
finish();
}
});

// String data= intent.getStringExtra("extra_data");
// Log.d("SecondActivity",data);
}

我们还是构建了一个Intent用于传递数据,但这个Intent没有任何”意图”.通过putExtra()方法把数据存在Intent中.然后调用setResult()方法,这个方法是专门用于向上一个活动返回数据的,它接收两个参数,第一个用于向上个活动返回处理结果,一般只使用RESULT_OKRESULT_CANCELED两个值,第二个参数把带有数据的intent传递回去.
最后用finish()方法销毁当前活动.
由于使用的startActivityForResult()方法来启动这个活动,销毁这个活动后会回调上一个活动的,onActivityResult()方法,因此我们重写FirstActivity中的这个方法来取得返回数据:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case 1:
if (resultCode==RESULT_OK){
String returnedData=data.getStringExtra("data_return");
Log.d("FirstActivity", "returnedData");
}
break;
default:
}
}

onActivityResult()有三个参数,requestCode启动活动传入的请求码;resultCode返回数据时传入的处理结果;data携带着返回数据的Intent.

此时我们点击button2按钮就会返回数据并打印,但是如果我们直接按返回键就不会反悔数据,这时我们重写onBackPress()方法,它返回后执行的过程和点击按钮相同,所以方法体的内容应该和onClick()相同:

1
2
3
4
5
6
7
@Override
public void onBackPressed() {
Intent intent =new Intent();
intent.putExtra("data_return","Hello FirstActivity");
setResult(RESULT_OK,intent);
finish();
}

值得注意的是这里重写了onBackPress()方法,也就是点击返回按钮时执行的方法,如果方法体里不加上finish()那么点击返回按钮是不会返回的.

活动的生命周期

返回栈

从之前的例子中可以看出,android的活动是可以层叠的,每启动一个新活动就会覆盖再原活动上.销毁上面的活动,之前的活动就会重新显示出来.

android使用任务(task)来管理活动.
一个任务就是一组存放在栈里的活动集合,这个栈称为返回栈(Back Stack).新活动处于栈顶,销毁新活动(出栈)前一个活动就出现了,系统总是显示处于栈顶的活动给用户.

活动状态

  • 运行状态
  • 暂停状态 活动不处于栈顶,但仍然可见时
  • 停止状态 活动不处于栈顶,且完全不可见时.
  • 销毁状态 依然占用内存知道系统回收它(最倾向被回收的状态)

生存周期

活动的七个方法与三个阶段:

  1. onCreate() 每个活动都会有的方法,在活动第一次被创建的时候调用

  2. OnStart() 这个活动在活动由不可见变为可见的时候调用

  3. OnResume() 这个方法在活动准备好和用户进行交互的时候调用,此时的活动一定位于返回栈的栈顶,并且处于运行状态。

  4. onPause() 这个方法在系统准备去启动或者恢复另一个活动的时候调用。 我们通常会在这个方法中将一些消耗 CPU 的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。

  5. onStop() 这个方法在活动完全不可见的时候调用。它和 onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么 onPause()方法会得到执行,而 onStop()方法并不会执行。

  6. onDestroy()这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。

  7. onRestart() 这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。

以上七个方法中除了 onRestart()方法,其他都是两两相对的,从而又可以将活动分为三种生存期。

三个阶段:

  1. 开始Activity:在这个阶段依次执行3个生命周期的方法,分别是:onCreate、onStart和onResume方法

  2. Activity重新获得焦点:如果Activity重新获得焦点,会依次执行3个方法,onRestart、onStart和onResume

  3. 关闭Activity:当Activity被关闭时系统会依次执行3个方法,onPause、onStop和onDestory。

活动周期

生存周期体验

页码:P67 为了提高看书效率从这里开始,这样的实际操练就简略记录

设置为对话框(Dialog)活动

进入AndroidManifest.xml对要注册为Dialog的活动的<activity>标签头加入代码android:theme:

1
2
3
4
<activity android:name=".DialogActivity"
android:theme="@android:style/Theme.Dialog">

</activity>

android:theme用于指定当前活动主题,我们使用的内置主题.这里出现了问题 参看:打开Dialog活动

活动回收前保存参数

停止状态的活动有可能被回收
onSaveInstanceState()方法可以保证在活动回收前一定会调用,通过这个方法来解决活动被回收时临时数据没有保存的问题.
onSaveInstanceState()方法会携带一个Bundle类型参数,Bundle提供了一些列方法保存数据.例如putString()用来保存字符串,这个方法第一个参数为键,第二个参数才是数据.
onCreate()方法中也有个Bundle类型参数,默认为null当我们用onSaveInstanceState()方法保存过数据就会把这个Bundle传递进来,使用Bundle响应的方法取出数据即可.

活动的启动模式

四种启动模式: standard, singleTop, singleTask, 在AndroidManifest.xml中通过<activity>标签指定android:launchMode属性来选择启动模式

standard(默认)

standard,标准启动模式,是活动默认启动模式.
使用standard模式的活动,系统不会在乎活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新实例.

standard

singleTop

启动活动时如果发现返回栈的栈顶已经是这个活动,则认为可以直接使用它,不会再创建新的活动实例.
singleTop

singleTask

每次启动活动系统首先在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用这个实例,并把在这个活动之上的所有活动全部出栈,如果没有发现就会创建这个活动的实例.
singleTask

singleInstance

这个模式的活动会启用一个新的返回栈来管理这个活动.
返回时,无论上个活动是谁,只返回当前活动所在的返回栈,返回栈为空后才返回上个调用这个活动的返回栈.
singleInstance

活动实践

知晓当前是哪一个活动

P81

随时退出活动

p82

启动活动的最佳写法

p84

第三章 UI开发

常用控件的使用

TextView

android:layout_width:layout_height指定了控件的宽度和高度,安卓所有控件都有着两个属性.
可选值:

  • match_parent 当前控件大小和父布局一样(官方推荐)
  • fill_parent 当前控件大小和父布局一样
  • wrap_content 当前控件大小刚好包住内容

除了这样也可以指定宽高为固定大小,但是在屏幕适配上会出问题.

使用android:gravity指定当前控件文字对其方式,可以用|来指定多个值
例如: center值的意义就和center_vertical|center_horizontal相同

使用android:textSize控制文字大小,单位是sp
android:textColor控制文字颜色

Button

侦听器的另一种使用方式
前面的学习中已经熟练的使用了匿名内部类的方式注册监听器,下面是实现接口的方式来注册,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ //此处实现了一个OnclickListener接口,这个活动具有了监听器类的特性,"单继承,但可以多实现"

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button=(Button) findViewById(R.id.button);
button.setOnClickListener(this); //本类现在属于监听器,所以可以传入this
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.button:
// 此处添加逻辑
break;
default:
break;
}
}
}

EditText

允许用户在控件里输入和编辑内容.
属性android:hint实现显示提示性文本,输入文字文本消失.
android:maxLines制定它的最大行数,超过行数后组件不再继续向下拉伸

ImageView

图片控件,图片通常放在drawable开头的目录下
android:src="@drawable/img_1"/>属性指定图片,后面是对图片资源的引用

在活动中还可以动态覆盖xml中设置好的图片.使用ImageView的setImageResource()方法.

ProgressBar(进度条)

控件的可见属性:android:visibility在布局文件中设置
可选值:

  • visible,默认的,可见的
  • invisible,控件透明,依然占据位置
  • gone,不可见且不占用空间

也可以在活动中,通过setVisibility()方法传入类似View.VISIBLE的值.

另外可以在布局文件中通过style属性给它指定进度条样式.
通过android:max属性指定进度条的最大值
在活动中通过setProgerss设置进度.

AlertDialog

弹出对话框,且对话框置于最上方,屏蔽其他控件的交互能力,用于提示重要内容或者警告.
在活动中通过AlertDialog.Builder创建一个AlertDialog实例,用这个实例的方法设置镖旗内容是否可取消等属性,setPositiveButton()方法设置确定按钮点击事件,setNegativeButton()按钮设置取消点击事件.最后使用show()方法显示出来

ProgressDialog

同AlertDialog相似,但是会显示一个进度条,表示后台忙请等待.

四种基本布局

p105

线性布局

LinearLayout,在线性方向上依次排列排列.
android:orientation属性指定排列方向vertical是竖直方向,horizontal是水平方向(默认).

android:layout_gravity属性指定控件在布局中的对其方式.

android:layout_weight允许使用比例的方式指定控件大小

相对布局

RelativeLayout
android:layout_alignParentXXX相对父的xxx位置可以是上下左右.
android:layout_centerInParent="true"相对于父的中间.
上面的属性需要布尔值

android:layout_above`android:layout_below指定当前控件位于另一个控件的上/下方android:layout_toRightOfandroid:layout_toLeftOf`指定当前控件位于另一个控件的左/右方. `android:layout_alignLeftandroid:layout_alignRightandroid:layout_alignTopandroid:layout_alignBottom`当前控件边缘和另一个控件左/右/上/下边缘对齐
上面的属性需要引用相对控件的id

帧布局

FrameLayout,所有控件默认摆放左上角,控件默认会按照先后顺序叠加.
但是可以使用android:layout_gravity指定对其方式

百分比布局

只有LinearLayout(线性)布局支持使用layout_weight属性来实现按比例指定控件大小的功能.其他两种布局都不支持

android引入了新的布局方式实现百分比布局
PercentFrameLayout`PercentRelativeLayout`

PercentFrameLayou:
为了让老版本android支持新的布局,在项目的build.gradle中添加百分比布局库的依赖,就能保证百分比布局的兼容性
打开App/build.gradle文件,在dependencies闭包中添加

1
compile 'com.android.support:percent:对应版本号'

修改布局文件<android.support.percent.PercentFrameLayou>t因为不是内置sdk必须把完整的包路径写下来
在布局文件头加入
xmlns:app="http://schemas.android.com/apk/res-auto"这样一个命名空间,这样才能使用百分百布局的自定义属性.
使用app:layout_widthPercent属性将各个按钮的宽度指定为布局的50%,高度同理,这里能够使用app前缀的属性是因为我们刚才定义了app的命名空间,当然我们一直能够使用android前缀的属性也是同理.
PercentFrameLayou会继承FrameLayout的特效,控件默认左上角,使用layout_gravity属性让四个按钮不重叠

PercentRelativeLayout:
同理

创建自定义控件

先来看看常用控件和布局的继承结构
View1
所有控件直接简介继承View,View是安卓中最基本UI组件,可在屏幕回执矩形区域,并能响应这个区域的各种事件.ViewGroup是一种特殊的View包含很多子ViewGroup和子View,是一种容器.
利用这种继承关系可以自定义控件.

引入布局

创建布局文件在另一个布局文件使用类似代码

1
<include layout="@layout/title" />

引入这个布局文件

下面是隐藏自带标题栏代码

1
2
3
4
ActionBar actionbar=getSupportActionBar(); //调用actionbar实例
if(actionbar!=null){
actionbar.hide(); //影藏actionbar
}

创建自定义控件

p122
新建TitleLayout继承自LinearLayout,让它成为我们自定义的标题栏控件.

非activity类中调用startActivity(intent)

1
startActivity(intent);  //改为getContext().startActivity(intent); //这个

非活动类调用finish();

1
((Activity)getContext()).finish(); //getcontext取得context,然后强转为活动类,外面加一个括号区分顺序,实际就是一个 Activity.finish()类似this.finish();

常用&难用控件 ListView

简单用法

p124

定制ListView界面

点击事件

p131

RecyclerView

同ListView一样,但是更强大,官方推荐

基本用法

新增控件,在support库中,首相在build.gradle中添加依赖才能使用.

1
implementation 'com.android.support:recyclerview-v7:28.0.0-rc02'

由于不是系统内置SDK使用时需要写出完整包名<android.support.v7.widget.RecyclerView>

横向滚动和瀑布流布局

P137

RecyclerView点击事件

p139

编写界面实践

P142

制作Nine_Pach图片

第一行代码中的方式不适用,工具已经集成到了AS中,对着导入的png图片右键,点击 Creat-9-patch file .

第四章 碎片Fragment

Fragment是什么

是一种可以嵌入在活动中的UI片段

Fragment的使用方式

Fragment的简单用法

建议使用support-4库的类不使用系统内置的,兼容差.
build.gradle已经注册了库的依赖我们不需要手动注册.

LeftFragment这个自己创建的控件加入主控件的时候需要使用android:name属性指定LeftFragment的包.

动态添加碎片

p155
添加碎片的方法内部包含步骤:

  1. 创建待添加碎片实例
  2. 获取FragmentManager,在活动中通过调用getSupportFragmentManager()方法得到
  3. 开启一个事物,通过fragmentManager调用buginTransaction()开启
  4. 使用开启的事物调用replace()方法实现,参数为需要传入容器的id和待添加的碎片实例.
  5. 使用事物调用commti()提交事物.

碎片中模拟返回栈(BackStack)

活动中动态添加碎片后,按返回键会直接退出当前活动,如果要只退出碎片,需要模拟返回栈.
使用FragmentTransaction的addToBackStack()方法,将事物添加到一个返回栈中,参数是一个名字用于描述返回栈状态,一般为null就可以.

碎片和活动通信

在活动中使用FragmentManager的findFragmentById()方法获取碎片实例,类似活动中findViewById()获取控件实例的方法.

1
RightFragment rightFragment = (RightFragment) getFragmentManager().findFragmentById(R.id.right_fragment);

得到实例后就能轻松使用实例的方法了.

在碎片中使用活动的getActivity()方法获得和当前碎片关联的活动实例:

1
MainActivity activity =(MainActivity)getActivity();

活动中需要使用Context对象时也可以使用这个方法,活动本身就是一个context.

碎片和碎片之间的通信就通过上面两个方法的组合实现.

碎片的生命周期

碎片的状态和回调

状态:

  1. 运行状态: 碎片可见且关联的活动处于运行状态
  2. 暂停状态: 当一个活动进入暂停状态,关联的碎片就进入到暂停状态
  3. 停止状态: 当活动进入停止状态,与之关联的碎片进入停止状态.或者通过调用FragmentTransaction的remove(),replace()方法将碎片从活动中移除且调用addToBackStack()方法.
  4. 销毁状态: 活动销毁时相关的碎片进入销毁,通过调用FragmentTransaction的remove(),replace()方法将碎片从活动中移除且没有调用addToBackStack()方法.

回调:
活动中有得回调方法,碎片中都有,除此之外还有额外的方法.

  1. onAttach(),当碎片和活动建立关联的时候调用.
  2. onCreatView(),为碎片创建视图(加载布局)的时候调用
  3. onActivityCreated().当与碎片相关联的活动一定已经创建完成的时候调用
  4. onDetach(),当碎片和活动解除关联的时候调用.
    FragmentCycle

体验碎片的生命周期

p163
碎片中的数据也可以通过”savedInstanceStanceState()”方法保存在Bundle参数中,防止停止的时候被消除.

动态加载布局的技巧

p166

使用限定符(Qualifiers)

p167

最小宽度限定符

p168

碎片的实践 (延迟)

广播

标准广播: Normal broadcasts,完全异步广播,发出广播所有接收器会在同一时刻接收,接收率比较高,但无法被截断.
有序广播: Ordered broadcasts,同步执行广播,广播发出后同一时刻只有一个广播接收器能够接受消息.广播接收器逻辑执行完毕后广播才会继续传递.广播接收器有优先级,优先级高的广播接收器先收到消息.优先级高的接收器还能截断广播

接收系统广播

动态注册监听网络变化

广播接收器可以注册广播,有两种方式:

  • 代码中注册 称为动态注册
  • AndroidManifest.xml注册 称为静态注册

创建广播接收器,需要创建一个类继承自BroadcastReceiver,并重写onReceive()方法.

在监听网络变化的时候由于会监听网络,所以需要在AndroidManifest中声明权限.

静态注册实现开机启动

动态注册广播接收器可以自由控制注册与注销,但是必须在程序启动后才能收到广播.静态注册可以在程序未启动的情况下收到广播.

可以直接使用AS直接new一个广播接收器,名字下方的复选框,Exported代表允许接收器接受程序外的广播,enabled代表是否启用这个接收器.

广播接收器的onReceive()方法中不能执行较多的逻辑或者耗时长的操作,因为广播接收器不允许开启线程,这个方法长时间没有结束程序就会报错.

发送自定义广播

发送标准广播

P189

发送有序广播

p191
有序广播使用方法sendOrderedBroadcast()接受两个参数第一个为Intent第二个是与权限相关的字符串.

注册广播时可以通过android:priority属性给广播设置优先级
在代码中还可以在Onreceive()方法中通过abortBroadcast()方法截断广播,阻止广播继续传播.

使用本地广播

p192
为了安全让程序内的广播不泄露,需要使用本地广播,android提供了一个LocalBroadcastManager来对广播进行管理,并提供发送广播和注册广播接收器的方法.

本地广播接收器无法在AndroidManifest.xml中通过静态注册,本地广播更安全,更高效.

广播实践-强制下线

p195
这里我的接收器有问题?反正没有接受广播成功

第六章 持久化技术

p206

文件存储

p207

将数据存储到文件中

context类提供一个方法openFileOutput(),用于将数据存储到指定文件中,两个参数,一个指定文件名称一个指定文件操作模式:MODE_PRIVATE如果存在直接覆盖;MODE_APPEND如果存在则追加.

通过openFileOutput()方法返回一个FileOutputStream对象,用它构建一个OutputStreamWriter对象,接着用这个对象构建一个BufferedWriter对象.然后使用BufferedWriter对象将数据写到文件中.

从文件中读取数据

p211
对应的Context类中提供了openFileInput()方法用于从文件中读取数据,接受一个参数,即要读取的文件名,返回一个FileInputStream对象.然后通过它构建出InputStreamReader对象,使用这个对象构建出BuffereReader对象,然后通过这个对象一步步读取,并存放在一个StringBuilder对象中,最后将读取的内容返回就可以了.

SharedPreferences存储

p213
使用键值对的方式来存储.

将数据存储到SharedPreferences中

android中提供了三种获取SharedPreferences对象的方法:

  • Context类中的getSharePreferences()方法 :两个参数一个指定文件名称,一个用于指定操作模式,目前就只有MODE_WORLD_READABLE这个模式推荐,也可以输入0等同于这个模式.
  • Activity类中getPreferences()方法:雷士上一个方法,但是只有一个参数就是操作模式,文件名称自动用当前活动的类名称.
  • PreferenceManager类中getDefaultSharedPreferences()方法: 静态方法,接受一个Context参数,并自动使用当前应用程序的包名作为前缀.

存储步骤:
使用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象,向SharedPreferences.Editor对象中添加数据(使用类似putString() putBoolean()的方法),调用apply()方法,将添加的数据提交完成存储操作

SharedPreferences文件使用xml格式对数据存储.

从SharedPreferences中读取数据

直接使用SharedPreferences对象对象提供的get方法(getString(),getBoolean()….),两个参数,第一个是键,第二个是默认值即找不到键时以什么样的默认值返回.

实现记住密码功能

p218

SQLite数据库存储

创建数据库

使用SQLiteOpenHelper帮助类,实现简单的数据库创建升级.
这是抽象类,两个抽象方法onCreate() onUpgrade()实现创建,升级数据库逻辑.
两个实例方法:getReadableDatabase()getWritableDatabase().这两个方法创建或打开一个数据库,并返回一个可对数据库读写操作的对象.不同的是当数据库不可写入的时候getReadableDatabase()方法返回的对象以只读的方式打开,而getWritableDatabase()方法将出现异常.

SQLiteOpenHelper有两个构造方法可提供重写,一般使用参数少的那个构造方法.接受四个参数,Context,数据库名,自定义Cursor数据库查询的时候返回一个自定义cursor一般传入null,当前数据库版本号可用于对数据库升级操作.
构建出实例后再调用两个实例方法就能够直接创建数据库了,然后重写的onCreate()方法会的到执行,这里通常会处理一些创建表的逻辑.

使用adb shell查看数据库

p225
使用adb shell进入数据库目录,输入sqlite3 数据库名称就可以打开数据库

添加数据

getReadableDatabase()getWritableDatabase().这两个方法创建或打开一个数据库,并返回一个SQLiteDatebase对象,除了使用sql语句外,SQLiteDatebase,中提供了各种方法对数据库进行操作.
insert()方法用于添加数据:三个参数,第一个参数表名,第二个参数指定未添加数据时给耨些可空的列自动赋值null(一般用不到直接传入null即可),第三个是ContentValues对象它提供了一系列put()方法重载用于向ContentValues中添加数据只需要将表中没得列名及相应的待添加数据传入即可.

更新数据

p232
同样SQLiteDatabase提供了updata()方法四个参数:

  1. 表名
  2. ContentValues对象,把更新的数据在这里装进去
  3. 第三第四用于约束更新某一行或者某几行中的数据,不指定默认更新所有行.

删除数据

p234
同样SQLiteDatabase提供了delete方法,三个参数:

  1. 表名
  2. 第二第三用于约束删除某行或某几行数据,不指定默认删除所有行.

查询数据

p235
SQLiteDatabase提供了query()方法用于查询数据
这个方法最短的方法重载也需要7个参数.

  1. 表名
  2. 第二个用于指定查询哪几列,不指定这查询所有
  3. 第三四用于约束查询某一行或者某几行数据
  4. 第五用于指定要去group by的列,不指定则代表不进行group by操作.
  5. 第六个用于对group by之后的数据进行进一步过滤,不指定则代表不过滤
  6. 第七格用于指定查询结果的排序方式,不指定则默认.

下面是详细参考

query

query()方法会返回一个Cursor对象查询到的所有数据从这个对象取出.

使用SQL操作数据库

sql

使用LitePal操作数据库

p240
LitePal是一个开源android数据库框架,采用对象关系映射(ORM)模式,地址是:https://github.com/LitePalFramework/LitePal

配置LitePal

直接在app/build.gradle中声明开源库的引用就可以了
dependencies闭包中添加

1
implementation 'org.litepal.android:core:2.0.0'

接下来配置litepal.xml文件.右击app/src/main新建一个assets目录,然后在assets目录下再新建一个litepal.xml文件,编辑如下内容.

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8" ?>
<litepal>
<dbname value="BookStore"></dbname> //指定数据库名
<version value="1"></version> //指定数据库版本号
<list> //指定所有映射模型

</list>
</litepal>

然后配置LitePalApplication,修改AndroidManifest中的代码
<application>中添加android:name="org.litepal.LitePalApplication"

创建和升级数据库

创建一个java bean,这个类Book就是一个sql中对应的book表,接下来将Bool类添加到映射模型当中,修改litepal.xml中的代码

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8" ?>
<litepal>
<dbname value="BookStore"></dbname>
<version value="1"></version>
<list>
<mapping class="com.example.elsewhere997.litepaltest.Book"></mapping>
</list>
</litepal>

这里使用<mapping>标签来声明我们要配置的映射模型类,注意一定要使用完整的类名称.不论有多少模型要映射都使用同样的方式配置在<list>标签中.

所有配置工作完成,只要进行任意一次数据库操作,数据库就会自动创建出来.

使用LitePal添加数据

之前创建的Book是没有继承关系的,LitePal在进行表管理操作的时候不需要模型类有继承结构,但是进行CRUD(增删改查)就必须继承DataSupport类.
这个类提供了sava方法用来完成数据添加的操作.也提供了其他的CRUD方法

使用LitePal更新数据

249

删除数据

两种方式
第一种直接调用已存储对象的delete()方法.
第二种调用DataSupport的deleteAll()方法,三个参数,第一个表名称,第二三个限制条件

查询数据


自己查询学习的知识

包括一些学过然后忘记的知识

Android Studio技巧

快捷键

学习过程中我觉得需要牢记的快捷键
Android Studio 平台 Win10

ALT+ENTER 快速import类
Ctrl+O 重写某个方法
ctrl+shift+/ 注释段
ctrl+/ 注释行
alt+insert 快捷生成getter,setter方法

文件名颜色的意思

颜色主要与版本控制(version control)有关

  • 绿色,已经加入控制暂未提交
  • 红色,未加入版本控制
  • 蓝色,加入,已提交,有改动
  • 白色,加入,已提交,无改动
  • 灰色:版本控制已忽略文件

JSON 简单学习

语法规则

一切都是对象,对象表示为键值对,数据由分号间隔,花括号保存对象,方括号保存数组.

键值对

键在前面用""包裹,值在:后面如果是字符串也用""包裹,值可以是任何数据,也可以是对象或者数组
对象(花括号的那个)里存放键值对,用,隔开.
同时数组里也可以存放键值对,但是更多的数组里是存放索引,像这样["java","c#","html"]

java内部类调用外部类的方法

直接使用外部类名称.this.方法即可,例如Inner是内部类,Outer是外部类,在Inner中调用Outer.this.Print()即可使用Outer的print方法

关于context

建议阅读

Context是什么

Activity,Serverce都算Context.把场景抽象为一个Context类,用户的每次交互都是在场景里,还有一些没有界面的场景,如服务.一个程序可以看作为一个工作环境,在环境有不同场景,我们会在其中切换.android程序的四大组件,其他的控件等都要在context环境下工作
它是一个纯抽象类,如图是它的实现
context

关于侦听器中的

1
new view.onclicklistener(){.....}

首先我们可以确定的是这个用法是属于java语法范畴的:
new 后面肯定是个构造器,构造器后面是有括号的,所以onClickListener是构造器(要new出来的类),前面的View表示命名空间,这个onClickListener是View中的。
查看View.class的源码可以发现:
public interface OnClickListener {
void onClick(View var1);
}
onClickListener这是View内部的接口(成员接口),new的时候要实现onClick()方法。

this与activity.this

知乎问题 未读

学习过程中我觉得需要牢记的快捷键
win系统

ALT+ENTER 快速import类
Ctrl+O 重写某个方法
ctrl+shift+/ 注释段
ctrl+/ 注释行
alt+insert 快捷生成getter,setter方法


遇到的问题&解决方法

“android.content.res.Resources$NotFoundException: String resource ID #0x2”错误

解决方案 当调用setText()方法时如果传入int型是不会被当成内容而是resourceID来使用!

所以报错!

转为String传入即可

No resource identifier found for attribute ‘roundIcon’ in package ‘android’

问题描述

报错: ERROR:No resource identifier found for attribute ‘roundIcon’ in package ‘android’

解决方案

roundlcon是高版本才有特性,一般是由于调节targetSdkVersion版本造成的,
删除清单文件AndroidManifest.xml里的 android:roundIcon=”@mipmap/ic_launcher_round”
这句话就OK了
只要看到Error:No resource identifier……,肯定是xml文件里面出问题了,盯着找就好了
如果是in package ‘XXX’,这个多半就是自己定义的控件在应用的时候出了问题

参考博客:https://blog.csdn.net/androidfszl/article/details/61919384

Git如何将本地仓库合并到远程(未解决)

这样一个情景:远程仓库的dev分支已经有内容,我在本地新建了一个仓库代码写完了,准备把这个本地仓库关联到远程仓库并推送到dev分支.

可能的解决方案:https://blog.csdn.net/zhouhuacai/article/details/78284990

安装时遇到的问题

Android Studio安装踩坑

创建控件不显示

问题描述

在按照书本布局章节创建第一个控件时,手动输入代码创建控件(button)不显示,但是控件有一个警告,大致意思是android:text="haha"等号后面不建议接输入内容(但依然可以直接输入),建议使用引用@string/button_1这样的引用格式来引用资源,可以手动在values下的strings.xml创建,也可以直接用警告中的快速修复,编译器自动帮你创建并引用,结果相同.
修复后并没有解决问题,依然不显示.直接在Design中拖一个控件也不显示.
但是直接运行项目是能够现实的,只是在preview中不显示.

解决方式

两种方法原理都不知道…应该是AS的bug

方法一

直接修改布局的主题,直接在preview内修改,似乎只要主题不是AppThem就能够显示
as1

方法二(我使用的方法)

修改app目录下的build.gradle文件内的 dependencies依赖
implementation 'com.android.support:appcompat-v7:28.0.0-alpha3' 改为 implementation 'com.android.support:appcompat-v7:28.0.0-alpha1'
重启(刷新)AS即可.

运行项目问题1

错误提示:
Instant Run performed a full build and install since
the installation on the device does not match the local build on disk.

解决方法:可忽略

activity响应http的action问题

问题描述

第二章有一个这样的示例,让自己的activity可以响应http的action,但是在AndroidManifest.xml中报错.
提示为

1
activity supporting action_view is not set as browsable

按这个意思看,应该是要把这个activity设为brosable

实际上忽略这个错误直接运行还是能够达到预期效果,但Logcat会报一个错,虽然没有影响程序运行.

解决方案

添加一个category把activity设置为BROSABLE

1
2
3
4
5
6
7
8
<activity android:name=".ThirdActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
</intent-filter>
</activity>

错误消失

打开对话框式的活动

问题描述

操作书上P69页例子时出错
点击按钮打开DialogActivity运行报错:

1
2
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.elsewhere997.activityliffecycletest/com.example.elsewhere997.activityliffecycletest.DialogActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.

不能启动活动,从caused可以看出是活动的主题出问题了,要使用Theme.AppCompat的主题.因为我们之前的DialogActivity继承自AppComjpatActivity

1
public class DialogActivity extends AppCompatActivity

解决方案

方法一 修改主题

修改AndroidManifest.xml中的dialog活动主题的代码为

1
android:theme="@style/Theme.AppCompat.Dialog">

重新编译运行,成功了

方法二 直接继承Activity

如果不需要继承AppCompatActivity,直接继承Activity,就可以使用@android:style/Theme.Dialog主题了
重新编译运行,成功了

Cannot resolve symbol R

问题描述

提示为:Cannot resolve symbol R
R文件识别不了但是确实存在,正常情况应该不会报错.
Android Studio 无法识别同一个 package 里的其他类,将其显示为红色,但是 compile 没有问题。鼠标放上去后显示 “Cannot resolve symbol XXX”,重启 Android Studio,重新 sync gradle,Clean build 都没有用。

解决方案

方案一

菜单栏Build–Rebuild Project
但是本错误并没有解决.以后备用

方案二

点击菜单中的 “File” -> “Invalidate Caches / Restart”,然后点击对话框中的 “Invalidate and Restart”,清空 cache 并且重启。没有解决,备用

方案三(重点)

重新检查查看build窗口,发现错误提示,布局文件,代码layout_alignParentleft出错,not found
检查后发现没有大写leftL修复解决.
仔细检查代码!善用提示窗口,不要光看代码提示

Cannot set the value of read-only property ‘outputFile’(待解决)

错误描述

我在完成任务`git@lawyer5.cn:selection-coolcode/android.git`
clone后在AS运行,build的时候报错:

1
Cannot set the value of read-only property 'outputFile' for......

解决方案

方案一(备用)

然后照着网上的帖子[ As Android plugin 3.0 migration guide suggests:] 修改了bulid.gradle中的代码

1
2
3
4
5
6
7
variant.outputs.all { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
// def fileName = "coolcode_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
// output.outputFile = new File(outputFile.parent, fileName)
outputFileName = "coolcode_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk" //改动的
}

然后try again… 继续错误

1
All of them match the consumer attributes:.......

照着帖子:https://blog.csdn.net/qqcrazyboy/article/details/77900183 基本配置好apt到annotationProcessor

还是有一个错误

1
All flavors must now belong to a named flavor dimension

查看error后面给的网站,于是又在app的build.gradle里的 defaultConfig加了一句 flavorDimensions "versionCode"

继续错误

1
failed linking references.

然后禁用aapt2

1
android.enableAapt2=false

然后,就是 java complier: 50errors,哇心累.

这个方法没有解决问题,留作备用

方案二

一开始运行这个项目的时候,我选择了更新Android Gradle Plugin然后就报错

1
Cannot set the value of read-only property 'outputFile' for......

不断的google后还是一直报不同的错误,最后直接错变成了 java complier: 78errors,搞了一下午成这样,哇心累.
然后用git清空了所有修改,重新打开项目,没有更新gradle,运行成功了.

必须定义为final变量

错误描述

错误的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private class ForceOfflineReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
AlertDialog.Builder builder =new AlertDialog.Builder(context);
builder.setTitle("Warning");
builder.setMessage("You are forced to be offline. Please try to login again.");
builder.setCancelable(false);
builder.setPositiveButton("OK",new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCollector.finishAll(); //销毁所有活动
Intent intent=new Intent(context,LoginActivity.class); //这里的context会报错
}
}) //设置确定标签
}
}

错误提示

1
Variable 'context' is accessed from within inner class, needs to be declared final

解决方式

java中规定,内部类只能访问外部类中的成员变量,不能访问方法中定义的变量,如果要访问方法中的变量,就要把方法中的变量声明为final(常量)的,因为这样可以使变量全局化,就相当于是在外部定义的而不是在方法里定义的

这里需要把onReceive()中的参数Context 加上final修饰.

使用adb Shell拒绝

错误描述

使用adb Shell进入data/data…文件夹的时候提示Permission denied,全校被拒绝

解决方案

在命令行继续输入 su root,然后手机确认授root权限.

使用sqlite3工具 not found

错误描述

用adb shell 打开数据库文件,提示没有sqlite3,查看了手机system/bin/目录有sqlite3.exe文件,root也给了,android7.1

1
2
2|D6503:/ $ sqlite3 BookStore.db
/system/bin/sh: sqlite3: not found

解决方案

如果文章有问题欢迎指出,或者你也可以联系我
本文作者:E1se
本文链接: 2018/09/07/第一行代码读书笔记/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!
-------------本文结束-------------
0%