常用的布局
LinearLayout
Android 2.2开始fill_parent改名为match_parent ,从API Level为8开始我们可以直接用match_parent来代替fill_parent
orientation方向;vertical垂直;horizontal水平
gravity: 对齐方式,子控件相对于当前控件的对齐方式
layout_gravity:当前控件相对于父控件的对齐方式
margin:当前控件相对于四周的间距。
padding:当前控件中的子控件相对于当前控件四周的间距。
注意:在LinearLayout中,match_parent是充满父布局剩余空间,剩余空间,剩余空间。
上面的例子中,TextView能显示出来,然后Button占据了剩余的全部空间。
RelativeLayout
RelativeLayout 第一个控件默认都是从左上角开始布局,要控制位置需要设置每个控件相对于其他控件的位置。注意上例中改为RelativeLayout,第二个控件会把第一个控件遮住。
layout_below:位于哪个控件的下方
layout_above:位于哪个控件的上方
layout_toLeftOf:指定当前控件位于哪个控件的左边
layout_alignRight: 两个控件的右边边缘对齐
layout_alignParentRight:当前控件基于父窗体的对其方式
layout_centerHorizontal:水平居中
layout_centerVertical:垂直居中
layout_centerInParent:位于父窗体的中间
举个RelativeLayout的例子
FrameLayout
帧布局中的子控件默认放在左上角,是一层一层向上叠加的。适合简单布局。
下面是帧布局
和上例RelativeLayout两个的布局都长这样
可以看到确实是一层一层往上覆盖的,并且RelativeLayout也有这样的特性。
logcat
最好自定义一个log工具类,指定level就可以过滤日志信息,保留我们需要的部分。比如指定level为WARN,则只会打印WARN和ERROR的信息。将level指定为NOTHING,任何日志都不会打印。
package Utils;
import android.util.Log;
public class LogUtils {
public static final int VERBOSE = 1;
public static final int DEBUG = 2;
public static final int INFO = 3;
public static final int WARN = 4;
public static final int ERROR = 5;
public static final int NOTHING = 6;
public static int level = VERBOSE;
// 琐碎的意义不大的信息,等级最低
public static void v(String tag , String msg){
if(level <= VERBOSE){
Log.e(tag, msg);
}
}
// debug级
public static void d(String tag , String msg){
if(level <= DEBUG)
Log.w(tag, msg);
}
// info级别,打印比较重要的数据,比debug高一级
public static void i(String tag , String msg){
if(level <= INFO)
Log.i(tag, msg);
}
// warn警告信息
public static void w(String tag , String msg){
if(level <= WARN)
Log.d(tag, msg);
}
// error错误信息
public static void e(String tag , String msg){
if(level <= ERROR)
Log.v(tag, msg);
}
}
登录样例-记住用户名密码
使用文件存储的方式,主要使用java.io,很简陋不推荐。
Android自带的方法openFileOutput和openFileInout默认保存在/data/data/包名/files/
文件夹下。只能当前程序使用,安全。
values下的strings.xml
LayoutTest
手机/邮箱
密码
记住我
登录
activity_main布局文件
MainActivity
package com.example.layouttest;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class MainActivity extends AppCompatActivity {
private EditText etAccount;
private EditText etPassword;
private CheckBox checkRemember;
private Button buttonLogin;
private Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// context很常用, 直接给mContext,以后都用它就行
mContext = this;
// 1. 获取控件
etAccount = (EditText) findViewById(R.id.account);
etPassword = (EditText) findViewById(R.id.password);
checkRemember = (CheckBox) findViewById(R.id.remember_me);
buttonLogin = (Button) findViewById(R.id.login);
// 6. 下次进入页面从本地读取用户名密码,显示,checkbox状态还是选中状态
String userinfo = getUserInfo();
if (!TextUtils.isEmpty(userinfo)) {
String username = userinfo.split("-")[0];
String password = userinfo.split("-")[1];
Log.d("pass", username);
Log.d("pass", password);
etAccount.setText(username);
etPassword.setText(password);
checkRemember.setChecked(true);
}
// 2. 设置登录按钮点击事件
buttonLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
login();
}
});
}
private void login() {
// 3. 获取用户名密码,checkbox是否选中
String username = etAccount.getText().toString().trim();
String password = etPassword.getText().toString().trim();
boolean isRemember = checkRemember.isChecked();
// 4. 判断用户名和密码是否为空,不为空请求服务器;为空就提示输入
if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
Toast.makeText(mContext, "请输入用户名或密码", Toast.LENGTH_SHORT).show();
return;
}
// 5. 判断checkbox是否选中,选中则保存用户名密码到本地
if (isRemember) {
boolean result = saveUserInfo(username, password);
if (result) {
Toast.makeText(mContext, "保存成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(mContext, "保存失败", Toast.LENGTH_SHORT).show();
}
} else {
FileOutputStream out = null;
try {
// 若用户没有勾选保存用户名密码,用一个同名的文件覆盖,但里面没有内容,下次进入,输入框就被清空了
out = openFileOutput("userinfo", MODE_PRIVATE);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Intent intent = new Intent(mContext, WelconmeActivity.class);
startActivity(intent);
finish();
}
private boolean saveUserInfo(String username, String password) {
String userInfo = username + "-" + password;
FileOutputStream out = null;
BufferedWriter writer = null;
try {
out = openFileOutput("userinfo", MODE_PRIVATE);
// 将字节流包装为字符流再包装成缓冲流
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(userInfo);
return true;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
private String getUserInfo() {
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try {
in = openFileInput("userinfo");
reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line=reader.readLine()) != null) {
content.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return content.toString();
}
}
SharedPreferences存储
上面的存储方法不推荐,现使用SharedPreferences。
SharedPreferences是通过xml文件来做数据存储的。一般用来存放一些标记性的数据,一些设置信息。
用法如下
// 1.通过Context对象创建一个SharedPreference对象
//name:sharedpreference文件的名称 mode:文件的操作模式
SharedPreferences sharedPreferences = getSharedPreferences("userinfo", Context.MODE_PRIVATE);
// 2.通过sharedPreferences对象获取一个Editor对象
Editor editor = sharedPreferences.edit();
// 3.往Editor中添加数据
editor.putString("username", username);
editor.putString("password", password);
// 4.提交Editor对象
editor.apply(); // editor.commit();这个是有返回值的,但是返回值用处不大,推荐使用apply()
// *********使用sharedPreferences读取数据**********
// 1.通过Context对象创建一个SharedPreference对象
SharedPreferences sharedPreferences = context.getSharedPreferences("userinfo", Context.MODE_PRIVATE);
// 2.通过sharedPreference获取存放的数据
//key:存放数据时的key defValue: 没找到key时候的默认值,根据业务需求来写
String username = sharedPreferences.getString("username", "");
String password = sharedPreferences.getString("password", "");
通过PreferenceManager可以获取一个默认的sharepreferences对象,默认以包名+ _shared_preferences.xml
这样的名称命名。
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
package com.example.layouttest;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private EditText etAccount;
private EditText etPassword;
private CheckBox checkRemember;
private Button buttonLogin;
private Context mContext;
private SharedPreferences pref;
private SharedPreferences.Editor editor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// context很常用, 直接给mContext,以后都用它就行
mContext = this;
// 获取SharedPreferences
pref = PreferenceManager.getDefaultSharedPreferences(mContext);
// 1. 获取控件
etAccount = (EditText) findViewById(R.id.account);
etPassword = (EditText) findViewById(R.id.password);
checkRemember = (CheckBox) findViewById(R.id.remember_me);
buttonLogin = (Button) findViewById(R.id.login);
// 没有取到就默认为false
boolean isRemember = pref.getBoolean("remember_password", false);
// 6. 下次进入页面从本地读取用户名密码,显示,checkbox状态还是选中状态
if (isRemember) {
String username = pref.getString("username", "");
String password = pref.getString("password", "");
etAccount.setText(username);
etPassword.setText(password);
checkRemember.setChecked(true);
}
// 2. 设置登录按钮点击事件
buttonLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
login();
}
});
}
private void login() {
// 3. 获取用户名密码,checkbox是否选中
String username = etAccount.getText().toString();
String password = etPassword.getText().toString();
// 4. 判断用户名和密码是否为空,不为空请求服务器;为空就提示输入
if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
Toast.makeText(mContext, "请输入用户名或密码", Toast.LENGTH_SHORT).show();
return;
}
// 5. 判断checkbox是否选中,选中则保存用户名密码到本地
editor = pref.edit();
if (checkRemember.isChecked()) {
editor.putString("username", username);
editor.putString("password", password);
editor.putBoolean("remember_password", true);
} else {
// 不勾选,下次进入不应该显示用户名密码,应该clear掉
editor.clear();
}
// 不管checkbox有没有选中,都提交
editor.apply();
Intent intent = new Intent(mContext, WelconmeActivity.class);
startActivity(intent);
finish();
}
}
SD卡
读写SD卡需要配置权限android.permission.WRITE_EXTERNAL_STORAGE
// 得到SD卡的路径
String path = Environment.getExternalStorageDirectory().getPath();
Log.d("storage", path); // 这里小米手机是 /storage/emulated/0
// SD卡的状态,是否挂载?
Log.d("storage", Environment.getExternalStorageState()); // 这里小米手机是 mounted,但不是实际的SD卡
//
File sdFile = Environment.getExternalStorageDirectory();
// File对象可以查看可用空间和已用空间
long usableSpace = sdFile.getUsableSpace();
long totalSpace = sdFile.getTotalSpace();
// 转换成用户能看懂的格式(M,G等)
String usableSpace_str = Formatter.formatFileSize(mContext, usableSpace);
String totalSpace_str = Formatter.formatFileSize(mContext, totalSpace);
Log.d("storage", usableSpace_str);
Log.d("storage", totalSpace_str);
/data/data/和/sdcard
-
/data/data/package-name/files: getFileDir().getPath();
是一个应用程序的私有目录,只有当前应用程序有权限访问读写,其他应用无权限访问。一些安全性要求比较高的数据存放在该目录,一般用来存放size比较小的数据。 -
/sdcard: Enviroment.getExternalStorageDirectory().getPath();
是一个外部存储目录,只用应用声明了
的一个权限,就可以访问读写sdcard目录;所以一般用来存放一些安全性不高的数据,文件size比较大的数据。
文件权限
linux下一个文件的权限由10位标示:
- 第1位:文件的类型。
d:文件夹
l:快捷方式
-:文件
其他9位每3位分为一组,每一组的3位按照rwx的顺序排列,没有该项权限就会"-"表示。如果用二进制表示,如果不是-就用1标示,是-用0标示;chmod指令赋权限。
- 2-4: 该文件所属用户对本文件的权限。
- 5-7:该文件所属用户组对本文件的权限。
- 8-10:其他用户对该文件的权限。
如常用的chmod -R 777
,777怎么来的?
rwx三个分别是1 1 1
,转换成十进制位7,2-10一共三组。每组都赋予 全部权限,就是777。
xml的生成
直接写
写一个备份短信(模拟)和恢复短信的功能。业务逻辑如下
a.备份
1.封装短信数据到list中
2.将list中的数据写到xml文件中。
b.恢复
1.解析xml文件中短信数据,封装到list集合中
2.将解析数据打印。
- 先写一个工具类SmsUtils来处理短信数据,实现备份和恢复的功能。
- 备份需要获取全部短信数据。写一个JavaBean封装短信数据,再将这些数据放入一个List里面(由SmsDao完成),再返回List给SmsUtils。
一般JavaBean是使用私有的(private)的成员变量,使用get/set方法加以封装。Android里面用JavaBean,不要用get、set,因为get/set用到了反射机制,而android内存有限。所以成员变量设置为public,可以用"."点运算符直接引用。*
package bean;
public class SmsBean {
/*
一般JavaBean是使用私有的(private)的成员变量,使用get/set方法加以封装。
Android里面用JavaBean,不要用get、set,因为get/set用到了反射机制,而android内存有限。
所以成员变量设置为public,可以用"."点运算符直接引用
*/
public int id;
public int number;
public String date;
public String msg;
@Override
public String toString() {
return "SmsBean{" +
"id=" + id +
", number=" + number +
", date='" + date + '\'' +
", msg='" + msg + '\'' +
'}';
}
}
假设这就是我们的所有短信数据,这里共五条,以列表的形式返回
package dao;
import java.util.ArrayList;
import java.util.List;
import bean.SmsBean;
public class SmsDao {
public static List getAllSms() {
List msgs = new ArrayList<>();
for (int i = 10; i > 0; i--) {
SmsBean msg = new SmsBean();
msg.id = i;
msg.msg = "请你吃饭,快出来!给你十秒 " + i;
msg.number = 123456;
msg.date = "2017/4/9";
msgs.add(msg);
}
return msgs;
}
}
package utils;
import android.content.Context;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.List;
import bean.SmsBean;
import dao.SmsDao;
public class SmsUtils {
public static boolean backupSms(Context mContext) {
// 获取所有sms
List allSms = SmsDao.getAllSms();
//将数据以xml格式封装到一个StringBduilder中
StringBuilder buffer = new StringBuilder();
//封装一个声明头
buffer.append("");
//封装根节点
buffer.append("");
//循环遍历list集合封装所有的短信
for (SmsBean smsBean : allSms) {
buffer.append("");
buffer.append("");
buffer.append(smsBean.number);
buffer.append(" ");
buffer.append("");
buffer.append(smsBean.msg);
buffer.append(" ");
buffer.append("");
buffer.append(smsBean.date);
buffer.append(" ");
buffer.append(" ");
}
buffer.append(" ");
FileOutputStream out = null;
BufferedWriter writer = null;
// 需要文件名后缀,注意和SharedPreferences(不用后缀名,默认xml)区别开来
try {
out = mContext.openFileOutput("smss.xml", mContext.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(buffer.toString());
return true;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
// TODO: 恢复短信
public static void restoreSms() {
}
}
package com.example.backrestore;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import utils.SmsUtils;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button btBackup;
private Button btRestore;
private Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
btBackup = (Button) findViewById(R.id.backup_message);
btRestore = (Button) findViewById(R.id.restore_message);
btBackup.setOnClickListener(this);
btRestore.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.backup_message:
boolean isBackup = SmsUtils.backupSms(mContext);
if (isBackup) {
Toast.makeText(mContext, "备份成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(mContext, "备份失败", Toast.LENGTH_SHORT).show();
}
break;
case R.id.restore_message:
SmsUtils.restoreSms();
break;
default:
}
}
}
XmlSerializer序列化xml文件
将backupSms()方法
修改成即可
public static boolean backupSms_android(Context context){
try {
/* 使用Android的Xml包,可以直接Xml.newSerializer()和Xml.newPullParser()而不用
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlSerializer xmlSerializer = factory.newSerializer(); */
//0.获取短信数据
List allSms = SmsDao.getAllSms();
//1.通过Xml获取一个XmlSerializer对象
XmlSerializer serializer = Xml.newSerializer();
//2.设置XmlSerializer的一些参数,比如:设置xml写入到哪个文件中
//os:xml文件写入流 encoding:流的编码
serializer.setOutput(context.openFileOutput("backupsms2.xml", Context.MODE_PRIVATE), "utf-8");
//3.序列化一个xml的声明头
//encoding:xml文件的编码 standalone:是否独立
serializer.startDocument("utf-8", true);
//4.序列化一个根节点的开始节点
//namespace:命名空间 name: 标签的名称。这里不指定就是null
serializer.startTag(null, "Smss");
//5.循环遍历list集合序列化一条条短信
for (SmsBean smsBean : allSms) {
serializer.startTag(null, "Sms");
//name:属性的名称 value:属性值
serializer.attribute(null, "id", smsBean.id+"");
serializer.startTag(null, "number");
//写一个标签的内容
serializer.text(smsBean.number+"");
serializer.endTag(null, "number");
serializer.startTag(null, "msg");
serializer.text(smsBean.msg);
serializer.endTag(null, "msg");
serializer.startTag(null, "date");
serializer.text(smsBean.date);
serializer.endTag(null, "date");
serializer.endTag(null, "Sms");
}
//6.序列化一个根节点的结束节点
serializer.endTag(null, "Smss");
//7.将xml写入到文件中,完成xml的序列化,写入结束
serializer.endDocument();
return true;
}catch (Exception e) {
e.printStackTrace();
}
return false;
}
方便了不少,也不容易写错。
xml解析
解析方式
DOM Document Object Model 文档对象模型
核心思想: 把文档中所有内容都 封装成对象。
对象种类:
Document 整个(HTML,XML)文档
Element 文档中每一个标签都会被封装成Element对象
Attribute 标签上的每一个属性都会被封装成Attribute
Text 文档中,标签内的文本都会封装成Text对象
Common 注释,文档中的注释 ,会被封装成common.
- Dom的优势:将文档结构(所有内容)都以对象的形式保留在了内存中。我们可以对内存中的(Dom树)进行增删改查操作并且操作很方便。
- Dom的劣势:因为保留了全部文档内容,资源消耗比较大。
SAX解析思想
在读取xml文档时, 已经根据定义好的事件,对xml内容进行了筛选.解析完成后,内存中只保留了我们想要的内容。比较节约资源.。在资源比较匮乏的平台使用,比如手机。
缺点: 没有保留文档的结构, 无法进行增删改的操作。只能查询。
文档开始事件: startDocument
文档结束事件: endDocument
元素开始事件: startElement
元素结束事件: endElement
文本事件: character
Pull解析
与Sax一样,都属于事件驱动的解析方式,相比Sax解析过程更加灵活。
- SAX一旦开始解析就是从头读到尾,不解析完整个文档不会停.
- pull解析较为灵活,是以事件为单位,手动向下继续.。如果获得到我们要找的内容,可以停止继续解析。
上面序列化后的xml大概长这样
123456
"请你吃饭,快出来!给你5秒 5"
"2017/4/10"
123456
"请你吃饭,快出来!给你5秒 5"
"2017/4/10"
123456
"请你吃饭,快出来!给你5秒 5"
"2017/4/10"
SmsUtils里面写恢复短信的方法
public static List restoreSms(Context context) {
List smsBeanList = null;
SmsBean sms = null;
try {
//1.通过Xml获取一个XmlPullParser对象
XmlPullParser xmlPullParser = Xml.newPullParser();
//2.设置XmlPullParser对象的参数,需要解析的是哪个xml文件,设置一个文件读取流
xmlPullParser.setInput(context.openFileInput("smss.xml"), "utf-8");
// 3. 获得当前事件类型
int type = xmlPullParser.getEventType();
// 4. 不是文档结尾就一直解析
while (type != XmlPullParser.END_DOCUMENT) {
// 获取当前元素/节点的名字
String nodeName = xmlPullParser.getName();
switch (type) {
// 开始标签
case XmlPullParser.START_TAG:
// 找到这个标签就新建列表
if (nodeName.equals("Smss")) {
smsBeanList = new ArrayList<>();
// 找到这个标签就新建sms
} else if (nodeName.equals("Sms")) {
sms = new SmsBean();
sms.id = Integer.parseInt(xmlPullParser.getAttributeValue(null, "id"));
} else if (nodeName.equals("number")) {
sms.number = Integer.parseInt(xmlPullParser.nextText());
} else if (nodeName.equals("msg")) {
sms.msg = xmlPullParser.nextText();
} else if (nodeName.equals("date")) {
sms.date = xmlPullParser.nextText();
}
break;
// 结束标签
case XmlPullParser.END_TAG:
// 如果结束标签是Sms则一条短信数据封装完毕封装
if (nodeName.equals("Sms")) {
smsBeanList.add(sms);
}
break;
default:
}
// 下一个元素,返回事件类型
type = xmlPullParser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
return smsBeanList;
}
再修改MainActivity,在case R.id.restore_message里处理逻辑
@Override
public void onClick(View v) {
switch (v.getId()) {
...
case R.id.restore_message:
List smss = SmsUtils.restoreSms(mContext);
if (smss != null) {
restoreText.setText(smss.toString());
Toast.makeText(mContext, "成功恢复"+smss.size()+"条短信", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(mContext, "恢复失败", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
看下结果,备份时候是5条。成功恢复!
by @sunhaiyu
2017.4.10