Android开发环境的搭建
1、安装jdk开发环境
甲骨文公司jdk1.8下载地址 1.下载安装即可,建议安装到指定文件目录下 2.安装完成之后,配置java的JAVA_HOME环境变量,Android开发工具需要使用。 3.java -version 可以查看有没有配置成功。
2、安装android studio开发工具
android studio开发工具下载地址 1.傻瓜式安装,下一步,下一步。
3、第一次使用下载SDK
1.自定义设置
2.定义安卓SDK的存放地址
3.安装安卓的SDK
4、设置AndroidStudio的参数
观看视频教学
5、配置ADB的路径/创建一个AndroidStudio自带的模拟器
1.找到AndroidSDK的路径,找到ADB的目录配置进环境变量里。(path)
2.打开androidstudio,创建一个手机模拟器。
3.新建
4.等待安装完成!!
5.如果提示不能使用的话,进入bios系统开启设备支持虚拟化技术。
6.可以直接下载demo进虚拟手机里运行,查看状况。
安卓开发UI布局
1、新建项目
1.创建项目
2.目录介绍
- 新建页面
- 完整的页面创建过程包括三个步骤:
- 在 layout 目录下创建 XML 文件
- 创建与 XML 文件对应的 Java 代码
- 在 AndroidManifest.xml 中注册页面配
- 快速生成页面源码
- 依次选择右键菜单New→Activity→Empty Activity,弹出图示的页面创建窗口。
- 输入各项信息后,单击窗口右下角的 Finish 按钮,即可完成新页面的创建动作
2、线性布局(LinearLayout)
1.什么是线性布局?其实呢,线性布局就是把孩子都摆在同一条线上!
1.设置视图的宽高
视图宽度通过属性android:layout_width表达, 视图高度通过属性android:layout_height表达, 宽高的取值主要有下列三种:
- match_parent:表示与上级视图保持一致。
- wrap_content:表示与内容自适应。
- 以dp为单位的具体尺寸
2.线性布局内部两种排列方式
线性布局内部的各视图有两种排列方式:
- orientation属性值为horizontal时,内部视图在水平方向从左往右排列。
- orientation属性值为vertical时,内部视图在垂直方向从上往下排列。
- 如果不指定orientation属性,则LinearLayout默认水平方向排列。
//垂直
android:orientation="vertical"
//水平
android:orientation="horizontal"
3.线性布局的权重
- 线性布局的权重概念,指的是线性布局的下级视图各自拥有多大比例的宽高
- 权重属性名叫layout_weight,但该属性不在LinearLayout节点设置,而在线性布局的直接下级视图设置,表示该下级视图占据的宽高比例
- layout_width填0dp时,layout_weight表示水平方向的宽度比例
- layout_height填0dp时,layout_weight表示垂直方向的高度比例
//权重
android:layout_weight="1"
android:layout_weight="1"
3、相对布局(RelativeLayout)
相对布局的下级视图位置由其他视图决定。用于确定下级视图位置的参照物分两种:
- 与该视图自身平级的视图;
- 该视图的上级视图(也就是它归属的RelativeLayout)
如果不设定下级视图的参照物,那么下级视图默认显示在RelativeLayout内部的左上角。
相对位置的取值
相对位置的属性取值 | 相对位置说明 |
layout_toLeftOf | 当前视图在指定视图的左边 |
layout_toRightOf | 当前视图在指定视图的右边 |
layout_above | 当前视图在指定视图的上方 |
layout_below | 当前视图在指定视图的下方 |
layout_alignLeft | 当前视图与指定视图的左侧对齐 |
layout_alignRight | 当前视图与指定视图的右侧对齐 |
layout_alignTop | 当前视图与指定视图的顶部对齐 |
layout_alignBottom | 当前视图与指定视图的底部对齐 |
layout_centerInParent | 当前视图在上级视图中间 |
layout_centerHorizontal | 当前视图在上级视图的水平方向居中 |
layout_centerVertical | 当前视图在上级视图的垂直方向居中 |
layout_alignParentLeft | 当前视图与上级视图的左侧对齐 |
layout_alignParentRight | 当前视图与上级视图的右侧对齐 |
layout_alignParentTop | 当前视图与上级视图的顶部对齐 |
layout_alignParentBottom | 当前视图与上级视图的底部对齐 |
用true来控制,例如:
//当前视图在上级视图中间
android:layout_centerInParent="true"
1.相对布局相对于父控件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http:///apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="中间"
/>
</RelativeLayout>
相对位置的属性取值 | 相对位置说明 |
layout_centerInParent | 当前视图在上级视图中间 |
layout_centerHorizontal | 当前视图在上级视图的水平方向居中 |
layout_centerVertical | 当前视图在上级视图的垂直方向居中 |
layout_alignParentLeft | 当前视图与上级视图的左侧对齐 |
layout_alignParentRight | 当前视图与上级视图的右侧对齐 |
layout_alignParentTop | 当前视图与上级视图的顶部对齐 |
layout_alignParentBottom | 当前视图与上级视图的底部对齐 |
2.相对布局相对于同级控件
相对位置的属性取值 | 相对位置说明 |
layout_toLeftOf | 当前视图在指定视图的左边 |
layout_toRightOf | 当前视图在指定视图的右边 |
layout_above | 当前视图在指定视图的上方 |
layout_below | 当前视图在指定视图的下方 |
layout_alignLeft | 当前视图与指定视图的左侧对齐 |
layout_alignRight | 当前视图与指定视图的右侧对齐 |
layout_alignTop | 当前视图与指定视图的顶部对齐 |
layout_alignBottom | 当前视图与指定视图的底部对齐 |
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http:///apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="中间"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/button"
android:layout_centerHorizontal="true"
android:text="我在中间的上面"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/button"
android:text="我在中间的左边"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/button"
android:text="我在中间的右边"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_below="@id/button"
android:text="我在中间的下边"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
注意:组件的id要书写正确
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="中间"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/button"
android:layout_centerHorizontal="true"
android:text="我在中间的上面"/>
4、绝对布局(AbsoluteLayout)
AbsoluteLayout是靠xy来控制自己的位置
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout
xmlns:android="http:///apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_x="156dp"
android:layout_y="324dp"
android:text="Button" />
</AbsoluteLayout>
5、表格布局(TableLayout)
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http:///apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TableRow>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮1"
android:layout_weight="1"
>
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮2"
android:layout_weight="1"
>
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮3"
android:layout_weight="1"
>
</Button>
</TableRow>
<TableRow>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮4"
android:layout_weight="1"
>
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮5"
android:layout_weight="1"
>
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮6"
android:layout_weight="1"
>
</Button>
</TableRow>
<TableRow>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮7"
android:layout_weight="1"
>
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮8"
android:layout_weight="1"
>
</Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮9"
android:layout_weight="1"
>
</Button>
</TableRow>
</TableLayout>
6、帧布局(FrameLayout)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http:///apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--通常用作播放器暂停的界面-->
<View
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#ff00"
android:layout_gravity="center"
/>
</FrameLayout>
7、网格布局(GridLayout)
- 网格布局支持多行多列的表格排列
- 网格布局默认从左往右、从上到下排列,它新增了两个属性
- columnCount属性,它指定了网格的列数,即每行能放多少个视图
- rowCount属性,它指定了网格的行数,即每列能放多少个视图
8、滚动视图(ScrollView)
滚动视图有两种:
- ScrollView,它是垂直方向的滚动视图;垂直方向滚动时,layout_width属性值设置为match_parent,layout_height属性值设置为wrap_content。
- HorizontalScrollView,它是水平方向的滚动视图;水平方向滚动时,layout_width属性值设置为wrap_content,layout_height属性值设置为match_parent。
9、按钮控件(Button)
按钮控件Button由TextView派生而来,它们之间的区别有:
- Button拥有默认的按钮背景,而TextView默认无背景;
- Button的内部文本默认居中对齐,而TextView的内部文本默认靠左对齐;
- Button会默认将英文字母转为大写,而TextView保持原始的英文大小写;
按钮控件的新增属性: 与TextView相比,Button增加了两个新属性:
- textAllCaps属性,它指定了是否将英文字母转为大写,为true是表示自动转为大写,为false表示不做大写转换。
- onClick属性,它用来接管用户的点击动作,指定了点击按钮时要触发哪个方法。
10、安卓开发中常用的单位
布局中常用的单位
- 像素单位px 像素单位不建议使用,除非是手表,或者机顶盒。
- 适配的单位dp 这适配屏幕的单位,推荐使用,在实际开发中,U设计师会给你标好的。(推荐使用,需要技术相应的dp值来使用!!!)
- 字体单位sp sp:全名 scaled pixels-best for text size,放大像素(比例像素),与刻度无关,可以根据用户的字体大小首选项进行缩放,主要用来处理字体的大小;
1、设置文本内容
设置文本内容有两种方式:
- 在 XML 文件中通过属性 android:text 设置文本
- 在 Java 代码中调用文本视图对象的 setText 方法设置文本
2、引用字符串资源
- 在XML文件中引用(@string/***)
- 在Java代码中引用(R.string.***)
3、设置文本的大小
- 在 Java 代码中调用 setTextSize 方法,即可指定文本大小。
- 在 XML 文件中则通过属性 android:textSize 指定文本大小,此时需要指定字号单位。
- px:它是手机屏幕的最小显示单位,与设备的显示屏有关。
- dp:它是与设备无关的显示单位,只与屏幕的尺寸有关。
- sp:它专门用来设置字体大小,在系统设置中可以调整字体大小。
4、设置文本的颜色
在 Java 代码中调用 setTextColor 方法即可设置文本颜色,具体色值可从 Color 类取。
Color类中的颜色类型 | 说明 | Color类中的颜色类型 | 说明 |
BLACK | 黑色 | GREEN | 绿色 |
DKGRAY | 深灰 | BLUE | 蓝色 |
GRAY | 灰色 | YELLOW | 黄色 |
LTGRAY | 浅灰 | CYAN | 青色 |
WHITE | 白色 | MAGENTA | 玫红 |
RED | 红色 | TRANSPARENT | 透 |
5、RGB颜色定义
- 在XML文件中则通过属性android:textColor指定文本颜色,色值由透明度alpha和RGB三原色(红色red、绿色green、蓝色blue)联合定义。
- 色值有八位十六进制数与六位十六进制数两种表达方式,例如八位编码FFEEDDCC中,FF表示透明度,EE表示红色的浓度,DD表示绿色的浓度,CC表示蓝色的浓度。
- 透明度为FF表示完全不透明,为00表示完全透明。RGB三色的数值越大,表示颜色越浓, 也就越亮;数值越小,表示颜色越淡,也就越暗。
使用色值定义文字颜色:
- 在Java代码中设置色值需要添加0x前缀表示十六进制数。
- 在XML文件中设置色值需要添加“#”前缀
6、引用颜色资源
- 在XML文件中引用(@color/***)
- 在Java代码中引用(R.color.***)
7、在代码中设置视图宽高
首先确保XML中的宽高属性值为wrap_content,接着打开该页面对应的Java代码,依序执行以下三个步骤:
- 调用控件对象的getLayoutParams方法,获取该控件的布局参数。
- 布局参数的width属性表示宽度,height属性表示高度,修改这两个属性值。
- 调用控件对象的setLayoutParams方法,填入修改后的布局参数使之生效
8、设置视图的间距
设置视图的间距有两种方式:
- 采用layout_margin属性,它指定了当前视图与周围平级视图之间的距离。包括layout_margin、layout_marginLeft、layout_marginTop、layout_marginRight、layout_marginBottom
- 采用padding属性,它指定了当前视图与内部下级视图之间的距离。包括padding、paddingLeft、paddingTop、paddingRight、paddingBottom
9、设置视图的对齐方式
- 设置视图的对齐方式有两种途径:
- 采用layout_gravity属性,它指定了当前视图相对于上级视图的对齐方式。
- 采用gravity属性,它指定了下级视图相对于当前视图的对齐方式。
- layout_gravity与gravity的取值包括:left、top、right、bottom,还可以用竖线连接各取值,例如“left|top”表示即靠左又靠上,也就是朝左上角对齐
10、点击事件的处理(重要)
(第一种方式) 在要被点击的控件里添加onClick属性 格式:android:onClick="XXXX"
<Button
android:id="@+id/button01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/shape"
android:text="按钮1"
android:color="#ffffff"
android:layout_weight="1"
android:onClick="onOne01">
接下来,我们就在对应使用这个布局的Activity上面写一个方法,这个方法的格式为:
public void 方法名(View view){//view触发事件的视图控件
Log.d(TAG,"编写要执行的方法");
}
public void onOne01(View view){
if(view instanceof Button){
Button view1 = (Button) view;
String s = view1.getText().toString();
Log.d(TAG,"s="+s);
}
Log.d(TAG,"One be click ...");
}
(第二种方式) 第二种方式呢,就是通过ld声明的方式来找到控件,然后呢,对这个控件设置点击事件。 1.给对应的控件添加id 2.在对应的activity里头找到控件
private Button button01;
private Button button02;
private Button button03;
/**
* 在这个方法里面找到全部控件
*/
private void initViev() {
//找到全部控件并定义成内部私有属性
button01 = (Button)this.findViewById(.button01);
button02 = (Button)this.findViewById(.button02);
button03 = (Button)this.findViewById(.button03);
}
3.设置点击事件完整代码
package com.tian;
import static android.util.Log.d;
import .AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private Button button01;
private Button button02;
private Button button03;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.table_layout);
//找控件
initViev();
//设置点击事件
initClickEvent();
}
/**
* 设置点击事件
*/
private void initClickEvent() {
//第一种设置方式
button01.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v instanceof Button){
Button v1 = (Button) v;
String s = v1.getText().toString();
d(TAG, "onClick: 111"+s);
}
}
});
//第二种设置方式,可以设置统一处理的方法(需要做好控件类型控制和判断)
button02.setOnClickListener(this);
button03.setOnClickListener(this);
}
/**
* 在这个方法里面找到全部控件
*/
private void initViev() {
//找到全部控件并定义成内部私有属性
button01 = (Button)this.findViewById(.button01);
button02 = (Button)this.findViewById(.button02);
button03 = (Button)this.findViewById(.button03);
}
/**
* 重写该类的点击处理函数,处理所有按键的点击事件
*/
@Override
public void onClick(View v) {
//如果有多个控件设置点击事件,那么我们这里面统一处理的话,需要判断是那个控件。
if(v == button02){
d(TAG, "onClick:2 "+button02.getText().toString());
}else if(v == button03 ){
d(TAG, "onClick:3 "+button03.getText().toString());
//....用同样的方法去判断
}
//另外一种方法就是用switch来判断id
//先拿到id
int id = v.getId();
d(TAG, "onClick: " + id);
switch (id){
case .button01:
// one 这个内容被点击了
// 就是在这里处理就Ok了
break;
case .button02:
// one 这个内容被点击了
// 就是在这里处理就Ok了
break;
case .button03:
// one 这个内容被点击了
// 就是在这里处理就Ok了
break;
}
}
}
使用以下代码来隐藏输入的密码:
安卓数据持久化存储android:inputType=“textPassword”
1、保存数据
界面代码:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http:///apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00aaff"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="登录即代表阅读内容并同意阅读条款"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom = "true"
android:layout_marginBottom="30dp"
android:textSize="18dp"
android:textColor="@color/white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:orientation="vertical"
android:padding="30dp"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@mipmap/ic_launcher_round"
android:text="QQ"
android:textColor="@color/white"
android:textSize="50sp"
/>
<EditText
android:id="@+id/user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:hint="QQ号码/手机号码/邮箱"
android:textColorHint="@color/white"
android:textColor="@color/white"
/>
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="密码"
android:textColorHint="@color/white"
android:layout_marginTop="10dp"
android:inputType="textPassword"/>
<Button
android:id="@+id/loginButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录"
android:textSize="20dp"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/passwordIsNull"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="忘记密码?"
android:textSize="15dp"
android:textColor="@color/white"
/>
<TextView
android:id="@+id/newUser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="新用户注册"
android:textColor="@color/white"
android:layout_alignParentRight = "true"
/>
</RelativeLayout>
</LinearLayout>
</RelativeLayout>
逻辑代码:
package com.example.qqlogindemo;
import .AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.File;
import java.io.FileOutputStream;
/**
* 为什么我们直接写一个文件名的时候,去写文件,报出的异常是read-only.
* 其实呢,在Android系统中,每一个应用呢就是一个用户,每个用户它的权限是特定的。不可操作其他应用的内容。
* 以"/“为根自录的,它不是跟windows一样的
*
* 当我们看到我们当前应用的数据保存目录下创建了这个文件info.text,就说明我们可以保存数据了
*/
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//用于日志过滤的(log)
private static final String TAG = "MainActivity";
private EditText user;
private EditText password;
private Button button;
private TextView passwordIsNull;
private TextView newUser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//第一步,找到控件
initView();
//第二步,给控件绑定上点击事件
initViewEvent();
}
private void initViewEvent() {
user.setOnClickListener(this);
password.setOnClickListener(this);
button.setOnClickListener(this);
passwordIsNull.setOnClickListener(this);
newUser.setOnClickListener(this);
}
/**
* 这个方法是用来找到控件并定义成私有成员变量
*/
private void initView() {
user = (EditText)this.findViewById(.user);
password = (EditText)this.findViewById(.password);
button = (Button)this.findViewById(.loginButton);
passwordIsNull = (TextView) this.findViewById(.passwordIsNull);
newUser = (TextView) this.findViewById(.newUser);
}
@Override
public void onClick(View v) {
int id = v.getId();
switch (id){
case .loginButton:
handlerLoginEvent(v);//处理登录逻辑!!
break;
}
}
/**
* 处理登录逻辑事件
* 拿到界面中输入的账号和密码
*/
private void handlerLoginEvent(View view) {
//第三步,我们要拿到界面上的内容,包括:账号和密码
String userText = user.getText().toString();
String passwordText = password.getText().toString();
Log.d(TAG, "账号:"+userText+";密码:"+passwordText);
//把账号密码保存起来
saveUserInfo(userText,passwordText);
}
/**
*保存用户信息的文件存储操作
*/
private void saveUserInfo(String userText, String passwordText) {
File file = new File("/data/data/com.example.qqlogindemo/info.text");//找到对应的位置存储路径
try {
if(!file.exists()){//文件不存在就创建
file.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
//以我们特定的格式来存储
fileOutputStream.write((userText+"***"+passwordText).getBytes());
fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
截图:
2、查看保存的数据
第一种方法: 1.用命令adb devices 查看手机设备adb shell 进入安卓linux系统 2.找到/data/data/包名(com.example.qqlogindemo)路径下查找有没有info.text文件。cat info.text 命令查看
generic_x86_arm:/data/data/com.example.qqlogindemo # cat info.text
dsadsad***dsadasdasdsageneric_x86_arm:/data/data/com.example.qqlogindemo #
第二种方法:
3、通过系统的方法获取到保存的路径
this.getFilesDir()方法
- 怎么获取到文件保存的路径呢?/data/data/com.example.qqlogindemo/files
- 输出的结果为:/data/user/0/com.example.qqlogindemo/files(里面的文件内容与上面的一致应该是属于映射路径)
- 也就是说,这个getFilesDir()这个方法它拿到的路径是/data/user/0/包名/files这个路径
/**
* 处理登录逻辑事件
* 拿到界面中输入的账号和密码
*/
private void handlerLoginEvent(View view) {
//第三步,我们要拿到界面上的内容,包括:账号和密码
String userText = user.getText().toString();
String passwordText = password.getText().toString();
Log.d(TAG, "账号:"+userText+";密码:"+passwordText);
//把账号密码保存起来
saveUserInfo(userText,passwordText);
}
private void saveUserInfo(String userText, String passwordText) {
//怎么获取到文件保存的路径呢?/data/data/com.example.qqlogindemo/files
//输出的结果为:/data/user/0/com.example.qqlogindemo/files(里面的文件内容与上面的一致应该是属于映射路径)
//也就是说,这个getFilesDir()这个方法它拿到的路径是/data/user/0/包名/files这个路径
File filesDir = this.getFilesDir();
Log.d(TAG, "文件的路径是: "+filesDir.toString());
File file = new File(filesDir,"info.text");//找到对应的位置存储路径
try {
if(!file.exists()){//文件不存在就创建
file.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
//以我们特定的格式来存储
fileOutputStream.write((userText+"***"+passwordText).getBytes());
fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//获取到缓存文件存储的路径
File cacheDir = this.getCacheDir();
Log.d(TAG, "缓存文件存储的路径: "+cacheDir);
//获取文件保存的路径
File filesDir = this.getFilesDir();
Log.d(TAG, "文件的路径是: "+filesDir.toString());
File file = new File(filesDir,"info.text");//找到对应的位置存储路径
4、账号密码判空提示
弹出提示窗口代码:
Toast.makeText(this,"密码不能为空..",Toast.LENGTH_SHORT).show();
主要是利用:TextUtils类的isEmpty方法来实现
String userText = user.getText().toString();
String passwordText = password.getText().toString();
//账号判空操作
if (userText.length() == 0){
//长度为空
Toast.makeText(this,"账号不能为空..",Toast.LENGTH_SHORT).show();
return;
}
//密码判空操作
if (passwordText.length() == 0){
Toast.makeText(this,"密码不能为空..",Toast.LENGTH_SHORT).show();
return;
}
//第二种方法:(常用的以下)
if(TextUtils.isEmpty(userText)){
//长度为空
Toast.makeText(this,"账号不能为空..",Toast.LENGTH_SHORT).show();
return;
}
if(TextUtils.isEmpty(passwordText)){
//长度为空
Toast.makeText(this,"密码不能为空..",Toast.LENGTH_SHORT).show();
return;
}
5、读取数据回显
1.利用activity的生命周期函数来实现onRestart()函数
@Override
protected void onRestart() {
super.onRestart();
// File filesDir = this.getFilesDir();
// File file = new File(filesDir,"info.text");//找到对应的位置存储路径
try {
FileInputStream fileInputStream = this.openFileInput("info.text");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
String info = bufferedReader.readLine();
bufferedReader.close();
//fileOutputStream.write((userText+"***"+passwordText).getBytes());是我们之前保存的形式
//我们拿到数据之后要进行切割
String[] split = info.split("\\*\\*\\*");//两个反斜杆进行转义的操作
for (int i = 0;i<split.length;i++){
Log.d(TAG, "分割之后的元素: ["+i+"]="+split[i]);
}
//回显数据
user.setText(split[0]);
password.setText(split[1]);
} catch (Exception e) {
e.printStackTrace();
}
}
6、数据存储到SD卡上
1.不常用的方式
用adb shell进入手机linux系统查看内存挂载地址,文件夹mnt。
进入挂载地址/storage/self/primary
前面我们把数据保存到应用的内部:/data/data/com.sunofbeaches.aalogindemo/files 那么现在我们就开始学习怎么把这个数据保存到SD 上!
新建Activity流程
1.新建一个新的Activity,继承Activity类,重写onCreate方法
2.注册Activity,四大组件都需要注册,然后修改程序的主入口
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http:///apk/res/android"
package="com.example.qqlogindemo">
<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/Theme.QQloginDemo">
<activity
android:name=".MainActivity"
android:exported="true">
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MAIN" />-->
<!-- <category android:name="android.intent.category.LAUNCHER" />-->
<!-- </intent-filter>-->
</activity>
<!-- 都设置了主入口就从上往下找,第一个作为主入口-->
<activity android:name=".SdkActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3.设置布局setContentView(R.layout.sdk_activity);
public class SdkActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//设置布局
setContentView(R.layout.sdk_activity);
}
}
3.找到控件用sd_button = (Button)this.findViewById(.sd_button); 4.设置点击事件用 sd_button.setOnClickListener(this);方法 5.实现监听代码
@Override
public void onClick(View v) {
if(v == sd_button){
//写数据到sd卡上
File filePath = new File("/storage/self/primary");//之前获取的sd卡挂载的路径。
File file = new File(filePath, "info.txt");
try {
FileOutputStream fileOutputStream = new FileOutputStream(file);
//写入东西
fileOutputStream.write("我是练习sd卡存储的程序!!".getBytes());
//关闭输出流
fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
6.读写需要添加权限,在AndroidManifest.xml里添加。
<!--添加sd卡的读取权限,-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
要打开权限空间:
然后就可以看到存储路径下的info.txt文件了
2.常用的方式
1.获取sd卡的存储路径,有专用的api获取。
- Environment.getExternalStorageDirectory();
//获取sd卡的路径
File externalStorageDirectory = Environment.getExternalStorageDirectory();
Log.d(TAG, "sd卡的存储路径: "+externalStorageDirectory.toString());
为什么要这样子获取呢?就是因为不同的手机厂商,它们的扩展卡的名字不一样,通过这个API,就可以获取到它们的扩展卡的路径。
2.我们在实际开发中,会遇到这样的问题,怎么样知道这个手机有没有SD卡? 我们通过一个api来判断这个SD卡是否已经挂载了
- Environment.getExternalStorageState()
//判断设备有没有挂载SD卡
String externalStorageState = Environment.getExternalStorageState();
if(externalStorageState.equals(Environment.MEDIA_MOUNTED)){
Log.d(TAG, "该手机挂载了SD卡");
return;
}
if (externalStorageState.equals(Environment.MEDIA_UNMOUNTED)){
Log.d(TAG, "该手机SD卡已经移除");
return;
}
3.计算SD卡的可用空间
获取SDK卡相关的信息,比如:可用空间 使用getFreeSpace()方法
//显示内存容量是多少
File exFile = Environment.getExternalStorageDirectory();
long freeSpace = exFile.getFreeSpace();
//把long类型转成我们直观的空间大小,比如说:多少M,多少KB,多少MB
String s = Formatter.formatFileSize(this, freeSpace);//this是上下文。
Log.d(TAG, "显示内存容量: "+s);
7、SharePreference
一般用于保存这个偏好设置,比如说我们设置里面的条目。 SharePreference使用步骤
- 第一步:拿到这个SharePreference
settings_info = this.getSharedPreferences("settings_info", MODE_PRIVATE);
这里面这个this指的是上下文Context,在视频中我们是在Activity里面所以直接使用this。因为这Activity间接地继承了Context。
- 第二步:进入编辑模式
SharedPreferences.Editor edit = settings_info.edit();
- 第三步:保存数据
edit.putBoolean("state",isChecked);
- 第四步:提交编辑器
edit.commit();
- 进过这四个步骤,我们就可以把数据保存到SharePreference里了!!
完整代码:
package com.example.qqlogindemo;
import .Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.Switch;
public class PreferenceActivity extends Activity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
private static final String TAG = "PreferenceActivity";
Switch is_switch;
SharedPreferences settings_info;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_preference);
//找控件
is_switch = (Switch)this.findViewById(.is_switch);
is_switch.setOnCheckedChangeListener(this);
//拿到SharedPreferences
settings_info = this.getSharedPreferences("settings_info", MODE_PRIVATE);
//获取数据回显
boolean state = settings_info.getBoolean("state", false);
is_switch.setChecked(state);
}
@Override
public void onClick(View v) {
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
//我们在这里需要对数据进行保存
Log.d(TAG, "onCheckedChanged: "+isChecked);
SharedPreferences.Editor edit = settings_info.edit();
edit.putBoolean("state",isChecked);
edit.commit();
}
}
switch控件编写:
shareprefernece存储也是属于这个内部存储,它跟files/cache也是一样的,在/data/data/包名下/shared_prefs 以xml的文件形式保存起来。它有一个特点,内容保存都是是键值对的方式进行保存。(如下图)
获取数据回显:
//拿到SharedPreferences
settings_info = this.getSharedPreferences("settings_info", MODE_PRIVATE);
//获取数据回显
boolean state = settings_info.getBoolean("state", false);
is_switch.setChecked(state);
1、创建数据库并创建表
1.首先新建一个DatabaseHelper类继承SQLiteOpenHelper类实现重写两个方法和编写构造方法:
package com.example.qqlogindemo;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "DatabaseHelper";
/**
*
* @ context 上下文
* @ name 数据库名称
* @ factory 游标工厂
* @ version 版本号
* 通过构造方法创建了我们的数据库
*/
public DatabaseHelper(Context context) {
super(context, Constants.DATABASE_NAME, null, Constants.VERSION_CODE);
}
/**
*
* 当第一次创建数据库的时候被调用
*/
@Override
public void onCreate(SQLiteDatabase db) {
//创建时的回调
Log.d(TAG, "创建数据库... ");
//创建数据表的操作写在这里面
String sql = "CREATE TABLE "+Constants.TABLE_NAME+"(id integer,name varchar,age integer,salary integer)";
Log.d(TAG, "sql: "+sql);
//db.execSQL(sql);
db.execSQL("CREATE TABLE user(id integer,name varchar,age integer,salary integer)");
Log.d(TAG, "数据库版本: "+db.getVersion());
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//升级数据库时的回调
Log.d(TAG, "升级数据库... ");
String sql;
switch (oldVersion){
case 1:
//编写版本1需要到指定版本添加的字段值
sql = "alter table "+Constants.TABLE_NAME+" add phone integer,address varchar";
db.execSQL(sql);
break;
case 2:
sql = "alter table "+Constants.TABLE_NAME+" add address varchar";
db.execSQL(sql);
break;
case 3:
//当前版本
break;
}
}
}
- onCreate:当第一次创建数据库的时候被调用
- onUpgrade:当数据库版本改变时执行这个回调方法
- public DatabaseHelper(Context context) { super(context, Constants.DATABASE_NAME, null, Constants.VERSION_CODE);大撒大撒 }
构造方法里的参数是
/**
*
* @ context 上下文
* @ name 数据库名称
* @ factory 游标工厂
* @ version 版本号
* 通过构造方法创建了我们的数据库
*/
public DatabaseHelper(Context context) {
super(context, Constants.DATABASE_NAME, null, Constants.VERSION_CODE);
}
2.为了方便管理某些配置参数新建一个Constants配置类
package com.example.qqlogindemo;
public class Constants {
//定义数据库名称
public static final String DATABASE_NAME = "atian.db";
//定义版本
public static final int VERSION_CODE = 1;
//定义表名
public static final String TABLE_NAME = "user";
}
3.在Activity里new DatabaseHelper调用getWritableDatabase()方法
package com.example.qqlogindemo;
import .Activity;
import android.os.Bundle;
public class DatabaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_database);
DatabaseHelper databaseHelper = new DatabaseHelper(this);
databaseHelper.getWritableDatabase();
}
}
创建表的工作一般在onCreate这个回调函数里编写。
2、编写Dao类
package com.example.qqlogindemo;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
public class Dao {
private static final String TAG = "Dao";
DatabaseHelper mHelper;
public Dao(Context context) {
//创建数据库
mHelper = new DatabaseHelper(context);
}
//增
public void insert(){
SQLiteDatabase db = mHelper.getWritableDatabase();
String sql = "insert into "+Constants.TABLE_NAME+"(id,name,age,salary) values(?,?,?,?)";
db.execSQL(sql,new Object[]{1,"张三",45,5000});
db.close();
}
//删
public void delete(){
SQLiteDatabase db = mHelper.getWritableDatabase();
String sql = "delete from "+Constants.TABLE_NAME+" where age = 45";
db.execSQL(sql);
db.close();
}
//改
public void update(){
SQLiteDatabase db = mHelper.getWritableDatabase();
String sql = "update "+Constants.TABLE_NAME+" set salary = 2 where age = 45";
db.execSQL(sql);
db.close();
}
//查
public void query(){
SQLiteDatabase db = mHelper.getWritableDatabase();
String sql = "select * from "+Constants.TABLE_NAME;
Cursor cursor = db.rawQuery(sql, null);
//Cursor是游标,指向某一行某一列
while (cursor.moveToNext()){
//获取到age年龄
int index = cursor.getColumnIndex("age");
String age = cursor.getString(index);
Log.d(TAG, "查询到的年龄是: "+age);
}
cursor.close();
db.close();
}
}
3、编写测试类
package com.example.qqlogindemo;
import android.content.Context;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
import java.util.List;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http:///tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
private static final String TAG = "ExampleInstrumentedTest";
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.example.qqlogindemo", appContext.getPackageName());
}
@Test//测试插入
public void testInsert(){
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
Dao dao = new Dao(appContext);
dao.insert();
}
@Test//测试删除
public void testDelete(){
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
Dao dao = new Dao(appContext);
dao.delete();
}
@Test//测试修改
public void testUpdate(){
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
Dao dao = new Dao(appContext);
dao.update();
}
@Test//测试查询
public void testQuery(){
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
Dao dao = new Dao(appContext);
dao.query();
}
}
4、使用android的api实现增删改查操作
先获取SQLiteDatabase db = mHelper.getWritableDatabase(); 然后调用相应的方法。
package com.example.qqlogindemo;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class Dao {
private static final String TAG = "Dao";
DatabaseHelper mHelper;
public Dao(Context context) {
//创建数据库
mHelper = new DatabaseHelper(context);
}
//增
public void insert(){
SQLiteDatabase db = mHelper.getWritableDatabase();
// String sql = "insert into "+Constants.TABLE_NAME+"(id,name,age,salary) values(?,?,?,?)";
// db.execSQL(sql,new Object[]{1,"张三",45,5000});
ContentValues values = new ContentValues();
values.put("id",2);
values.put("name","xqt");
values.put("age",25);
values.put("salary",5000);
db.insert(Constants.TABLE_NAME, null, values);
db.close();
}
//删
public void delete(){
SQLiteDatabase db = mHelper.getWritableDatabase();
// String sql = "delete from "+Constants.TABLE_NAME+" where age = 45";
// db.execSQL(sql);
int delete = db.delete(Constants.TABLE_NAME, null, null);
//等于1就是删除成功了
Log.d(TAG, "delete: "+delete);
db.close();
}
//改
public void update(){
SQLiteDatabase db = mHelper.getWritableDatabase();
// String sql = "update "+Constants.TABLE_NAME+" set salary = 2 where age = 45";
// db.execSQL(sql);
ContentValues values = new ContentValues();
values.put("salary",2000);
db.update(Constants.TABLE_NAME,values,null,null);
db.close();
}
//查
public void query(){
SQLiteDatabase db = mHelper.getWritableDatabase();
// String sql = "select * from "+Constants.TABLE_NAME;
// Cursor cursor = db.rawQuery(sql, null);
// //Cursor是游标,指向某一行某一列
// while (cursor.moveToNext()){
// //获取到age年龄
// int index = cursor.getColumnIndex("age");
// String age = cursor.getString(index);
// Log.d(TAG, "查询到的年龄是: "+age);
// }
//
// cursor.close();
Cursor cursor = db.query(Constants.TABLE_NAME, null, null, null, null, null, null);
while (cursor.moveToNext()){
int id = cursor.getInt(0);
String name = cursor.getString(1);
Log.d(TAG, "id: "+id+";name: "+name);
}
db.close();
}
}
Cursor:相当于是游标,确定行的位置。ContentValues :相当于是kv的键值对,相当于是设置传参。
5、数据库事务
有两个特点:1.安全性 情景: 每个月15号,你公司都会给你发工作。 操作流程:
公司的财务账号有10000000,那么它要-12000 那你的账号就要增加12000
可能在这两个过程中,出现了问题,比如说,停电,那么公司,账号上减了钱,但是你的账号没钱进入。 这个时候,就可以使用数据库事务来解决这个问题。
2.高效性
使用普通的形势向数据添加3000条数据 time = 14790
再使用开启事务的形势插入3000条数据 time = 210
对比耗时多少:
原理呢:这个没开始事务的是打开数据库,插入数据,关闭数据库。(耗时很多) 开启事务的:把数据存到内存里,然后一次写入到数据库里。
//开启事务
db.beginTransaction();
try {
//这里里面编写多条sql并执行
db.execSQL("insert into user(id,name,age,salary) values(1,\"张三\",45,5000)");
db.execSQL("insert into user(id,name,age,salary) values(1,\"张三\",45,5000)");
//标记数据库操作成功
db.setTransactionSuccessful();
}catch (Exception e){
throw new RuntimeException("出错了!!!");
}finally {
//关闭事务
db.endTransaction();
db.close();
}
1、了解AndroidManifest.xml
包名:package="com.example.qqlogindemo 添加权限:
<!--添加sd卡的读取权限,-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
详细地址:https://notes.sunofbeach.net/pages/dbc5a7/
2、新建Activity类
1.新建一个XXXXActivity类,继承Activity。重写onCreate。
package com.example.qqlogindemo;
import .Activity;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
public class DatabaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//设置视图
setContentView(R.layout.activity_database);
}
}
2.新建布局xml
activity_database.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http:///apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="数据库界面"
android:textSize="20dp"
/>
</RelativeLayout>
3.进行Activity的静态注册到AndroidManifest.xml里
<activity android:name=".DatabaseActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
android:label="@string/app_name"可以设置app的名称 一个安卓应用可以有多个入口的。
3、界面跳转(显式意图)
显式Intent:按名称(完全限定类名)指定要启动的组件。 1.首先要创建一个意图对象,然后通过startActivity方法来跳转
String userText = user.getText().toString().trim();
String passwordText = password.getText().toString().trim();
if(userText.equals("")){
//提示框
Toast.makeText(this,"账号为空!",Toast.LENGTH_SHORT).show();
return;
}
if(passwordText.equals("")){
Toast.makeText(this,"密码为空!",Toast.LENGTH_SHORT).show();
return;
}
//这里进行页面跳转
//我们先要创建一个意图对象,然后通过startActivity方法来跳转
Intent intent = new Intent(this,TwoActivity.class);
//中间添加跳转页面需要的参数
intent.putExtra("userText",userText);
intent.putExtra("passwordText",passwordText);
startActivity(intent);
2.跳转后利用getIntent()方法获取参数
//获取跳转的参数
Intent intent = getIntent();
String userText = intent.getStringExtra("userText");
String passwordText = intent.getStringExtra("passwordText");
//获取输出控件,并输出
TextView textView = this.findViewById(.text_two);
textView.setText("账号为:"+userText+",密码为:"+passwordText+"");
4、界面跳转(隐式意图)
隐式意图是相对于显式意图来说的,显式意图可以看得到对应的类,而隐式意图看不到。
接下来,我们就是通过隐式意图的方式来实现界面的跳转:
步骤:
这里面我们针对的是应用内的跳转
第一步:创建一个Intent
Intent intent = new Intent();
第二步:在AndroidManifest.xml里配置目标跳转Activity的意图过滤器(是通过意图过滤来启动的)
…….
<activity android:name”.SecondActivity”>
<intent-filter>
<action name=”com.sunofbeaches.LOGIN_INFO”>
</intent-filter>
</activity>
第三步:给意图设置action
intent.setAction(“com.sunofbeaches.LOGIN_INFO”);
//中间添加跳转页面需要的参数
intent.putExtra("userText",userText);
intent.putExtra("passwordText",passwordText);
第四步:然后
startActivity(intent);
其实,我们的显式意图是用来应用内跳转。 而隐式意图用于第三方应用的跳转。
1.过滤logcat运行程序的cmp
命令:
然后手机上点击谷歌浏览器
获取到:
cmp=com.android.chrome/com.google.s.chrome.Main其实组件的名称,也就是ComponentName = 包名/类的路径名称。前缀包名相同可以省略,也就是所在的包跟包名是一样的。
就可以省略。
cmp=com.android.chrome/com.google.s.chrome.Main
2.跳转到谷歌浏览器(通过显式意图)
//新建一个Intent对象
Intent intent = new Intent();
//第一种写法(对应抓取到的包名和类名)
intent.setClassName("com.android.chrome","com.google.s.chrome.Main");
//第二种写法
ComponentName componentName = new ComponentName("com.android.chrome","com.google.s.chrome.Main");
intent.setComponent(componentName);
//开始跳转
startActivity(intent);
其实,我们的显式意图是用来应用内跳转。 而隐式意图用于第三方应用的跳转。
3.跳转到谷歌浏览器(通过隐式意图)
步骤: 第一步:创建Intent对象
Intent intent = new Intent();
第二步:给这个Intent对象设置Action,设置它的category值,如果5.1以上系统需要设置包名。这些信息可以从安卓的源码里看到
intent.setAction("android.intent.action.SEARCH");
intent.addCategory("android.intent.category.DEFAULT");
intent.setPackage("com.android.browser");
第三步:startActivity(intent)进行跳转。
//开始跳转
startActivity(intent);
5、组件之间的数据传输(Intent)
之前我们已经学习了组件之间的跳转,其实我们也尝试过数据之间的传输对吧!
在第一节课的时候,我们学习了怎么跳转,并且把数据传递到下一个界面,也就是我们登录那个例子,把登录的信息传到下一个界面.
接下来我们则系统进学习一下如何把数据传到下一个界面。大家也不要局限于Activity之间的数据传递,也就是说,这是组件与组件之间的数据传递,也适用于后面我们学到的服务,广播接收者…
四大组件,对吧
1.基本数据类型的传输
Intent的组成部分:
元素名称 | 设置方法 | 说明与用途 |
Component | setComponent | 组件,它指定意图的来源与目标 |
Action | setAction | 动作,它指定意图的动作行为 |
Data | setData | 即Uri,它指定动作要操纵的数据路径 |
Category | addCategory | 类别,它指定意图的操作类别 |
Type | setType | 数据类型,它指定消息的数据类型 |
Extras | putExtras | 扩展信息,它指定装载的包裹信息 |
Flags | setFlags | 标志位,它指定活动的启动标志 |
前面要跳转的话需要创建一个意图对象,也就是Intent。这个Intent其实就是数据的载体,把数据扔intent里面。
所以就有了:
前面的String name是key,也就是这里put,另外一边则是get了。get的时候需要传入key,这样才能获取到对应的值。
一般来说,这个key定义为一个常量,并且两个组件都能访问到。
另外一边则是获取数据是吧!
首先我们要拿到意图对象,也就是Intent
通过get类型(key)的方法来获取到对应的内容,这样子就完成了数据内容的传输了。
例子:
package com.sunofbeaches.componentdatadeliver;
import android.content.Intent;
import android.os.Bundle;
import .AppCompatActivity;
import android.view.View;
/**
* 虽然说我们这节课是组件之间的数据传递
* 我们常说的组件有Activity,BroadcastReceiver,Service,ContentProvider
* <p/>
* 这里的话我们只学习Activity之间的数据传输,其实其他组件之间的数据传输也是一样的。
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 第一个按钮被点击了
*
* @param view
*/
public void firstClick(View view) {
//这样子写也是可以的哦!
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("booleanKey", true);
intent.putExtra("charKey", 'a');
intent.putExtra("byteKey", (byte) 1);
intent.putExtra("shortKey", (short) 2);
intent.putExtra("intKey", 3);
intent.putExtra("longKey", 4l);
intent.putExtra("floatKey", 0.5f);
intent.putExtra("doubleKey", 0.6d);
startActivity(intent);
}
/**
* 第二个按钮被点击了
*/
public void secondClick(View view) {
}
}
第二个Activity
package com.sunofbeaches.componentdatadeliver;
import .Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
/**
* Create by TrillGates 2017/11/23
* 这是第二个界面,我们就在这个界面获取一个内容吧:
*/
public class SecondActivity extends Activity {
private static final String TAG = "SecondActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/**
* 拿到启动这个Activity的意图对象
*/
Intent intent = getIntent();
if (intent != null) {
//前面的是key,后面的是默认值,假设获取不到的时候,就会返回默认值,也就是后面的那个值。
//比如说我们把key写错了,这样子就获取不到值了。
boolean booleanValue = intent.getBooleanExtra("booleanKey", false);
//
char charValue = intent.getCharExtra("charKey", '*');
//
byte byteValue = intent.getByteExtra("byteKey", (byte) 0);
//
short shortValue = intent.getShortExtra("shortKey", (short) 0);
//
int intValue = intent.getIntExtra("intKey", -1);
//
long longValue = intent.getLongExtra("longKey", 0l);
//
float floatValue = intent.getFloatExtra("floatKey", 0.0f);
//
double doubleValue = intent.getDoubleExtra("doubleKey", 0.0d);
Log.d(TAG, "booleanValue = " + booleanValue);
Log.d(TAG, "charValue = " + charValue);
Log.d(TAG, "byteValue = " + byteValue);
Log.d(TAG, "shortValue = " + shortValue);
Log.d(TAG, "intValue = " + intValue);
Log.d(TAG, "longValue = " + longValue);
Log.d(TAG, "floatValue = " + floatValue);
Log.d(TAG, "doubleValue = " + doubleValue);
}
}
}
执行结果是怎么样的呢?我们点击一下按钮如下:
2.引用数据类型的数据传输
前面已经说了如何传输基本数据类型,那么后面的话我们说一说怎么传递对象。
前面我们看到可以传String呢?String呢不是基本数据类型,它是引用数据类型。
String是已经实现了序列化的接口的:
Bitmap也就是位图,位图对象也是实现了序列化的接口的。(传图片)
所以我们可以传位图对象,但是要注意的是它的大小 ,后面我们会讲到意图对象的传值的大小限制。
接下来,我们使用Intent来传一个对象:User
首先,我们要把User序列化。
package com.sunofbeaches.componentdatadeliver;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by TrillGates on 17/11/24.
* God bless my code!
*/
public class User implements Parcelable {
private String name;
private int age;
public User(String name, int age) {
this.age = age;
= name;
}
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
public void setName(String name) {
= name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
}
实现Parcelable也行,Serializable也行。后者是Java的,前者是google写的类。两者的不同是前者比较高效,它是写到内存里的,后者是写到持久化存储单元里的。
然后呢?看代码吧:
/**
* 第二个按钮被点击了
*/
public void secondClick(View view) {
User user = new User("TrillGates", 25);
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("user", user);
startActivity(intent);
}
第二个Activity里的代码:
User user = intent.getParcelableExtra("user");
Log.d(TAG, "usr Name == " + user.getName());
Log.d(TAG, "usr age == " + user.getAge());
运行起来,结果如下:
除了这样传以外,还可以怎么传呢?其实我们可以以协议的形式来传数据的呢:
实际的列子有那些呢?比如说我们第三应用发短信,第三方应用要调用电话拨号器。这个时候就需要去看它的意图过滤器了:
<intent-filter>
<action android:name="android.intent.action.CALL" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="tel" />
</intent-filter>
我们可以看到,这其实就是约束,数据的约束,也就是说,我们要拨打电话的时候,是这样子的:
Intent intent = new Intent():
intent.addAction(“android.intent.action.CALL”);
intent.setCategory(“android.intent.category.DEFAULT”);
intent.setData(Uri.parse(“tel://10086”));
startActivity(intent);
这样能看明白吗?以上是纯手写的代码,没有开发工具也不知道单词有没写错。 OK,知道这个以后,我们就明白了,还可以通过setData来传数据,并且,在另外一个界面,通过getData来获取到。
获取到的是全部内容哦,包括约束:tel://10086
3.Intent封装数据的大小限制
有的时候,我们传的数据是挺大的。比如说,我们要传一张图片的时候,就很大了,是吧。那么它的现实是多大呢?我们可以看看官方的文档。
那怎么办呢?可以写到SD卡上,把路径传过去就可以。对于IPC来说,有好多种方式。IPC就是跨进程通迅啦,后面我们学习到服务的时候 ,我们也会学到AIDL,这也是IPC的一种方式。
从上面的文章我们可以知道,它限制的大小为1M,这块其实是共享内存来的。也就是Blundle.详细请看我们的视频课程吧!
4.结束活动
从当前页面回到上一个页面,相当于关闭当前页面,返回代码如下:finish(); // 结束当前的活动页面
5.实现拨打电话的功能
本质是隐式跳转: 阅读安卓源码得到隐式跳转信息
//新建一个Intent对象
Intent intent = new Intent();
//以下信息阅读源码获得
intent.setAction("android.intent.action.CALL");
intent.addCategory("android.intent.category.DEFAULT");
//要根据它的格式来编写uri。
Uri parse = Uri.parse("tel:10086");
//设置数据
intent.setData(parse);
//开始跳转
startActivity(intent);
AndroidManifest.xml添加权限:
<uses-permission android:name="android.permission.CALL_PHONE"/>
第二种写法: ACTION_DIAL:跳转到拨打电话界面 ACTION_CALL:直接拨打电话
Intent intent = new Intent(Intent.ACTION_CALL,Uri.parse("tel:10086"));
6.Activity的数据回传
数据回传的基础流程:
直接上代码:
OneActivity
package com.example.qqlogindemo.activitys;
import .Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.example.qqlogindemo.R;
public class OneActivity extends Activity implements View.OnClickListener {
private static final String TAG = "OneActivity";
EditText user ;
EditText password ;
Button button;
private Button egg_button;
Button phone_button;
Button sms_button;
Button recharge_button;
TextView text_view;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_one);
//初始化
initView();
//设置点击事件
initViewEvent();
}
private void initViewEvent() {
button.setOnClickListener(this);
egg_button.setOnClickListener(this);
phone_button.setOnClickListener(this);
sms_button.setOnClickListener(this);
recharge_button.setOnClickListener(this);
}
private void initView() {
user = this.findViewById(.login_user);
password = this.findViewById(.login_password);
button = this.findViewById(.login_button);
egg_button = this.findViewById(.egg_button);
phone_button = this.findViewById(.phone_button);
sms_button = this.findViewById(.sms_button);
recharge_button = this.findViewById(.recharge_button);
text_view = this.findViewById(.text_view);
}
@Override
public void onClick(View v) {
int id = v.getId();
switch (id){
case .login_button:
handlerLoginEvent(v);
break;
case .egg_button:
handlerEgg(v);
break;
case .phone_button:
handlerPhone(v);
break;
case .sms_button:
handlerSms(v);
break;
case .recharge_button:
handlerRecharge(v);
break;
}
}
private void handlerRecharge(View v) {
//跳转到充值界面
//新建一个Intent对象
Intent intent = new Intent(this,PayActivity.class);
//调用充值页面
startActivityForResult(intent,1);
}
/**
* 返回的结果会在这里面
* @param requestCode
* @param resultCode
* @param data
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode==1){
if (resultCode == 2){
String resultContent = data.getStringExtra("resultContent");
//显示文字内容
text_view.setText(resultContent);
//改变文字大小
text_view.setTextSize(20);
}
if (resultCode == 3){
String resultContent = data.getStringExtra("resultContent");
//显示文字内容
text_view.setText(resultContent);
//改变文字大小
text_view.setTextSize(20);
}
}
}
private void handlerSms(View v) {
//新建一个Intent对象
Intent intent = new Intent();
}
private void handlerPhone(View v) {
//新建一个Intent对象
Intent intent = new Intent(Intent.ACTION_DIAL,Uri.parse("tel:10086"));
// intent.setAction("android.intent.action.CALL");
// intent.addCategory("android.intent.category.DEFAULT");
// Uri parse = Uri.parse("tel:10086");
// intent.setData(parse);
//开始跳转
startActivity(intent);
}
private void handlerEgg(View v) {
//新建一个Intent对象
Intent intent = new Intent();
// //第一种写法
// intent.setClassName("com.android.chrome","com.google.s.chrome.Main");
//
// //第二种写法
// ComponentName componentName = new ComponentName("com.android.chrome","com.google.s.chrome.Main");
// intent.setComponent(componentName);
intent.setAction("android.intent.action.SEARCH");
intent.addCategory("android.intent.category.DEFAULT");
intent.setPackage("com.android.browser");
//开始跳转
startActivity(intent);
}
private void handlerLoginEvent(View v) {
String userText = user.getText().toString().trim();
String passwordText = password.getText().toString().trim();
if(userText.equals("")){
Toast.makeText(this,"账号为空!",Toast.LENGTH_SHORT).show();
return;
}
if(passwordText.equals("")){
Toast.makeText(this,"密码为空!",Toast.LENGTH_SHORT).show();
return;
}
//这里进行页面跳转
//我们先要创建一个意图对象,然后通过startActivity方法来跳转
Intent intent = new Intent(this,TwoActivity.class);
//中间添加跳转页面需要的参数
intent.putExtra("userText",userText);
intent.putExtra("passwordText",passwordText);
startActivity(intent);
Log.d(TAG, "userText: "+userText);
Log.d(TAG, "passwordText: "+passwordText);
}
}
PayActivity
package com.example.qqlogindemo.activitys;
import .Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.example.qqlogindemo.R;
public class PayActivity extends Activity {
Button recharge;
Button unRecharge;
EditText money;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pay);
//初始化
initView();
//设置点击事件
initViewEvent();
}
private void initViewEvent() {
recharge.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String trim = money.getText().toString().trim();
if (trim.equals("")){
Toast.makeText(PayActivity.this,"请输入金额..",Toast.LENGTH_SHORT).show();
return;
}
//进行网络访问,进行充值
Intent intent = new Intent();
intent.putExtra("resultContent","充值成功!");
//setResult有两个重载的方法,一个是只有resultCode的,一个是有resultCode和Intent的。
setResult(2,intent);
//结束活动
finish();
}
});
unRecharge.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("resultContent","取消充值!");
//setResult有两个重载的方法,一个是只有resultCode的,一个是有resultCode和Intent的。
setResult(3,intent);
//结束活动
finish();
}
});
}
private void initView() {
recharge = (Button)this.findViewById(.recharge);
unRecharge = (Button)this.findViewById(.unRecharge);
money = (EditText)this.findViewById(.money);
}
}
activity_pay.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http:///apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/money"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:hint="请输入充值金额"
android:inputType="number" />
<Button
android:id="@+id/recharge"
android:layout_width="match_parent"
android:text="充值"
android:textSize="20dp"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/unRecharge"
android:layout_width="match_parent"
android:text="取消充值"
android:textSize="20dp"
android:layout_height="wrap_content"/>
</LinearLayout>
实现相机的回传,需要查看安卓上层应用源码进行开发 setResult里的返回码源码里面固定的。
6、Activity的生命周期
https://notes.sunofbeach.net/pages/1ffec7/
Activity的生命周期:
- onCreate:创建活动。把页面布局加载进内存,进入了初始状态。(初始)
- onStart:开始活动。把活动页面显示在屏幕上,进入了就绪状态。(可见)
- onResume:恢复活动。活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作、允许用户输入文字等等。(获取到焦点)
- onPause:暂停活动。页面进入暂停状态,无法与用户正常交互。(失去焦点)
- onStop:停止活动。页面将不在屏幕上显示。(不可见)
- onDestroy:销毁活动。回收活动占用的系统资源,把页面从内存中清除。(结束)
- onRestart:重启活动。重新加载内存中的页面数据。(重启——>onStart)
- onNewIntent:重用已有的活动实例
1.各状态之间的切换过程
打开新页面的方法调用顺序为:
onCreate→onStart→onResume
关闭旧页面的方法调用顺序为:
onPause→onStop→onDestroy
onCreate和onDestroy主要保存信息的代码
package com.sunofbeaches.activitylifecircledemo;
import android.content.SharedPreferences;
import android.os.Bundle;
import .AppCompatActivity;
import android.text.TextUtils;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
private EditText mInputBox;
private SharedPreferences mMsgConfig;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//前面我们学习数据存储的时候学习过了sp的存储,这里我们就使用sp来存储这些简单的数据即可
mMsgConfig = this.getSharedPreferences("MsgConfig", MODE_PRIVATE);
//输入框控件
mInputBox = (EditText) this.findViewById(.message_content);
//在sp里拿到内容
String content = mMsgConfig.getString("content", null);
//如果内容不为空的话,再设置到输入框里去,显示出来。
if (content != null) {
mInputBox.setText(content);
}
}
@Override
protected void onDestroy() {
//销毁之前,拿到输入框框的内容,然后判断是否为空,不为空的话保存起来,为下一次进入的时候显示出来。
String content = mInputBox.getText().toString().trim();
if (!TextUtils.isEmpty(content)) {
mMsgConfig.edit().putString("content", content).commit();
}
super.onDestroy();
}
}
设置主题为透明: android:theme=“@android:style/Animation.Translucent”
2.横竖屏切换Activity生命周期的变化
切换的话,Activity执行了onPause,再执行onStop和onDestroy方法。 也就是说,它先是走完了自己的生命周期,再重新开始。
对于横竖屏生命周期的总结是:先销毁掉原来的生命周期,然后再重新跑一次。 但是,这样子是不是会有问题呢?有些场景下: 比如说,做游戏开发 。横竖屏的切换,生命周期重新加载,那么当前页面的数据也会重新开始了。
那怎么样处理横竖屏的生命周期呢?
第一种方法,写它横竖屏,也就是说,指定该Activity是横屏或者是竖屏,在那里修改呢? 在配置文件里修改:
假设说,我们修改为横屏,一般来说,游戏横屏的比较多嘛,比较方便操作。
<activity android:name=".LandscapeActivity"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
修改成横屏了,我们再跑一次。发现它直接就横屏显示了。
接着我们点击切换横竖屏的按钮:
生命周期没有发生变化,但是,屏幕却竖起来了。
那有没有一种方式,能让屏幕随着屏幕的旋转而旋转,但是并不硬性生命周期的变化呢?
是可以的呢,我们需要设置一下忽略的配置变化就可以啦!
怎么设置呢?
<activity android:name=".LandscapeActivity"
android:configChanges="orientation|screenSize|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
可以知道生命周期并没有发生改变,但是我们UI已经切换过来了,对吧!
7、Activity的启动模式
1.创建基本的Activity
2.Activity 的启动模式
3.在配置文件中指定启动模式
最基本最直接的方式就是直接修改AndroidManifest.xml里的属性配置即可,比如说:
launchMode属性的取值说明见下表:
launchMode属性值 | 说明 |
standard | 标准模式,无论何时启动哪个活动,都是重新创建该页面的实例并放入栈顶。如果不指定launchMode属性,则默认为标准模式 |
singleTop | 启动新活动时,判断如果栈顶正好就是该活动的实例,则重用该实例;否则创建新的实例并放入栈顶,也就是按照standard模式处理 |
singleTask | 启动新活动时,判断如果栈中存在该活动的实例,则重用该实例,并清除位于该实例上面的所有实例;否则按照standard模式处理 |
singleInstance | 启动新活动时,将该活动的实例放入一个新栈中,原栈的实例列表保持不变 |
新建线程任务:new Handler()
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private int time = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
if (time < 5) {
startActivity(new Intent(MainActivity.this, NewActivity.class));
Log.d(TAG, "start new activity...");
time++;
handler.postDelayed(this, 1000);
}
}
});
}
}