安卓的一些笔记
外观
基础控件与公用API
基础的控件基类都是View
大多数基础的控件都支持 setOnClickListener(...)
方法
-
文字与输入框
- TextView
- EditText
可用的方法有
- getText()
- setText()
- setVisibility(View.GONE | View.VISIBLE)
- setFocusable(false)
- setError(null | “error messages”)
- addTextChangedListener(…)
-
按钮
Button
可用的方法有
- setText()
- setAlpha(0.3f)
- setEnabled(false)
-
单选框
CheckBox
可用的方法有
- setEnabled(false)
- isChecked()
- setChecked(true)
-
布局
- LinearLayout 纵向线性排列
- TableLayout 对行中的列做对齐处理
- TableRow
- FrameLayout 不常用
xml属性与布局控制
常见的
android:id
android:text
android:textSize
android:textStyle
android:inputType 数字还是密码等,可以与键盘互动
android:ems 文字输入框的长度
android:maxLength
android:maxLines
android:gravity(控件内的对齐)
android:visibility
android:layoutDirection
android:layout~gravity~
android:background
其他
android:imeOptions (键盘回车键的动作)
android:nextFocusForward (下一个聚焦的)
android:selectAllOnFocus (聚焦时选择全部)
android:numeric (数字的具体格式,即将废弃但能用来解决bug)
布局相关
android:paddingTop
android:paddingRight
android:layout~width~
android:layout~height~
android:layout~marginLeft~
方便的
layout
style
-
数字键盘与地板移动的bug
靠近下方的数字输入框会被键盘挡住,
第一次点击时正常但之后就会发生问题.
因为靠右和最新的数字冲突.
解决:
不使用android:inputType=number
使用android:numeric=integer
长度单位
常用的:
px(pixels) 像素,对应屏幕上的一个像素点
dp(device independent pixels) 独立像素,在160dpi的屏幕上1dp=1px,在其他屏幕上使用不同比例以统一外观
sp(scaled pixels) 比例像素,有着dp的性质,同时可以根据系统对字体的设置更改大小,如果不希望系统设置更改应用字体大小,苏姚使用dp
不常用的如下:
in(inch) 英寸,1 inch = 2.54cm
pt(point) 点,1 pt = 1/72 inch
mm 毫米
id表记,string,color,style的继承
xml文件中,
使用 @+id/xxx
表示新建的id,
使用 @id/xxx
表示已经存在的id.
java文件中使用
R.id.xxx
表示id.
在 app/src/main/res/values/strings.xml
文件中统一定义项目需要的字符串,
在java文件中使用 R.string.xxx
调用字符串
在 app/src/main/res/values/colors.xml
文件中定义项目需要用的色彩,
可以使用的表达方式有
- 十六进制色彩
#3F51B5
- 调用安卓系统提供的色彩
@android:color/black
在xml文件中使用 @color/xxx
调用色彩
在 app/src/main/res/values/styles.xml
文件中统一定义项目需要的风格,
比如输入框的大小,字体的大小,颜色等等.
视图与局部视图
在 app/src/main/res/layout/xxx.xml
文件中定义各个页面需要的外观.
在java文件中使用 setContentView(R.layout.activity_details);
声明需要使用的视图文件.
在各个视图文件中可以使用
1 | <include |
调用一个已经有的局部视图
高级控件
-
标题栏
android自己提供的标题栏(ActionBar)好像比较简单,
只能居左显示文字,还不能修改色彩,不能添加菜单栏或者返回箭头.
因此常常使用另外的Toolbar
作为替代.
环境上还需要一定配置,
在app/build.gradle
文件中引入v7支持,
但之后的数字并不确定,可能和想要运行的安卓平台版本有关,
这里的23只是例子1
2
3
4dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.1.1'
}然后需要在
app/src/main/res/values/styles.xml
文件中取消ActionBar的使用1
2
3
4<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
</style>然后就能使用Toolbar的标签了
1
2
3
4
5
6
7
8
9
10<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/appDefault"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/title" />
</android.support.v7.widget.Toolbar> -
列表与元素
xml文件中使用
1
2
3<Spinner
android:id="@+id/COD_section"
android:spinnerMode="dropdown" />可以在string中定义选项
1
2
3
4<string-array name="spinner_options">
<item>option1</item>
<item>option2</item>
</string-array>然后在java中使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// set dropdown box
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.spinner_options, android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
// get selected Item
spinner.getSelectedItemId();
// set selected Item to id=1
spinner.setSelection(1);
// handle item selected event
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
if (spinner.getSelectedItemId() == 0) {
// something
} else {
// something
}
}
public void onNothingSelected(AdapterView<?> adapterView) {
}
} -
对话框
-
闪现的
1
Toast.makeText(LoginActivity.this, R.string.error_incorrect_email_password, Toast.LENGTH_SHORT).show();
-
带有按键的
同时带有确认和取消的,
可以使用点击其他区域来关闭对话框.1
2
3
4
5
6
7
8
9
10new AlertDialog.Builder(StatisticsActivity.this)
.setTitle(R.string.task_3)
.setMessage(R.string.confirm_send)
.setNegativeButton(R.string.action_cancel_send, null) // 返回键则返回
.setPositiveButton(R.string.action_confirm_send, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
something(); // 确定键则执行函数
}
}).show();限制取消方式,并在取消后执行任务
1
2
3
4
5
6
7
8
9
10
11
12new AlertDialog.Builder(StatisticsActivity.this)
.setTitle(getText(R.string.task_3))
.setMessage(getText(R.string.error_business) + "\n" + TextUtils.join("\n", failedNum))
.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialogInterface) {
// delete data...
something();
}
})
.show()
.setCanceledOnTouchOutside(false); // 仅可以使用返回键关闭对话框
-
页面与参数
页面堆栈模型
页面之间的关系可以想象是堆栈,总是显示早上方的页面.
当按下返回时,栈顶的页面被销毁,显示下一个页面.
因此如果想从某页面直接跳转到首页,首页按下返回又只能退出应用时需要单独的配置.
带参数页面跳转
跳转前的页面(From向To跳转)
1 | // 准备参数 |
跳转后页面接收参数
1 | Intent intent = getIntent(); |
带参数页面返回
下级页面中
1 | Intent intent = new Intent(); |
上级页面中
1 |
|
全局传递参数
如果使用intent的方式逐个页面传递需要全局传递的变量,会比较麻烦,一般使用以下方法
在 app/src/main/java/package/ThisApp.java
中定义好全局变量以及获取的方法
1 | public class ThisApp extends Application { |
在需要使用全局变量的页面中获取
1 | private ThisApp thisApp = (ThisApp) getApplication(); |
返回键的重写
1 |
|
数据库
安卓应用为了长久保存数据,
可以使用一个放在本地的数据库,
一般数据库使用的是 sqlite,
sqlite使用文件来作为数据存储的对象,
一般存放的位置是 /data/data/<applicationId>/databases/
开发中使用android studio的Device File Explore管理功能提取数据库文件出来.
应用的更新不能修改已经保存在应用中路径下的数据库文件,
因此如果代码中更新了软件的数据库中的字段,
目前已知的做法只能重新安装整个软件.
数据库操作
-
建立数据库
在
app/src/main/java/<package>/CRSQLiteOpenHelper.java
中定义数据库的创建语句1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28package xxx;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class CRSQLiteOpenHelper extends SQLiteOpenHelper {
public CRSQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
public void onCreate(SQLiteDatabase sqLiteDatabase) {
String sql = "CREATE TABLE if NOT EXISTS invoice(" +
" id INTEGER PRIMARY KEY autoincrement," +
" invoice_num VARCHAR(15) NOT NULL," +
" created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
" created_user_id INT(11) NOT NULL" +
" )";
sqLiteDatabase.execSQL(sql);
}
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
// 此处可能处理更新时需要卸载并清除数据库的问题
}
}然后在应用的’主程序’中调用即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package xxx;
import android.app.Application;
import android.database.sqlite.SQLiteDatabase;
public class ThisApp extends Application {
private SQLiteDatabase db;
public SQLiteDatabase getDB() {
return this.db;
}
public void onCreate() {
super.onCreate();
CRSQLiteOpenHelper crSQLiteOpenHelper = new CRSQLiteOpenHelper(getApplicationContext(), "create_db", null, 1);
this.db = crSQLiteOpenHelper.getWritableDatabase();
}
} -
使用数据库
获取db对象
1
db = thisApp.getDB();
-
增
使用的接口可以是
insert(String table,String nullColumnHack,ContentValues values)
1
2
3ContentValues value = new ContentValues();
value.put("invoice_num", '1234567');
db.insert("invoice", null, value); -
删
可以使用的接口是
delete(String table,String whereClause,String[] whereArgs)
1
2// delete from invoices where id = 7
db.delete("invoices", "id = ?", new String[]{String.valueOf(7)})也可以自定义语句操作
1
2db.execSQL("delete from invoice where invoice_num not in(\'"
+ TextUtils.join("\',\'", failedNum) + "\');"); -
改
可以使用的接口是
update(String table,ContentValues values,String whereClause, String[] whereArgs)
1
2
3
4
5
6
7// update orders set OrderPrice = 800 where Id = 6
ContentValues cv = new ContentValues();
cv.put("OrderPrice", 800);
db.update("orders",
cv,
"Id = ?",
new String[]{String.valueOf(6)}); -
查
接口查询是
public Cursor query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy,String limit);
但参数非常复杂,本人使用自定义查询.- table 表名
- columns 列名数组
- selection where条件数组
- selectionArgs where条件参数数组
- groupBy groupby条件
- having having条件
- orderBy orderby条件
- limit limit条件
1
2
3
4
5
6
7
8String sql = "select * from invoice where invoice_num = \'" + deliveryNo + "\' limit 1";
Cursor cursor = db.rawQuery(sql, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
senderId = cursor.getInt(cursor.getColumnIndex("sender_id"));
}
cursor.close();
} -
事务
1
2
3
4
5
6
7
8
9db.beginTransaction();
try {
saveCustomer();
db.setTransactionSuccessful();
} catch {
//Error in between database transaction
} finally {
db.endTransaction();
}
-
volly网络请求
volly是谷歌提供的异步的网络请求组件,
能够适应大量的并发,
而且和应用的声明周期比较契合,
一般使用该方式.
json代表的请求流程
主要框架
1 | RequestQueue requestQueue = Volley.newRequestQueue(DetailsActivity.this); |
其中的变量
1 | String connectUrl = baseURL + "/api/xxx"; |
基础认证
认证的类型有许多,其中最简单的是http basic authentication.
在http请求头部添加字段,然后在服务器上验证.
java中使用
1 | JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST, connectUrl, dataToPost, listener, errorListener) { |
其中credentials的形式是 key:value
的格式
postman测试流程
一般测试流程
- 点击页面(或使用其他方法),从控制台的network下找到需要抄的请求
- postman中填写地址,请求方法,header等
- 根据浏览器中显示的body信息,填写postman中的body信息
- 如果是json格式的post请求,内容的定义可以使用:
Body->raw(下拉框选json)->直接填写json数据
- 如果是json格式的post请求,内容的定义可以使用:
测试
上面的例子中已经有添加TAG以及打log的方法,
使用如下方法可以查看log
-
Android Studio左下方的Logcat标签
-
Android Studio左下方的run标签或debug标签(区别是Logcat多一些时间戳和PID,应用名的信息)
-
使用命令行查看
1
2
3
4
5获得PID
adb shell ps | grep com.xxx
获取指定PID的log
adb logcat | grep <PID>
相比直接从命令行查看log,使用Android Studio优点有
- 无需查找PID
- 没有多余的时间戳和PID等信息,更简洁
环境与打包
权限要求
在 app/src/main/AndroidManifest.xml
文件中使用诸如以下的方法定义app请求的权限
例子中请求了联网权限.
1 | <uses-permission android:name="android.permission.INTERNET"/> |
版本管理
在 app/build.gradle
文件中定义了许多应用信息
- 签名信息
- 编译用sdk,最低sdk版本
- 应用的版本
- 需要的外部库
应用版本相关的有 versionCode
和 versionName
,
versionCode
是整数,习惯上随着开发而+1迭代
versionName
是形式如 1.0.0
的给用户看的版本号,随便写
签名
要发布签名过的app时似乎似乎需要一些概念
- 密码库,比如
xxx.jks
- 密码库的进入密码
- 密码库中密码条目的标记
- 密码库中密码条目的密码
配置的目录在(在File->Project Structure->Modules->Signing Configs),
最后会反映在 app/build.gradle
文件中.
然后选择(Build->Build APK(s))来编译文件,
应该会产生一个 app/build/outputs/apk/release/app-release.apk
文件.
可以通过(Build->Generate Signed Bundle or APK)中创建秘钥库,
但貌似在此处编译不好用.