本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如果错漏,欢迎留言指正
Android正向开发
一、HelloWorld
工程文件目录结构
最终效果
应用程序的目录结构:
.idea(还有个.build)都是自动生成的文件,无需理会,无需手动编辑。app,最重要的文件,里面放置的就是项目的代码文件和资源文件。(下文会详细讲它里面的文件)
build,编译时自动生成的文件,无需关心。libs,如项目需用到第三方jar包(比如百度地图,极光推送等),则需要将这些jar包放到此目录下。src,有三个目录:androidTest目录用来编写Android Test测试用例的,进行自动化测试用的。main目录中的java目录则是存放所有你的项目源代码,res目录则存放资源文件,资源文件是什么,就是你的布局文件,字符串文件,图片文件等,都放此目录,当然布局文件存放在layout目录,字符串文件等放在values目录,图标则放在mipmap目录下,图片则放在drawable目录下。最后,还有一个文件:AndroidManifest.xml文件,它是注册四大组件的,还可以添加应用权限。test目录则用来编写Unit Test测试用的,也是进行自动化测试用的。
.gitignore,将app中的文件和目录排除在版本控制之外。build.gradle文件,app模块的gradle构建脚本,指定项目构建相关的配置。(非常重要)
plugins { // 指定了使用的插件,这里是Android应用插件。
id 'com.android.application'
}
android {
namespace 'com.example.helloworld'
compileSdk 32 // 指定项目的编译版本,9是表示使用Android 11(API level 32)的SDK编译
defaultConfig {
applicationId "com.example.helloworld" // 定项目的包名,包名是唯一性,是应用的唯一标识。
minSdk 21 // 指定最低的兼容的Android系统版本
targetSdk 32 // 指定目前使用到最高的Android系统版本
versionCode 1 // 指定项目版本号
versionName "1.0" // 指定项目版本名
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes { // 指定了项目生成安装文件的相关配置
release {
minifyEnabled false // 表示是否使用混淆,而它的下一行则是设置使用的混淆文件
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
} compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies { // 指定了项目所有的依赖关系:本地依赖,库依赖和远程依赖。
// 本地依赖,对本地的jar包和目录添加依赖关系,implementationfileTree声明,将libs中的.jar文件都添加到构建目录中。
// 远程依赖,对jcenter仓库的上的开源项目添加依赖关系,也就是平时我们在github上添加的第三方开源库,也是直接implementation,先检查本地是否有缓存,没有就直接联网下载到构建路径。
// 库依赖,对项目中的库模块进行依赖,implementation project声明,通常格式为implementation project(‘:库名’)
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.google.code.gson:gson:2.8.5'
// implementation "com.squareup.okhttp3:okhttp:4.9.1" // 有了retrofit,把okhttp注释掉也没关系,因为etrofit内部封装了okhttp
implementation "com.squareup.retrofit2:retrofit:2.9.0"
}
proguard-rules.pro,是混淆文件,指定项目代码的混淆规则,为了防止apk文件被别人破解时采取混淆代码。
gradle,顾名思义,就是构建项目的gradle,为了使得构建你的app项目方便管理,使用gradle是最好的。而gradle文件里面包含gradlewrapper配置文件,使用gradle wrapper方式会自动联网下载gradle,,当然AndroidStudio会首先检查本地是否有缓存gradle,没有就会自动联网下载gradle,这样就不用自己先下载gradle,当然如果要使用离线模式,也可以自己setting:File--Settings--Build,Execution,Deployment--Gradle.gitignore文件,将指定的文件排除在版本控制之外。build.gradle,项目全局的gradle构建脚本,也是重要的文件之一。
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false // 两个插件的版本都是7.3.1,并且应用的状态都是false,这意味着这两个插件的配置不会被直接使用,而是可以在子项目或模块的配置文件中引用和应用。
}
gradle.properties,全局的gradle配置文件,这里配置的属性能影响到项目所有的gradle编译脚本。
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "HelloWorld"
include ':app'
gradlew和gradlew.bat,用于在命令行界面下执行gradle命令,gradlew在linux和mac中执行,gradlew.bat则在windows下执行。local.properties,本机中AndroidSDK的路径,一般自动生成,除非发生变化,则要修改setting.gradle,用于指定项目中所有引入的模块。一般可自动生成,也可自行设置,比如引入flutter模块时,可在此文件上设置该模块路径等。- 凡是在Android Studio运行生成的都是测试版安装文件。
- 编译出来的apk存放在
app/build/outputs/apk/deebug/app-debug.apk - 正式版需
Bild—Generate Signed Bundle/Apk里产生。
前端页面编写
- 页面不是关键,大概了解一下即可,
逆向的是java的后端逻辑。
<?xml version="1.0" encoding="utf-8"?>
<!-- xml(可扩展标记语言)被设计用来传输和存储数据;html超文本标记语言被设计用来显示数据。它们都是标准通用标记语言的子集。
xml是独立于软件和硬件的信息传输工具
xml注释不能放在第一行
-->
<!-- 布局换成线性布局 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!-- 线性布局1
- 边框大小 指定高度250dp
- 背景色
- 距离上边大小
- 布局调成垂直方向的 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="#dddddd"
android:layout_marginTop="150dp"
android:orientation="vertical">
<!-- 文本区域 用户登陆
- 边框大小 自适应高度wrap_content
- 文本
- 居中
- 文字大小 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="用户登录"
android:textAlignment="center"
android:textSize="25dp"
android:layout_marginTop="10dp">
</TextView>
<!-- 线性布局2 (#坑 默认水平的,需要把父布局设置成垂直的)
- 边框大小
- 内边距 好看点 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<!-- 文本区域 用户名
- 边框大小
- 文本
- 居中 -->
<TextView
android:layout_width="60dp"
android:layout_height="match_parent"
android:text="用户名:"
android:gravity="center">
</TextView>
<!-- 编辑框
- 给文本编辑框设置id ,“@+id/” 是固定的,后端的java代码可以寻找到id,然后获得输入的用户名
- 边框大小
- 默认是单行
- 文字大小 -->
<EditText
android:id="@+id/txt_user"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="text"
android:singleLine="true"
android:textSize="14dp">
</EditText>
</LinearLayout>
<!-- 线性布局3
- 边框大小
- 内边距 好看点 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:paddingLeft="15dp"
android:paddingRight="15dp">
<!-- 文本区域 密码
- 边框大小
- 文本
- 居中 -->
<TextView
android:layout_width="60dp"
android:layout_height="match_parent"
android:text="密码:"
android:gravity="center">
</TextView>
<!-- 编辑框
- 给密码框设置id ,“@+id/” 是固定的,后端的java代码可以寻找到id,然后获得输入的密码
- 边框大小
- 密码框
- 文字大小 -->
<EditText
android:id="@+id/txt_pwd"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textPassword"
android:singleLine="true"
android:textSize="14dp">
</EditText>
</LinearLayout>
<!-- 线性布局4 登陆
- 居中
- 边框大小 -->
<LinearLayout
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="60dp">
<!-- 按钮 登陆
- 给按钮设置id ,“@+id/” 是固定的,后端的java代码可以寻找到id,然后给按钮绑定事件
- 边框大小 内容有多少就占多少
- 右边距
- 文本 -->
<Button
android:id="@+id/btn_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:text="登录">
</Button>
<!-- 按钮 重置
- 给按钮设置id ,“@+id/” 是固定的,后端的java代码可以寻找到id,然后给按钮绑定事件
- 边框大小 内容有多少就占多少
- 右边距
- 文本 -->
<Button
android:id="@+id/btn_reset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="重置">
</Button>
</LinearLayout>
</LinearLayout>
</LinearLayout>
后端逻辑
package com.example.helloworld;
import androidx.appcompat.app.AppCompatActivity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.Map;
import java.util.TreeMap;
//import okhttp3.Call;
import retrofit2.Call;
import okhttp3.FormBody;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import retrofit2.Retrofit;
public class MainActivity extends AppCompatActivity {
public Button btnReset, btnLogin;
public TextView txtUser, txtPwd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
}
public void initView() {
// 寻找页面上有用的标签
txtUser = findViewById(R.id.txt_user);
txtPwd = findViewById(R.id.txt_pwd);
btnLogin = findViewById(R.id.btn_login);
btnReset = findViewById(R.id.btn_reset);
}
public void initListener() {
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
doLogin();
}
});
btnReset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 点击btn_reset标签的按钮,执行方法
doReset();
}
});
}
public void doReset() {
txtUser.setText("");
txtPwd.setText("");
Log.d("cisco", "点击重置按钮,清空输入框"); // tag,msg 打上tag的好处是在logcat上可以通过tag去过滤日志
/*
System.out日志打印不可控制、打印时间无法确定、不能添加过滤器、日志没有级别区分……,Log比System.out强大。
Log.v(): 这个方法用于打印那些最为琐碎的,意义最小的日志信息。对应级别 verbose,是Android 日志里面级别最低的一种。
Log.d(): 这个方法用于打印一些调试信息,这些信息对你调试程序和分析问题应该是有帮助的。对应级别 debug,比 verbose 高一级。
Log.i(): 这个方法用于打印一些比较重要的数据,这些数据应该是你非常想看到的,可以帮你分析用户行为的那种。对应级别 info,比 debug 高一级。
Log.w(): 这个方法用于打印一些警告信息,提示程序在这个地方可能会有潜在的风险,最好去修复一下这些出现警告的地方。对应级别 warn,比 info 高一级。
Log.e(): 这个方法用于打印程序中的错误信息,比如程序进入到了 catch 语句当中。当有错误信息打印出来的时候,一般都代表你的程序出现严重问题了,必须尽快修复。对应级error,比 warn 高一级
*/ }
public void doLogin() {
Log.d("cisco", "点击登录按钮");
/// 1.获取输入的数据
String username = String.valueOf(txtUser.getText());
String password = String.valueOf(txtPwd.getText());
/*
StringBuilder sb = new StringBuilder(); sb.append(username); sb.append(password); String result = sb.toString(); */
// user=root&pwd=123 TreeMap<String, String> map = new TreeMap<String, String>();
map.put("user", username);
map.put("pwd", password);
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : map.entrySet()) { // 循环得到每一个实体
String key = entry.getKey();
String value = entry.getValue();
sb.append(key);
sb.append("=");
sb.append(value);
sb.append("&");
}
// pwd=11111111&user=123123&
sb.deleteCharAt(sb.length() - 1);
String result = sb.toString();
Log.d("cisco", "加密前的字符串:" + result);
// 3.用md5对用户名密码做一个签名
String signString = md5(result);
map.put("sign", signString);
Log.d("cisco", "加密后的结果:" + signString);
/// 4.1将三个值:用户名、密码、签名 网络请求发送API(校验)
/*
okhttp,安装 & 引入 & 使用(创建一个线程去执行,
不然只有一个线程的话,按下登陆按钮后发起网络请求,此时界面就卡住了,直到线程在等到了返回的数据包,处理完之后界面才恢复正常)
HTTP协议的网络通信框架分为两类:
系统自带的HTTP网络通信库(android9之后,彻底取消了对HTTPClient的支持,所以现在只剩下HttpURLConnection)
系统第三方HTTP网络通信库okhttp3、WebSocket、XMPP、Protobuf、retrofit(内部封装了okhttp,让你用的更加的简单)
1.引入,在build.gradle中 implementation "com.squareup.okhttp3:okhttp:4.9.1" 2.配置,在AndroidManifest.xml中 网络权限<uses-permission android:name="android.permission.INTERNET"/>
3.支持http(仅测试使用)
new Thread() { @Override public void run() {
// 2.发送网络请求,调用OKHttp发送请求 + 创建线程
OkHttpClient client = new OkHttpClient.Builder().build(); // 创建OkHttpClient对象
// user=xxx&pwd=xxxx&sign=xxxx // FormBody body = new FormBody.Builder().add("user", username).add("pwd", password).add("sign", signString).build();
// 创建请求体,以json的形式发送,格式为:{"user":"..."}
JSONObject json = new JSONObject(map); String jsonString = json.toString(); RequestBody body = RequestBody.create(MediaType.parse("application/json;charset=utf-8"),jsonString);
Request req = new Request.Builder().url("http://192.168.0.8:9000/auth").post(body).build(); Call call = client.newCall(req); try { // 网络等因素可能会导致失败,需要捕获异常
Response res = call.execute(); // 发送请求
ResponseBody resBody = res.body(); // 获取响应体
String dataString = resBody.string(); Log.d("请求发送成功", dataString);
} catch (IOException ex) { Log.d("----->", "请求失败");
}
// 3.获取结果
Log.d("------->", "点击登录按钮");
} }.start(); */
/* // 创建拦截器
Interceptor interceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException {
// 1988812212 + 固定字符串 => md5加密
// 拦截请求后,往请求头加入一些额外的新参数"ts": "1988812212"、"sign":"xxxx"
Request request = chain.request().newBuilder().addHeader("ts", "1988812212").addHeader("sign", "xxxx").build();
// 请求前
Response response = chain.proceed(request); // 继续执行后面的拦截器
// 请求后
return response; } }; 逆向场景:抓包发现,传了好多参数,且参数与其他请求重合比较多,可能是拦截器中实现。
思路:jadx反编译 + 关键字搜索: Interceptor new Thread() { @Override public void run() {
// 2.发送网络请求,调用OKHttp发送请求 + 创建线程
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build(); // 创建OkHttpClient对象,所有的请求都会经过拦截器
// user=xxx&pwd=xxxx&sign=xxxx // FormBody body = new FormBody.Builder().add("user", username).add("pwd", password).add("sign", signString).build();
// 创建请求体,以json的形式发送,格式为:{"user":"..."}
JSONObject json = new JSONObject(map); String jsonString = json.toString(); RequestBody body = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), jsonString);
Request req = new Request.Builder().url("http://192.168.0.8:9000/auth").post(body).build(); Call call = client.newCall(req); try { // 网络等因素可能会导致失败,需要捕获异常
Response res = call.execute(); // 发送请求
ResponseBody resBody = res.body(); // 获取响应体
String dataString = resBody.string(); Log.d("请求发送成功", dataString);
} catch (IOException ex) { Log.d("----->", "请求失败");
}
// 3.获取结果
Log.d("------->", "点击登录按钮");
} }.start(); */
/// 4.3 Retrofit new Thread(){
@Override
public void run() {
// 先在工程文件中定义一个接口HttpReq,在接口里面声明好这些字段,比如@POST("/api/v1/post")
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://192.168.0.8:9000/").build(); // 创建Retrofit对象
HttpReq req = retrofit.create(HttpReq.class);
// Call<ResponseBody> call = req.postLogin("cisco","123123");
// 创建请求体,以json的形式发送,格式为:{"user":"..."}
JSONObject json = new JSONObject(map);
String jsonString = json.toString();
RequestBody form = RequestBody.create(MediaType.parse("application/json;charset=utf-8"),jsonString);
Call<ResponseBody> call = req.postLoginJson(form); // 发送请求
try { // 网络等因素可能会导致失败,需要捕获异常
ResponseBody responseBody = call.execute().body(); // 发送请求,获取响应体
String responseString = responseBody.string();
Log.d("cisco", "请求发送成功:" + responseString);
// {"code":1000,"data":"success","token":"dfoui3io4j9s7djksldjflskd"}
// 反序列化,字符串 -> 对象 序列化,对象 -> 字符串类型
// 需要在工程文件中自定义一个类,注明反序化后的json的字段
HttpResponse obj = new Gson().fromJson(responseString,HttpResponse.class);
// token保存本地xml文件
// /data/data/com.example.helloworld;
SharedPreferences sp = getSharedPreferences("s5_city", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("token",obj.token); // token 类似浏览器中cookie,但anroid没有cookie,token是保存到xml文件中的
editor.commit();
// 读
SharedPreferences sp2 = getSharedPreferences("s5_city", MODE_PRIVATE);
String token = sp2.getString("token","");
Log.d("cisco", "Retrofit返回的结果:"+ token);
} catch (Exception e) {
e.printStackTrace();
Log.d("cisco", "网络请求异常");
}
}
}.start();
}
/**
* md5加密
*
* @param dataString 待加密的字符串
* @return 加密结果
*/
private String md5(String dataString) {
try {
MessageDigest instance = MessageDigest.getInstance("MD5");
byte[] nameBytes = instance.digest(dataString.getBytes());
// 十六进制展示
StringBuilder sb = new StringBuilder();
for (int i = 0; i < nameBytes.length; i++) {
int val = nameBytes[i] & 255; // 负数转换为正数
if (val < 16) {
sb.append("0");
}
sb.append(Integer.toHexString(val));
}
return sb.toString();
} catch (Exception e) {
return null;
}
}
}
使用okhttp
- 引入,在build.gradle中添加依赖
implementation "com.squareup.okhttp3:okhttp:4.9.1" - 配置,在AndroidManifest.xml中
<uses-permission android:name="android.permission.INTERNET"/> - 支持http(仅测试使用)
使用retrofit
- 在项目的 build.gradle 文件中添加依赖
implementation "com.squareup.retrofit2:retrofit:2.9.0" - 配置,在AndroidManifest.xml中
<uses-permission android:name="android.permission.INTERNET"/> - 支持http(仅测试使用)第2,3步okhttp已经配置过了
- 要在工程文件中定义一个接口HttpReq,在接口里面声明好这些字段,
// HttpReq.java
package com.example.helloworld;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
public interface HttpReq {
// 向/auth 以form格式发送POST请求 name=xx&pwd=xxx
@POST("/auth")
@FormUrlEncoded
Call<ResponseBody> postLogin(@Field("name") String userName, @Field("pwd") String password);
// 向/users 以json格式发送POST请求 name=xx&pwd=xxx
@POST("/users")
Call<ResponseBody> postLoginJson(@Body RequestBody body);
}
#坑/逆向/app逆向/jadx/url定位 逆向场景:根据url定位的时候发现是使用Retrofit发送请求::则找对应的调用的地方,比如req.postLogin,就可以看到传的参数是什么了
拦截器
#坑/逆向/app逆向/jadx/参数定位 逆向场景:抓包发现,传了好多参数,且参数与其他请求重合比较多,可能是拦截器中实现。 ::jadx反编译 + 关键字搜索: Interceptor
- 拦截器的原理是通过链式结构对请求/响应进行拦截和修改,从而实现多种功能。
- 对于网络请求,通常会使用网络请求框架(如 Retrofit)来实现拦截器。该框架会提供一个链式结构,允许用户在该链上添加多个拦截器。当网络请求发送时,这些拦截器依次被调用,允许拦截请求/响应并对其进行修改。
- 每个拦截器通常都实现了一个
Interceptor接口,该接口定义了两个方法:intercept()和proceed()。当请求发送时,intercept()方法被调用,该方法可以对请求进行修改。当该方法调用proceed()方法时,下一个拦截器被调用,直到所有拦截器都被处理完毕。
序列化和反序列化
- 序列化 (Serialization) 是指将数据结构或对象状态转换为可存储或传输的格式的过程。
- 反序列化 (Deserialization) 是指将存储或传输的格式重新转换为数据结构或对象状态的过程。
- 序列化和反序列化通常用于将数据在不同的系统之间进行存储和传输。
#坑/java/库/Gson 无法导入 Gson 库::在项目的 build.gradle 文件中添加依赖
implementation 'com.google.code.gson:gson:2.8.5'
- 需要在工程文件中自定义一个类,注明
反序化后的json的字段
// HttpResponse.java
package com.example.helloworld;
public class HttpResponse {
public int code;
public String data;
public String token;
}
本地xml文件
#坑/逆向/app逆向/jadx/参数定位/device_id 逆向场景:SharedPreferences:: 1.app启动时,向后端API发送请求:设备信息->device_id 2.device_id写入本地的XML 3.再次发送请求时,携带deviceid=?
flask 后端API
from flask import Flask,request, jsonify
# 创建应用实例
app = Flask(__name__) # 'Flask' 参数是 应用程序模块 或 包 的名称
# __name__是适用于大多数情况的便捷快捷方式
# 路由 (装饰器)
# @app.route('/') # route()装饰器告诉 Flask 什么路径触发下面的功能
# def hello():
# # 该函数返回我们想要在浏览器中显示的消息内容
# return 'Hello World!'
# # 默认类型 HTML, 因此字符串中的 HTML 将被浏览器渲染
# 当有人访问http://127.0.0.1:9000/auth 就会执行这个auth()函数
@app.route('/auth', methods=["GET", "POST"]) # 默认路由仅响应 GET 请求。可以使用 route() 装饰器的方法参数来处理不同的 HTTP 方法
def auth():
# 1.获取数据
print(request.json)
# print(request.form)
# print(request.headers)
# 2.校验签名的合法性
# 3.业务处理
return jsonify({"code": 1000, 'data': "success","token":"dfoui3io4j9s7djksldjflskd"}) # 返回json
# http://127.0.0.1:9000/users
@app.route('/users', methods=["GET", "POST"])
def users():
# 1.获取数据
print(request.json)
# print(request.headers)
# 2.校验签名的合法性
# 3.业务处理
return jsonify({"code": 1000, 'data': "success","token":"dfoui3io4j9s7djksldjflskd"})
if __name__ == '__main__':
# app.run(host="127.0.0.1", port=9000) # 回环地址只能在本机上调试使用
app.run(host="192.168.0.8", port=9000) # 如果电脑和手机处于一个局域网,改成PC的ip,手机才能访问到
# app.run(host="0.0.0.0", port=5000) # 表示启动Flask应用并监听0.0.0.0(代表所有网络接口)地址上的9000端口。这意味着你可以从任何机器上访问这个Flask应用,只要通过 <your-ip-address>:9000 访问
二、Android正向开发
- 了解框架、基本控件(文本框、编辑框、按钮、列表相关的控件)、布局活动组件
- 服务组件、广播接受者、内容提供者
- 文件存储、xml存储、数据库网络数据存储
1. 概述
android系统发展
- Android系统最初由
安迪·鲁宾等人开发制作,最初开发。这个系统的目的是创建一个数码相机的先进操作系统,后来发现市场需求不够大,加上智能手机市场快速成长,于是Android被改造为一款面向智能手机的操作系统。Android于2005年8月被美国科技企业Google收购。
Android发展历程请参见维基百科词条:zh.wikipedia.org/wiki/Androi…
android框架
- 应用程序框架可以说是一个应用程序的核心,是所有参与开发的程序员共同使用和遵守的约定,大家在其约定上进行必要的扩展,但程序始终保持主体结构的一致性。其作用是让程序保持清晰和一目了然,在满足不同需求的同时又不互相影响。
- Android分为应用层、应用框架层、系统运行库层和Linux内核层。我们在开发应用时都是通过框架来与Android底层进行交互,接触最多的就是应用框架层了。
- Android系统提供给应用开发者的本身就是一个框架,所有的应用开发都必须遵守这个框架的原则。我们在开发应用时就是在这个框架上进行扩展。Android应用框架功能如下。
android.app:提供高层的程序模型和基本的运行环境。android.content:包含对各种设备上的数据进行访问和发布。android.database:通过内容提供者浏览和操作数据库。android.graphics:底层的图形库,包含画布、颜色过滤、点、矩形,可以将它们直接绘制到屏幕上。android.location:定位和相关服务的类。android.media:提供一些类管理多种音频、视频的媒体接口。android.net:提供帮助网络访问的类,超过通常的java.net.接口。android.os:提供了系统服务、消息传输和IPC机制。android.opengl:提供OpenGL的工具。android.provider:提供访问Android内容提供者的类。android.telephony:提供与拨打电话相关的API交互。android.view:提供基础的用户界面接口框架。android.util:涉及工具性的方法,例如时间日期的操作。android.webkit:默认浏览器操作接口。android.widget:包含各种UI元素(大部分是可见的)在应用程序的布局中。
AS快捷键
Alt+回车导入包,自动修正Ctrl+Alt+O优化导入的类和包Ctrl+E或者Alt+Shift+C最近更改的代码Shift+Click可以关闭文件Ctrl+[或]可以跳到大括号的开头结尾Ctrl+Shift+Backspace可以跳转到上次编辑的地方Ctrl+F12,可以显示当前文件的结构Alt+F7全局搜索字符串Ctrl+F7可以查询当前元素在当前文件中的引用,然后按F3可以选择Ctrl+N,查找类Ctrl+Shift+N查找文件Ctrl+R替换文本Ctrl+F查找文本Alt+Q可以看到当前方法的声明Ctrl+W可以选择单词继而语句继而行继而函数Alt+F1可以将正在编辑的元素在各个面板中定位Ctrl+P,可以显示参数信息Ctrl+Alt+Space类名或接口名提示Ctrl+Shift+Insert可以选择剪贴板内容并插入Alt+Insert生成代码(如get,set方法,构造函数等)Ctrl+Alt+L格式化代码Ctrl+/或Ctrl+Shift+/注释(//或者/*...*/)Ctrl+Alt+V可以引入变量。例如把括号内的SQL赋成一个变量Ctrl+Alt+T可以把代码包在一块内,例如try/catchAlt+Up and Alt+Down可在方法间快速移动F2 或Shift+F2高亮错误或警告快速定位Alt+Shift+C对比最近修改的代码Ctrl+H显示类结构图Ctrl+Q显示注释文档Alt+F1查找代码所在位置Ctrl+Shift+向上键移动行Ctrl+Y删除行ctrl+x是剪切行。如果不选中,则为剪切当行。)Ctrl+D复制行
2. 布局和控件
View(视图)是什么?
- 在Activity显示的控件 都叫做View(View类 是所有的控件类的父类,比如文本、按钮)。
- View类是Android的一个
超类,这个类几乎包含了所有的屏幕类型。每一个View都有一个用于绘图的画布,这个画布可以进行任意扩展。 - 在游戏开发中也可以自定义视图(View),这个画布的功能更能满足我们在游戏开发中的需要。
- 在Android中,任何一个View类都只需重写
onDraw方法来实现界面显示,自定义的视图可以是复杂的3D实现,也可以是非常简单的文本形式等。 - 游戏中最重要的就是需要与玩家交互,比如键盘输入、触笔点击事件,我们如何来处理这些事件呢?
- Android中提供了 onKeyUp、onKeyDown、onKeyMultiple、onKeyPreIme、onTouchEvent、onTrackballEvent等方法,可以轻松地处理游戏中的事件信息。
- 所以,在继承View时,需要
重载这几个方法,当有按键按下或弹起等事件时,按键代码自动会传输给这些相应的方法来处理。 - 游戏的核心是不断地
绘图和刷新界面,我们已经通过onDraw方法绘制了,下面来分析如何刷新界面。 - Android中提供了
invalidate方法来实现界面刷新,注意,invalidate 不能直接在线程中调用, 就是不可以在子线程中调用? - 因为它违背了 Android的
单线程模型:Android UI操作并不是线程安全的,并且这些操作必须在UI 线程中执行,因此Android中最常用的方法就是利用Handler来实现UI线程的更新。 其实用 AsyncTask 也可以。
View(视图)的通用属性
- android:background
- 设置背景色/背景图片。可以通过以下两种方法设置背景为透明:
"@android:color/transparent"和"@null"。 - 注意TextView默认是透明的,不用写此属性,但是Buttom/ImageButton/ImageView想透明的话就得写这个属性了。
- android:clickable
- 是否响应点击事件。
- android:contentDescription
- 设置View的备注说明,作为一种辅助功能提供,为一些没有文字描述的View提供说明,如ImageButton。这里在界面上不会有效果,自己在程序中控制,可临时放一点字符串数据。
- android:drawingCacheQuality
- 设置绘图时半透明质量。
- 有以下值可设置:
- auto(默认,由框架决定)
- high(高质量,使用较高的颜色深度,消耗更多的内存
- low(低质量,使用较低的颜色深度,但是用更少的内存)。
- android:duplicateParentState
- 如果设置此属性,将直接从父容器中获取绘图状态(光标,按下等)。
- 注意根据目前测试情况仅仅是获取绘图状态,而没有获取事件,也就是你点一下LinearLayout时Button有被点击的效果,但是不执行点击事件。
- android:fadingEdge
- 设置拉滚动条时 ,边框渐变的放向。none(边框颜色不变),horizontal(水平方向颜色变淡),vertical(垂直方向颜色变淡)。
- android:fadingEdgeLength
- 设置边框渐变的长度。
- android:fitsSystemWindows
- 设置布局调整时是否考虑系统窗口(如状态栏)
- android:focusable
- 设置是否获得焦点。
- 若有requestFocus()被调用时,后者优先处理。
- 注意在表单中想设置某一个如EditText获取焦点,光设置这个是不行的,需要将这个EditText前面的focusable都设置为false才行。在Touch模式下获取焦点需要设置focusableInTouchMode为true。
- android:focusableInTouchMode
- 设置在Touch模式下View是否能取得焦点。
- android:id
- 给当前View设置一个在当前layout.xml中的唯一编号,可以通过调用View.findViewById() 或Activity.findViewById()根据这个编号查找到对应的View。
不同的layout.xml之间定义相同的id不会冲突。格式如”@+id/btnName”
- android:keepScreenOn
- View在可见的情况下是否保持唤醒状态。常在
LinearLayout使用该属性,但是模拟器这里没有效果。
- android:longClickable
- 设置是否响应长按事件.
- android:minHeight
- 设置视图最小高度
- android:minWidth
- 设置视图最小宽度
- android:onClick
- 点击时从上下文中调用指定的方法。
- 这里指定一个方法名称,一般在Activity
定义符合如下参数和返回值的函数并将方法名字符串指定为该值即可:
public void onClickButton(View view)
android:onClick = "onClickButton"
- android:padding
- 设置上下左右的边距,以像素为单位填充空白。
- android:paddingBottom
- 设置底部的边距,以像素为单位填充空白。
- android:paddingLeft
- 设置左边的边距,以像素为单位填充空白。
- android:paddingRight
- 设置右边的边距,以像素为单位填充空白。
- android:paddingTop
- 设置上方的边距,以像素为单位填充空白。
- android:scrollX
- 以像素为单位设置水平方向滚动的的偏移值,在GridView中可看的这个效果。
- android:scrollY
- 以像素为单位设置垂直方向滚动的的偏移值
- android:scrollbarDefaultDelayBeforeFade
- 设置N毫秒后开始淡化,以毫秒为单位。
- android:scrollbarFadeDuration
- 设置滚动条淡出效果(从有到慢慢的变淡直至消失)时间,以毫秒为单位。
- Android2.2中滚动条滚动完之后会消失,再滚动又会出来,在1.5、1.6版本里面会一直显示着
- android:scrollbarSize
- 设置滚动条的宽度。
- android:scrollbarStyle
- 设置滚动条的风格和位置。设置值:insideOverlay、insideInset、outsideOverlay、outsideInset。
- android:scrollbarThumbHorizontal
- 设置水平滚动条的drawable。
- android:scrollbarThumbVertical
- 设置垂直滚动条的drawable.
- android:scrollbarTrackHorizontal
- 设置水平滚动条背景(轨迹)的色drawable
- android:scrollbars
- 设置滚动条显示。none(隐藏),horizontal(水平),vertical(垂直)。见下列代码演示使用该属性让EditText内有滚动条。但是其他容器如LinearLayout设置了但是没有效果。
- android:soundEffectsEnabled
- 设置点击或触摸时是否有声音效果
- android:tag
- 设置一个文本标签。可以通过View.getTag()或 for with View.findViewWithTag()检索含有该标签字符串的View。
- 但一般最好通过ID来查询View,因为它的
速度更快,并且允许编译时类型检查。
- android:visibility
- 设置是否显示View。
- 设置值:
- visible(默认值,显示)
- invisible(不显示,但是仍然占用空间)
- gone(不显示,不占用空间)
基本UI控件
TextView(文本框)
- id:为TextView设置一个组件id
- 根据id,我们可以在Java代码中通过findViewById()的方法获取到该对象,然后进行相关属性的设置,又或者使用RelativeLayout时,参考组件用的也是id。
- layout_width:组件的宽度
- 一般写:wrap_content或者match_parent(fill_parent),前者是控件显示的内容多大,控件就多大,而后者会填满该控件所在的父容器;
- 当然也可以设置成特定的大小,比如我这里为了显示效果,设置成了200dp。
- layout_height:组件的宽度,内容同上。
- gravity:设置控件中内容的对齐方向,TextView中是文字,ImageView中是图片等等。
- text:设置显示的文本内容,一般我们是把字符串写到string.xml文件中,然后通过@String/xxx取得对应的字符串内容的,这里为了方便我直接就写到""里,不建议这样写。
- textColor:设置字体颜色,同上,通过colors.xml资源来引用,别直接这样写。
- textStyle:设置字体风格,三个可选值:normal(无效果),bold(加粗),italic(斜体)。
- textSize:字体大小,单位一般是用sp。
- background:控件的背景颜色,可以理解为填充整个控件的颜色,可以是图片。
EditText(输入框)
- android:layout_gravity="center_vertical" 设置控件显示的位置:默认top,这里居中显示,还有bottom android:hint="请输入数字!"
- decimal android:singleLine="true" 设置单行输入,一旦设置为true,则文字不会自动换行。
- android:password="true" 设置只能输入密码
- android:textColor = "#ff8c00" 字体颜色
- android:textStyle="bold" 字体,bold, italic, bolditalic android:textSize="20dip" 大小
- android:textAlign="center" EditText没有这个属性,但TextView有
- android:textColorHighlight="#cccccc" 被选中文字的底色,默认为蓝色
- android:hint="提示信息"
- android:textColorHint="#ffff00" 设置提示信息文字的颜色,默认为灰色
- android:textScaleX="1.5" 控制字与字之间的间距
- android:typeface="monospace" 字型,normal, sans, serif, monospace
- android:background="@null" 空间背景,这里没有,指透明
- android:layout_weight="1" 权重,控制控件之间的地位,在控制控件显示的大小时蛮有用的。
- 密码框属性 android:password="true" 这条可以让EditText显示的内容自动为星号,输入时内容会在1秒内变成*字样。
- 纯数字 android:numeric="true" 这条可以让输入法自动变为数字输入键盘,同时仅允许0-9的数字输入
- 仅允许 android:capitalize="cwj1987" 这样仅允许接受输入cwj1987,一般用于密码验证
- android:editable="false" 设置EditText不可编辑
- android:singleLine="true" 强制输入的内容在单行
- android:ellipsize="end" 自动隐藏尾部溢出数据,一般用于文字内容过长一行无法全部显示时
Button(按钮)
- android:text=""---控件上的文本内容
- android:onClick="doClick"---点击此控件时调用的方法---方法名称为:doClick
- android:drawableTop=""---在Button组件上放置图
ImageButton(图像按钮)
android:src="@drawable/ "//图片地址
ImageView(图像视图)
android:src="@drawable/ "//图片地址
RadioButton(单选按钮)
CheckBox(复选框)
四大布局
LinearLayout(线性布局)的概念和属性
- LinearLayout(线性布局)的概念
- LinearLayout 线性布局有两种,分别是
水平线性布局和垂直线性布局 - LinearLayout属性android:orientation为设置
线性布局,当其="vertical"时,为垂直线性布局,当其="horizontal"时,为水平线性布局。
- LinearLayout(线性布局)的属性
- a. android:orientation
- 设置LinearLayout容器布局组件的方式:要么按行要么按列。只能取值:horizontal、vertical。
- b.android:gravity
- android:gravity属性是对该view 内容的限定.
- 比如一个button 上面的text. 你可以设置该text 在view的靠左,靠右等位置.
- 以button为例,android:gravity="right"则button上面的文字靠右
- c.android:layout_gravity
- android:layout_gravity是用来设置该view相对与起父view 的位置.
- 比如一个button 在linearlayout里,你想把该button放在靠左、靠右等位置就可以通过该属性设置.
- 以button为例,android:layout_gravity="right"则button靠右
- d.所有放置在LinearLayout中的组件都必须通过
android:layout_width和android:layout_height属性来告知LinearLayout如何对组件进行布局。有三个可选的值:- match_parent----占满父容器的所有空间;
- wrap_content ----组件将只占用为了正确显示器内容所需的空间。
- e.weight 属性,权值
- android:layout_weight 设置组件占用容器的空余显示空间的比例。
- 在使用垂直布局的情况下,使用android:layout_weight时,需要设置android:layout_height的值为0;
- 在使用水平布局的情况下,使用android:layout_weight时,需要设置android:layout_width的值为0。
- 在LinearLayout下才有android:layout_weight 属性,主要是为了按百分比进行布局
- f.关于margin 与padding 的区别问题
- padding是站在父view的角度描述问题,是自己的内容与其父控件的边之间的距离。
- margin则是站在自己的角度描述问题,自己与旁边的某个组件的距离,如果同一级只有一个view,那么它的效果基本上就和padding一样了。
RelativeLayout(相对布局)的概念和属性
- RelativeLayout(相对布局)的概念
- a.RelativeLayout是一个允许子视图相对于其他兄弟视图或是父视图显示的视图组(通过ID指定)。
- 每个视图的位置能够指定它相对于兄弟(比如在其他视图的左边或是下边)或是父视图(这里是指相对布局容器,比如底部对齐、中间偏左)的位置。
- b.RelativeLayou是一个用于设计用户界面的强大工具,因为它能消除嵌套视图组和保持我们布局为扁平结构,这可以提高运行时性能。
- 如果我们采用了多个嵌套的LinearLayout组,我们应该采用一个单独的RelativeLayout来替代。
- RelativeLayout(相对布局)的属性
- 第一类:属性值为true或false
- android:layout_centerHrizontal 水平居中
- android:layout_centerVertical 垂直居中
- android:layout_centerInparent 相对于父元素完全居中
- android:layout_alignParentBottom 贴紧父元素的下边缘
- android:layout_alignParentLeft 贴紧父元素的左边缘
- android:layout_alignParentRight 贴紧父元素的右边缘
- android:layout_alignParentTop 贴紧父元素的上边缘
- android:layout_alignWithParentIfMissing 如果对应的兄弟元素找不到的话,就以父元素做参照物
- 第二类:属性值必须为id的引用名“@id/id-name”
- android:layout_below 在某元素的下方
- android:layout_above 在某元素的的上方
- android:layout_toLeftOf 在某元素的左边
- android:layout_toRightOf 在某元素的右边
- android:layout_alignTop 本元素的上边缘和某元素的的上边缘对齐
- android:layout_alignLeft 本元素的左边缘和某元素的的左边缘对齐
- android:layout_alignBottom 本元素的下边缘和某元素的的下边缘对齐
- android:layout_alignRight 本元素的右边缘和某元素的的右边缘对齐
FrameLayout(帧布局)的概念和属性
- FrameLayout(帧布局)的概念
- 作为android四大布局中最为简单的布局之一,该布局直接在屏幕上开辟出了一块
空白区域,当我们往里面添加组件的时候,所有的组件都会放置于这块区域的左上角; - 帧布局的大小由子控件中最大的子控件决定,如果组件都一样大的话,同一时刻就只能看到最上面的那个组件了!
- 当然我们也可以为组件添加layout_gravity属性,从而制定组件的对其方式,帧布局在游戏开发方面用的比较多。
- FrameLayout(帧布局)的属性
- android:foreground:设置该帧布局容器的前景图像
- android:foregroundGravity:设置前景图像显示的位置
TableLayout(表格布局)的概念和属性
- TableLayout(表格布局)的概念
- TableLayout跟TableRow 是一组搭配应用的布局
- TableLayout置底TableRow在TableLayout的上方,而Button、TextView等控件就在TableRow之上,别的,TableLayout之上也可以零丁放控件。
- TableLayout是一个应用错杂的布局,最简单的用法就仅仅是拖沓控件做出个界面,但实际上,会经常在代码里应用TableLayout,例如做出表格的结果。
- TableLayout(表格布局)的概念和属性
- a.android:collapseColumns://隐藏指定的列
- 设置 TableLayout 内的 TableRow 中需要隐藏的列的列索引,多个用“,”隔开 ;
- 以第0行为序,隐藏指定的列:把android:collapseColumns=0,3 意思是把第0和第3列隐藏。
- b.android:shrinkColumns://收缩指定的列以适合屏幕、不会挤出屏幕
- 设置 TableLayout 内的 TableRow 中需要拉伸(该列会拉伸到所有可用空间)的列的列索引,多列个用“,”隔开(多列 每列填充空隙大小一样)
- 以第0行为序,自动延伸指定的列填充可用部分: 当LayoutRow里面的控件还没有布满布局时,shrinkColumns不起作用。
- 设置了shrinkColumns=1,4,布局完全没有改变,因为LayoutRow里面还剩足够的空间。当LayoutRow布满控件时,设置了shrinkColumns=2,5,则控件自动向垂直方向填充空间。
- c.android:stretchColumns://尽量把指定的列表填充空白部分
- 设置 TableLayout 内的 TableRow 中需要收缩(为了使其他列不会被挤到屏幕 外,此列会自动收缩)的列的列索引,多个用“,”隔开
- 以第0行为序,尽量把指定的列填充空白部分:设置stretchColumns=2,5,第1,4列被尽量填充同时向右填充,直到2,5被压挤到最后边)。
- d. 补充:
- 表格布局的子对象不能指定 layout_width 属性.宽度永远是 MATCH_PARENT。
- 不过子对象可以定义 layout_height 属性;其默认值是WRAP_CONTENT.
- 如果子对象是 TableRow,其高度永远是 WRAP_CONTENT。
3. Activity(活动)
四种任务栈
Activity生命周期
4. Intent(组件之间的信息专递)
Intent理解和使用
Intent简介
一个Intent是对一个即将进行的操作的抽象,Intent的字面意识就是”意图”- Android应用程序中的三种其他应用程序基本组件:Activity, Service和Broadcast Receiver,都是使用称为intent的消息来”激活”的。
- Intent代表了Android应用的启动”意图”,Android应用将会根据Intent来启动指定组件,至于到底启动哪个组件,则取决于Intent的各个属性。
Inent的使用
- Intent是由Component、Action、Data、Category、Extra及Flag六部分组成的,分别详细介绍:
- Component
- 组件名称实际上就是一个ComponentName对象,用于标识唯一的应用程序组件,即指明了期望的Intent组件,这种对象的名称是由目标组件的类名与目标组件的包名组合而成的。
- 在Intent传递过程中,组件名称是一个可选项,当指定它时,便是显式的Intent消息,我们称为“显示意图”,当不指定它时,Android系统则会根据其他信息及IntentFilter的过滤条件选择相应的组件,我们称之为 “隐式意图”。
// 创建一个ComponentName对象
ComponentName componentName = new ComponentName(
IntentDemoActivity.this, SecondActivity.class);
Intent intent = new Intent();
// 设置Intent的Component属性
intent.setComponent(componentName);
// 启动SecondActivity
startActivity(intent);
// 上面的代码其实完全可以简化为:
Intent intent = new Intent(IntentDemoActivity.this, SecondActivity.class);
// 启动SecondActivity
startActivity(intent);
// 也可以写为:
Intent intent=new Intent()
intent.setClass(IntentDemoActivity.this, SecondActivity.class)
startActivity(intent)
- Action
- Action实际上就是一个描述了Intent所触发动作名称的字符串,在Intent类中,已经定义好多字符串常量来表示不同的Action
- 当然,开发人员也可以自定义Action,其定义规则同样非常简单。动作名必须是独一无二的字符串,所以,一个好的习惯是使用基于Java包的命名方式的命名系统。
- 系统定义的Action常量有很多,下面只列出其中一些较常见的。
- ACTION_CALL,拨出Data里封装的电话号码。
- ACTION_EDIT,打开Data里指定数据所对应的编码程序。
- ACTION_VIEW,打开能够显示Data中封装的数据的应用程序。
- ACTION_MAIN,声明程序的入口,该Action并不会接收任何数据,同时结束后也不会返回任何数据。
- ACTION_BOOT_COMPLETED,BroadcastReceiver Action的常量,表明系统启动完毕。
- ACTION_TIME_CHANGED,BroadcastReceiver Action的常量,表示系统时间通过设置而改变。
//声明一个Intent对象
Intent intent = new Intent();
/**
* 设置Action属性,这里是跳到拨号界面
* ACTION_DIAL = "android.intent.action.DIAL";
*/
intent.setAction(Intent.ACTION_DIAL);
startActivity(intent);
- Action很大程度上决定了Intent的另外部分的结构, 就像一个方法名决定了它接受的参数和返回值一样.
- 因此, 最好给Action一个最能反映其作用的名字.一个Intent对象中的Action是使用getAction()和setAction()来读写的.
- Data
- Data主要是对Intent消息中数据的封装,主要描述Intent的动作所操作到的数据的URI及类型。
- 不同类型的Action会有不同的Data封装,例如打电话的Intent会封装tel://格式的电话URI,而ACTION_VIEW的Intent中Data则会封装http:格式的URI。
- 正确的Data封装对Intent匹配请求同样非常重要。
//声明一个Intent对象
Intent intent = new Intent();
/**
* 设置Action属性,这里是跳到拨号界面
* ACTION_DIAL = "android.intent.action.DIAL";
*/
intent.setAction(Intent.ACTION_DIAL);
Uri uri = Uri.parse("tel:0-123-456-789");
intent.setData(uri);
startActivity(intent);
- Category
- Category是对目标组件类别信息的描述。
- 同样作为一个字符串对象,一个Intent中可以包含多个Category。与Category相关的方法有三个,
- addCategory添加一个Category,
- removeCategory删除一个Category,
- 而getCategories得到一个Category。
- Android系统同样定义了一组静态字符常量来表示Intent的不同类别,下面列出一些常见的Category常量。
- CATEGORY_GADGET,表示目标Activity是可以嵌入到其他Activity中的。
- CATEGORY_HOME,表明目标Activity为HOME Activity。
- CATEGORY_TAB,表明目标Activity是TabActivity的一个标签下的Activity。
- CATEGORY_LAUNCHER,表明目标Activity是应用程序中最先被执行的Activity。
- CATEGORY_PREFERNCE,表明目标Activity是一个偏好设置的Activity。
- 一个Intent最多只能包含一个Action属性,但是一个Intent中可以包含多个Category属性。
ExtraExtra中封装了一些额外的附加信息,这些信息是以键值对的形式存在的。
- Intent可以通过putExtras()与getExtras()方法来存储和获取Extra。在Android系统的Intent类中,同样对一些常用的Extra键值进行了定义,如下所示。
- EXTRA_BCC,装有邮件密送地址的字符串数组。
- EXTRA_EMAIL,装有邮件发送地址的字符串数组。
- EXTRA_UID,使用ACTION_UID_REMOVED动作时,描述删除用户的id。
- EXTRA_TEXT,当使用ACTION_SEND动作时,描述要发送文本的信息。
Flag一些有关系统如何启动组件的标志位,Android同样对其进行了封装。
IntentFiter理解和使用
IntentFiter概念
- IntentFilter实际上相当于
Intent的过滤器,一个应用程序开发完成后,需要告诉Android系统自己能够处理哪些隐形的Intent请求,这就需要声明IntentFilter。 - IntentFilter的使用方法实际上非常简单,仅声明该应用程序接收什么样的Intent请求即可。
IntentFiter详细介绍
- IntentFilter过滤Intent时,一般是通过Action、Data及Category三方面进行监测的。
- 检查Action一个Intent只能设置一种Action,但是一个IntentFilter却可以设置多个Action过滤。
- 当IntentFilter设置了多个Action时,只需一个满足即可完成Action验证。
- 当IntentFilter中没有说明任何一个Action时,那么任何的Action都不会与之匹配。
- 而如果Intent中没有包含任何Action,那么只要IntentFilter中含有Action时,便会匹配成功。
- 检查Data数据的监测主要包含两部分,即数据的URI及数据类型,而数据URI又被分成三部分进行匹配(scheme、authority、path),只有这些全部匹配时,Data的验证才会成功。
- 检查CategoryIntentFilter同样可以设置多个Category
- 当Intent中的Category与IntentFilter中的一个Category完全匹配时,便会通过Category的检查,而其他的Category并不受影响。
- 但是当IntentFilter没有设置Category时,只能与没有设置Category的Intent相匹配。
IntentFiter的使用
- 为了注册一个应用程序组件为 Intent 处理者,在组件的 manifest 节点添加一个 intent-filter 标签。
- 在 Intent Filter 节点里使用下面的标签(关联属性),你能指定组件支持的动作、种类和数据:
- 动作测试
<intent-filter>元素中可以包括子元素<action>,比如:
< intent-filter >
< action android:name="com.example.project.SHOW_CURRENT" />
< action android:name="com.example.project.SHOW_RECENT" />
< action android:name="com.example.project.SHOW_PENDING" />
</ intent-filter >
- 一条
<intent-filter>元素至少应该包含一个<action>,否则任何Intent请求都不能和该<intent-filter>匹配。 - 如果Intent请求的Action和
<intent-filter>中个某一条<action>匹配,那么该Intent就通过了这条<intent-filter>的动作测试。 - 如果Intent请求或
<intent-filter>中没有说明具体的Action类型,那么会出现下面两种情况。 a. 如果<intent-filter>中没有包含任何Action类型,那么无论什么Intent请求都无法和这条<intent-filter>匹配; b.反之,如果Intent请求中没有设定Action类型,那么只要<intent-filter>中包含有Action类型,这个Intent请求就将顺利地通过<intent-filter>的行为测试。
- 类别测试
<
intent-filter>元素可以包含<category>子元素,比如:
< intent-filter . . . >
< category android:name="android.Intent.Category.DEFAULT" />
< category android:name="android.Intent.Category.BROWSABLE" />
</ intent-filter >
- 只有当Intent请求中所有的Category与组件中某一个IntentFilter的
<category>完全匹配时,才会让该 Intent请求通过测试,IntentFilter中多余的<category>声明并不会导致匹配失败。 - 一个没有指定任何类别测试的 IntentFilter仅仅只会匹配没有设置类别的Intent请求。
- 数据测试
数据在
<intent-filter>中的描述如下:
< intent-filter . . . >
< data android:type="video/mpeg" android:scheme="http" . . . />
< data android:type="audio/mpeg" android:scheme="http" . . . />
</ intent-filter >
<data>元素指定了希望接受的Intent请求的数据URI和数据类型,URI被分成三部分来进行匹配:scheme、 authority和path。- 其中,用setData()设定的Inteat请求的URI数据类型和scheme必须与IntentFilter中所指定的一致。
- 若IntentFilter中还指定了authority或path,它们也需要相匹配才会通过测试。
- action
- 使用 android:name 特性来指定对响应的动作名。
- 动作名必须是独一无二的字符串,所以,一个好的习惯是使用基于 Java 包的命名方式的命名系统。
- category
- 使用 Android:category 属性用来指定在什么样的环境下动作才被响应。
- 每个 Intent Filter 标签可以包含多个 category 标签。
- 你可以指定自定义的种类或使用 Android 提供的标准值,如下所示:
- ALTERNATIVE 你将在这章的后面所看到的,一个 Intent Filter 的用途是使用动作来帮忙填入上下文菜单。
- ALTERNATIVE 种类指定,在某种数据类型的项目上可以替代默认执行的动作。例如,一个联系人的默认动作时浏览它,替代的可能是去编辑或删除它。
- SELECTED_ALTERNATIVE与 ALTERNATIVE 类似,但 ALTERNATIVE 总是使用下面所述的 Intent 解析来指向单一的动作。
- SELECTED_ALTERNATIVE在需要一个可能性列表时使用。
- BROWSABLE 指定在浏览器中的动作。当 Intent 在浏览器中被引发,都会被指定成 BROWSABLE 种类。
- DEFAULT 设置这个种类来让组件成为 Intent Filter 中定义的 data 的默认动作。这对使用显式 Intent 启动的 Activity 来说也是必要的。
- GADGET 通过设置 GADGET 种类,你可以指定这个 Activity 可以嵌入到其他的 Activity 来允许。
- HOME HOME Activity 是设备启动(登陆屏幕)时显示的第一个 Activity 。通过指定 Intent Filter 为 HOME 种类而不指定动作的话,你正在将其设为本地 home 画面的替代。
- LAUNCHER 使用这个种类来让一个 Activity 作为应用程序的启动项。
- data
- data 标签允许你指定组件能作用的数据的匹配;如果你的组件能处理多个的话,你可以包含多个条件。
- 你可以使用下面属性的任意组合来指定组件支持的数据:
- android:host 指定一个有效的主机名(例如, com.google )。
- android:mimetype 允许你设定组件能处理的数据类型。例如,能匹配任何 Android 游标。
- android:path 有效地 URI 路径值(例如, /transport/boats/ )。
- android:port 特定主机上的有效端口。
- android:scheme 需要一个特殊的图示(例如, content 或 http )。
5. Service(服务)
Service生命周期
@todo
6. 五种存储数据的方式
6.1 SharedPreferences
- SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数。
- SharedPreferences保存数据,其背后是用xml文件存放数据,文件存放在
/data/data/<package name>/shared_prefs目录下: - 一个简单的存储代码如下:
/*
第一个参数用于指定该文件的名称,名称不用带后缀,后缀会由Android自动加上;
第二个参数指定文件的操作模式,共有四种操作模式。
1. MODE_APPEND: 追加方式存储
2. MODE_PRIVATE: 私有方式存储,其他应用无法访问
3. MODE_WORLD_READABLE: 表示当前文件可以被其他应用读取
4. MODE_WORLD_WRITEABLE: 表示当前文件可以被其他应用写入
5. MODE_MULTI_PROCESS: 适用于多进程访问(目前已被废弃,google官方推荐使用ContentProvider来实现进程间共享访问)
*/
SharedPreferences sharedPreferences = getSharedPreferences("cisco", Context.MODE_PRIVATE); // 私有数据
Editor editor = sharedPreferences.edit();// 获取editor对象
editor.putString("name", "cisco"); // 存储对象采用key-value键值对进行存放
editor.putInt("age", 4);
editor.commit();// 提交修改
- cisco.xml文件内容如下:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="name">cisco</string>
<int name="age" value="4" />
</map>
- 保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。
- 通过DDMS的File Explorer面板,展开文件浏览树,很明显SharedPreferences数据总是存储在
/data/data/<package name>/shared_prefs目录下。 - SharedPreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过SharedPreferences.edit()获取的内部接口Editor对象实现。
-
SharedPreferences本身是一 个接口,程序无法直接创建SharedPreferences实例,只能通过Context提供的getSharedPreferences(String name, int mode)方法来获取SharedPreferences实例,该方法中name表示要操作的xml文件名,第二个参数具体如下:
- Context.MODE_PRIVATE: 指定该SharedPreferences数据只能被本应用程序读、写。
- Context.MODE_WORLD_READABLE: 指定该SharedPreferences数据能被其他应用程序读,但不能写。
- Context.MODE_WORLD_WRITEABLE: 指定该SharedPreferences数据能被其他应用程序读,写。
-
Editor有如下主要重要方法:
- SharedPreferences.Editor clear():清空SharedPreferences里所有数据
- SharedPreferences.Editor putXxx(String key , xxx value): 向SharedPreferences存入指定key对应的数据,其中xxx 可以是boolean,float,int等各种基本类型据
- SharedPreferences.Editor remove(): 删除SharedPreferences中指定key对应的数据项
- boolean commit(): 当Editor编辑完成后,使用该方法提交修改
- SharedPreferences是以键值对来存储应用程序的配置信息的一种方式,它只能存储基本数据类型。
- 一个程序的配置文件仅可以在本应用程序中使用,或者说只能在同一个包内使用,不能在不同的包之间使用。
- 实际sharedPreferences是采用了XML格式将数据存储到设备中,在DDMS 中的File Explorer中的/data/data//shares_prefs下。
- SharedPreferences对象与SQLite数据库相比,免去了创建数据库,创建表,写SQL语句等诸多操作,相对而言更加方便,简洁。
- 但是SharedPreferences也有其自身缺陷,比如其职能存储boolean,int,float,long和String五种简单的数据类型,比如其无法进行条件查询等。
- 所以不论SharedPreferences的数据存储操作是如何简单,它也只能是存储方式的一种补充,而无法完全替代如SQLite数据库这样的其他数据存储方式。
- 适用范围:保存少量的数据,且这些数据的格式非常简单:字符串型、基本类型的值。
- 比如应用程序的各种配置信息(如是否打开音效、是否使用震动效果、小游戏的玩家积分等),解锁口令密码等。
6.2 文件存储
-rwxrw-r‐-1 root root 1213 Feb 2 09:39 abc
- 10个字符确定不同用户能对文件干什么?
- 第一个字符代表文件(-)、目录(d),链接(l)
- 其余字符每3个一组(rwx),读(r)、写(w)、执行(x)
- 第一组rwx:文件所有者的权限是读、写和执行
- 第二组rw-:与文件所有者同一组的用户的权限是读、写但不能执行
- 第三组r--:不与文件所有者同组的其他用户的权限是读不能写和执行
- 也可用数字表示为:r=4,w=2,x=1 因此rwx=4+2+1=7
- 1 表示连接的文件数
- root 表示用户
- root表示用户所在的组
- 1213 表示文件大小(字节)
- Feb 2 09:39 表示最后修改日期
- abc 表示文件名
文件存储数据的概念
- 文件存储是 Android 中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存到文件当中的,因而它比较适合用于存储一些简单的文本数据或二进制数据。
- 如果你想使用文件存储的方式来保存一些较为复杂的文本数据,就需要定义一套自己的格式规范,这样方便于之后将数据从文件中重新解析出来。
文件存储数据的实现步骤
- Context 类中提供了一个 openFileOutput ()方法,可以用于将数据存储到指定的文件中。
- 这个方法接收两个参数,第一个参数是文件名,在文件创建的时候使用的就是这个名称,注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到
/data/data/<packagename>/files/目 录 下 的 。 - 第 二 个 参 数 是 文 件 的 操 作 模 式 , 主 要 有 两 种 模 式 可 选 ,MODE_PRIVATE 和 MODE_APPEND。
- 其中 MODE_PRIVATE 是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而 MODE_APPEND 则表示如果该文件已存在就往文件里面追加内容,不存在就创建新文件。
- 其实文件的操作模式本来还有另外两种,MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE,这两种模式表示允许其他的应用程序对我们程序中的文件进行读写操作,不过由于这两种模式过于危险,很容易引起应用的安全性漏洞,现已在 Android 4.2 版本中被废弃。
- 除此之外,Context还提供了如下几个重要的方法:
- getDir(String name , int mode):在应用程序的数据文件夹下获取或者创建name对应的子目录
- File getFilesDir():获取该应用程序的数据文件夹得绝对路径
- String[] fileList():返回该应用数据文件夹的全部文件
- 读写sdcard上的文件,其中读写步骤按如下进行:
- 调用Environment的getExternalStorageState()方法判断手机上是否插了sd卡,且应用程序具有读写SD卡的权限,如下代码将返回true
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) - 调用Environment.getExternalStorageDirectory()方法来获取外部存储器,也就是SD卡的目录,或者使用"/mnt/sdcard/"目录
- 使用IO流操作SD卡上的文件
- 注意点:手机应该已插入SD卡,对于模拟器而言,可通过mksdcard命令来创建虚拟存储卡必须在AndroidManifest.xml上配置读写SD卡的权限。
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
6.3 SQLite
SQLite数据库的概念
- SQLite 是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少, 通常只需要几百 K 的内存就足够了, 因而特别适合在移动设备上使用。
- SQLite不仅支持标准的 SQL 语法,还遵循了数据库的 ACID 事务,所以只要你以前使用过其他的关系型数据库,就可以很快地上手 SQLite。而 SQLite 又比一般的数据库要简单得多,它甚至不用设置用户名和密码就可以使用。Android 正是把这个功能极为强大的数据库嵌入到了系统当中,使得本地持久化的功能有了一次质的飞跃。
- SQLite是轻量级嵌入式数据库引擎,它支持 SQL 语言,并且只利用很少的内存就有很好的性能。现在的主流移动设备,像Android iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到SQLite来存储我们大量的数据,所以我们就需要掌握移动设备上的SQLite开发技巧。
如何创建数据库
- Android 为了让我们能够更加方便地管理数据库,专门提供了一个 SQLiteOpenHelper 帮助类, 借助这个类就可以非常简单地对数据库进行创建和升级。
- 注意:SQLiteOpenHelper 是一个抽象类,这意味着如果我们想要使用它的话,就需要创建一个己的帮助类去继承SQLiteOpenHelper 中有两个抽象方法,分别是onCreate()和 onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
- SQLiteOpenHelper的构造方法
// context:上下文对象
// name:数据库名,创建数据库时使用的就是这里指定的名称
// factory:允许我们在查询数据的时候返回一个自定义的 Cursor,一般都是传入 null
// version:当 前 数 据 库 的 版 本 号 , 可 用 于 对 数 据 库 进 行 升 级 操 作
public MyDatabaseHelper(Context context, String name,CursorFactory factory, int version) {
super(context, name, factory, version);
}
- 这样,就构 建 出SQLiteOpenHelper 的实例之后,再调用它的 getReadableDatabase()或 getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在
/data/data/<package name>/databases/目录下。此时,重写的 onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。
- 如何创建表和删除表
// create:创建
// table :表
// UserInfo:表名
create table UserInfo (
// 使用了 primary key 将 id 列设为主键,并用 autoincrement 关键字表示 id 列是自增长的
// integer 表示整型,real 表示浮点型,text 表示文本类型,blob 表示二进制类型
id integer primary key autoincrement,
author text,
price real,
pages integer,
name text
)
- 在重写的 onCreate()方法使用SQLiteDatabase 的 execSQL()方法去执行这条建表语句通过该条语句删除指定的表db.execSQL("drop table if exists UserInfo") @todo 增删改查
6.4 content provider(内容提供者)
1.什么是内容提供者?
- 首先我们必须要明白的是ContentProvider(内容提供者)是android中的四大组件之一,但是在一般的开发中,可能使用比较少。
- ContentProvider为不同的软件之间数据共享,提供统一的接口。而且ContentProvider是以类似数据库中表的方式将数据暴露,也就是说ContentProvider就像一个“数据库”。
- 那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一样,只不过是采用URI来表示外界需要访问的“数据库”。
- 至于如何从URI中识别出外界需要的是哪个“数据库”这就是Android底层需要做的事情了,也就是说,如果我们想让其他的应用使用我们自己程序内的数据,就可以使用ContentProvider定义一个对外开放的接口,从而使得其他的应用可以使用我们自己应用中的文件、数据库内存储的信息。
- 当然,自己开发的应用需要给其他应用共享信息的需求可能比较少见,但是在Android系统中,很多数据如:联系人信息、短信信息、图片库、音频库等,这些信息在开发中还是经常用到的,这些信息谷歌工程师已经帮我们封装好了,我们可以使用谷歌给我的Uri去直接访问这些数据。
- 所以对于ContentProvider我们还是需要认真的学习的,在遇到获取联系人信息,图片库,音视频库等需求的时候,才能更好的实现功能。
2.为什么会有内容提供者?
- 当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。
- 虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。
- 而使用ContentProvider共享数据的好处是统一了数据访问方式,这也是为什么会有内容提供者的原因。
3.怎么使用内容提供者?
@todo
6.5 网络数据存储
WebView控件的使用
使用HTTP协议访问网络
- 工作原理:
- 就是客户端向服务器发出一条HTTP 请求,服务器收到请求之后会返回一些数据给客户端,然后客户端再对这些数据进行解析和处理就可以了。 @todo