Android/Android 应用程序
Android 应用程序(通常缩写为应用程序)驻留在扩展名为.apk的文件中。这是传统上用于 Java 应用程序的.jar文件格式的扩展。此文件的内容包括
- AndroidManifest.xml- 应用程序清单,它告诉 Android 系统有关应用程序组件的信息,它将在哪些 Android 版本上运行,以及它需要的哪些硬件功能和权限。特别是,权限会在用户从 Google Play 商店安装应用程序时显示给用户,因此他们可以选择接受或拒绝您的应用程序。如果某个特定的 Android 系统服务受权限控制,而您的清单中没有列出该权限,那么您的应用程序将被阻止访问该服务。
- 编译的 Java 字节码。但是,它没有使用 Sun 的JVM.class格式,而是使用Dalvikclasses.dex格式。
- (可选)本地机器代码。由于这必须是特定于体系结构的,因此 APK 格式允许提供此代码的替代版本,位于名为armeabi, mips等的子目录下。
- 应用程序资源。这可能包括屏幕布局、文本字符串(允许针对不同语言/区域进行本地化)、图标、颜色规格、样式定义以及其他各种内容,大多数以 XML 格式提供。这些内容大多数可以在运行时直接在代码中创建,但将它们保存在静态形式中通常可以更轻松地管理。特别是,它允许 Android 系统自动从替代资源中进行选择,而无需在代码中包含特殊情况。
用户在应用程序托盘中看到的图标不是对应于应用程序或.apk文件本身,而是对应于活动。基本上,活动是用户交互的单个屏幕。如果活动在清单中被标记为已导出,那么启动器将能够向用户显示该活动的图标,用户可以点击该图标来启动该活动。应用程序可能包含其他未导出的活动,这些活动仅在内部启动,可能是(直接或间接地)响应用户在已导出活动中的操作。如果应用程序具有多个已导出的活动,那么启动器中将显示相应的多个图标。
Android 基于 Linux 内核构建,可以运行常见的 Linux 格式的可执行文件。但是,您会注意到,APK 格式没有规定安装此类文件的条款,并且此类可执行文件无法轻松访问常见的 Android 类库。每个 Android 系统都包含一些标准可执行文件,包括工具箱,这是一个非常基本的命令行 shell。您可以在连接到 PC 的 SDK 上使用“adb shell
”命令访问它。还可以实现运行在 Android 设备本身的终端窗口;有关如何执行此操作的示例,请参阅ConnectBot的源代码。
APK 文件必须由开发者进行数字签名才能安装到 Android 设备上。您无需在 Google 或任何其他人那里正式注册您的签名密钥:签名密钥只是允许设备判断两个不同的 APK 文件是否来自同一个开发者。
安装到设备后,应用程序将被分配自己的(动态生成的)Linux 用户 ID。这使系统能够防止应用程序访问彼此的数据,除非以经过仔细授权的方式进行访问。
基于 Linux 内核,Android 支持标准的 POSIX 风格的进程和线程。但是,用户通常不会从这些概念的角度看待应用程序;相反,Android UI 使用任务和活动来工作。
通常,应用程序定义的所有活动都运行在单个进程中,而不同的应用程序运行在不同的进程中。但是,程序员可以通过多种方式控制这一点,例如指定launchMode和taskAffinity用于活动。此外,应用程序 process 属性允许两个相互信任的应用程序(由相同的私钥签名)在程序员需要时运行在同一个进程中。
请注意,在 Android 文档中,当说活动在后台“停止”时,这只意味着它的onStop方法已被调用,并且其 UI 不再被认为是活动的。与该活动相关的任何进程和线程都会继续正常运行,并且可以继续执行任何他们喜欢的操作,当然,唯一推荐的 UI 相关操作是发送通知,表明他们想要一些用户关注。
当然,此类后台进程/线程在系统资源不足时被杀死的优先级队列中处于较高位置。
请注意,您的应用程序进程最初只使用一个线程启动。特别是,所有 UI 调用都必须在此线程上发生。除了相关的 Handler 方法和Activity.runonUiThread之外,Android UI 类(视图、窗口小部件等)不是线程安全的,并且不能在任何其他线程上调用。您可以自由地创建额外的进程和线程;管理需要与 UI 协调的后台活动的最佳方法(显示进度更新、处理取消时的清理等)是使用AsyncTask。
UI 线程运行一个 Looper,您也可以实例化它在自己的线程上运行。如果 UI Looper 在用户事件发生后的 5 秒内没有获得一些 CPU 时间,您会看到令人恐惧的“应用程序无响应”(ANR) 警报。
如果您需要运行不会长时间占用 UI 线程的短任务,可以通过 Handlers 将它们排队到 Looper(请参阅post和postAtTime方法)。更耗时的操作,无法拆分成短时任务,应该委托给后台线程。
熟悉其他平台 Java 的程序员可能习惯于使用 TimerTask 来调度定时事件,但这些事件在自己的线程上运行,因此它们不安全用于直接进行 UI 调用。但是,您可以从后台线程调用 Activity.runonUiThread。
任务 是通过点击启动器中的图标启动的任何内容。启动器的应用程序托盘会自动填充所有已安装应用程序的适当条目(即主活动),这些条目可以复制到主屏幕。也可以在主屏幕上添加快捷方式。通常,任务在新的进程中启动,如果此类进程已经在运行,则点击图标只会将现有进程带到最前面。但是,可以更改这两种行为。
任务中的第一个(根)活动可以根据需要启动其他活动。实际上,除了最简单的应用程序之外,您编写的任何应用程序可能都包含多个活动。任务还可以启动其他完全独立的任务,区别在于这些任务在最近的任务列表中显示为单独的条目,而应用程序内部活动则不会。随着每个新活动的启动,它将成为对用户可见的最顶层的活动,而先前最顶层的活动将停用其 UI。这些附加活动通常在启动它们的进程中运行,但同样,这种行为可以改变。
按下标准 Android 后退键通常会终止最顶层的活动,并将先前最顶层的活动激活。终止任务的根活动将终止该任务并返回用户到之前运行的任何任务(可能是启动器)。
只要活动在运行,任务内的活动永远不会重新排序;将不在任务后退栈顶部的活动带到顶部,唯一的方法是终止该任务中所有位于它上方的活动。但是,任务可以重新排序,因为最近启动的八个任务中的任何一个都可以随时通过按 Home 键并点击出现的条目之一来带到最前面。
活动是通过 Context(包括您自己的活动)的子类的某些变体调用启动的。startActivity这些调用中的主要参数是意图。
在未经 root 的 Android 设备上,Java 代码在内存使用量方面受到严格限制。Java 对象的堆大小限制为大约 20 MB,而 Bitmap 对象的堆大小限制为类似的大小。哦,与该页面上关于recycle方法是“高级调用,通常不需要调用”的说法相反,我建议您始终调用recycle在您完成位图操作时,否则您的应用程序可能会在某个时刻崩溃并显示令人恐惧的消息“java.lang.OutOfMemoryError: bitmap size exceeds VM budget
”。
本机代码的内存使用量不受任意限制,可以自由分配所有可用内存。
活动和视图可以实现调用来保存和恢复它们的“实例状态”。这不同于保存/恢复用户数据;这纯粹是机制的一部分,可以让您的进程看起来一直在运行,即使系统需要杀死它,无论是由于内存不足还是由于重新启动它以进行方向更改。
如果您的活动处于最前面,用户按下后退按钮,那么这通常被认为是终止该活动的请求。下次用户启动您的活动时,他们通常不会期望它恢复到好像从未终止过的状态。在这种情况下,onSaveInstanceState和onRestoreInstanceState不被调用。
如果您的活动处于最前面,用户在它之上启动了其他活动,那么您的onSaveInstanceState方法将与onPause一起调用。如果用户在您的活动仍在运行时返回到您的活动,则onResume将按通常方式调用,但无需调用onRestoreInstanceState.
但是,如果您的活动不在最前面时系统内存不足,它可能会在用户未注意到的情况下被杀死。然后,当用户返回它时,它将重新启动,并且onRestoreInstanceState将被调用,以便您可以使其看起来像您一直在运行。
此外,如果您的活动处于最前面时设备在纵向和横向之间切换方向,则如果您的代码没有专门处理它,默认操作是系统杀死并重新启动您。只要您的布局经过适当的设计以处理这两种情况,这通常就足够了。
请注意,“实例状态”是什么完全取决于您。例如,地图查看器应用程序可能每次用户启动时都恢复到完全缩小的视图。因此,滚动位置和缩放倍率将作为实例状态的一部分保存和恢复。或者,您可能会决定应用程序每次退出和重新启动时都会记住其滚动偏移量和缩放倍率,在这种情况下,保存/恢复实例状态根本无关紧要。作为应用程序的编写者,这是您的选择。
请注意,不可能通过 Java API 杀死或中止另一个线程:相关的 Thread.stop/destroy 方法未实现,因为它们可能会使 Dalvik VM 处于不一致状态。
这也适用于基于 Thread 的类,例如 AsyncTask:一旦 doInbackground 方法开始在后台线程上执行,它将运行到完成,无论您是否传递true给 cancel 还是不传递。
当然,本机代码可以绕过此限制,但这会带来所有隐含的危险。此外,进程始终可以整体被杀死,因为一旦整个进程状态消失,我们就不会关心其一致性。
Android 应用程序主要使用 Java 编程语言编写,并设计为相互隔离运行,以维护安全的操作环境。每个应用程序都在其自己的 Dalvik 虚拟机实例中运行,并在其自己的 Linux 用户下运行。
与大多数(如果不是全部)其他移动平台相比,Android 真正支持多任务/多线程;因此,多个应用程序可以同时运行,每个应用程序可以同时执行多个操作。这意味着用户可以打开其邮件/新闻/等应用程序,并在收到新邮件时获得通知,而不是不断地自己检查。
Android 应用程序以 .apk 文件的形式交付(使用 jartool 签名的 zip 文件,只是后缀不同)。这些软件包包含应用程序运行所需的所有文件,其中有 classes.dex 文件,该文件是在 Dalvik VM 内部执行的文件,以及 AndroidManifest.xml 文件,它是纯文本 XML 清单的二进制表示。通常还有其他资源,如可绘制对象(图像/精灵/图标)和 XML 布局。
编写 Android 应用程序的受支持 IDE 是 Eclipse,但使用它并非强制性。
Android(Java)应用程序本身包含多个子类化 Intent 和 Activity 类(包括服务和内容提供者)的类。
Android 应用程序被编译成 .dex 文件格式的二进制文件,然后打包到 apk(zip 存档)文件中。
人们使用多种语言为 Android 编写程序。
我们将在本书的另一章中讨论使用 JavaScript、HTML5 和 CSS3 为 Android 编写程序 - Android/PhoneGap。
Android 脚本层 (SL4A) [1] 支持多种脚本语言。
有些人使用 Python For Android [2] 与 SL4A 一起为 Android 编写 Python 应用程序。
其他人使用 Kivy [3] 为 Android 编写 Python 应用程序。
有些人使用在 Android 设备本身运行的编辑器和编译器开发 Java 应用程序。我们将在后面的章节中讨论终端 IDE - Android/Terminal IDE。
一些可能有助于编码的库;有关测试库和系统,请参阅测试章节。
- ActionBarSherlock - 支持早期 Android 版本上的现代(4.x)操作栏样式界面,允许一个应用程序服务所有版本。
- AndroidAnnotations - 代码生成器,允许程序员避免编写许多 Android UI 和异步代码的样板代码。
- SlidingMenus - 提供“滑动面板向右显示菜单”功能。
- android-query - 简化 UI 和异步编码。
- Lars Vogel。 "Android 开发教程":如何使用 Eclipse 创建 Android 应用程序。
- Android 门户 在嵌入式 Linux Wiki 上有一个 Android 应用程序开发教程集。
UserProfile 类 :-
package com.mock.denethanjanaperera.mymock;
import android.provider.BaseColumns;
public final class UserProfile {
private UserProfile(){
}
public class Users implements BaseColumns{
public final static String TABLE_NAME = "UserInfo"; public final static String COLUMN_USERNAME = "username"; public final static String COLUMN_PASSWORD = "password"; public final static String COLUMN_GENDER = "gender"; public final static String COLUMN_DOB = "dateofBirth";
}
}
DBhelper 类 :-
package com.mock.denethanjanaperera.mymock.database;
import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper;
import com.mock.denethanjanaperera.mymock.User; import com.mock.denethanjanaperera.mymock.UserProfile;
import java.util.ArrayList;
public class DBHelper extends SQLiteOpenHelper {
private final static String DATABASE_NAME = "UserInfo.db";
public DBHelper(Context context) { super(context, DATABASE_NAME,null, 1); }
@Override public void onCreate(SQLiteDatabase sqLiteDatabase) {
String CREATE_TABLE = "CREATE TABLE " + UserProfile.Users.TABLE_NAME +" (" + UserProfile.Users._ID + " INTEGER PRIMARY KEY," + UserProfile.Users.COLUMN_USERNAME + " TEXT," + UserProfile.Users.COLUMN_DOB + " TEXT," + UserProfile.Users.COLUMN_GENDER + " TEXT," + UserProfile.Users.COLUMN_PASSWORD + " TEXT )";
sqLiteDatabase.execSQL(CREATE_TABLE);
}
@Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
public Long addInfo(String username, String password){
SQLiteDatabase sqLiteDatabase = getWritableDatabase();
ContentValues cv = new ContentValues(); cv.put( UserProfile.Users.COLUMN_USERNAME, username); cv.put( UserProfile.Users.COLUMN_PASSWORD, password );
Long rowId = sqLiteDatabase.insert(UserProfile.Users.TABLE_NAME, null,cv);
return rowId; }
public int updateInfo(String userId, String username, String password, String dob, String gender ){
SQLiteDatabase sqLiteDatabase = getWritableDatabase();
ContentValues cv = new ContentValues(); cv.put( UserProfile.Users.COLUMN_USERNAME, username); cv.put( UserProfile.Users.COLUMN_PASSWORD, password ); cv.put( UserProfile.Users.COLUMN_GENDER, gender); cv.put( UserProfile.Users.COLUMN_DOB, dob);
String select = UserProfile.Users._ID + " = ?"; String args[] = {userId};
int count = sqLiteDatabase.update(UserProfile.Users.TABLE_NAME, cv, select,args);
return count;
}
public ArrayList readAllInfo(){
SQLiteDatabase sqLiteDatabase = getReadableDatabase();
String[] projection = {
UserProfile.Users._ID, UserProfile.Users.COLUMN_USERNAME, UserProfile.Users.COLUMN_DOB, UserProfile.Users.COLUMN_GENDER, UserProfile.Users.COLUMN_PASSWORD };
String sortOrder = UserProfile.Users._ID + " DESC";
Cursor cursor = sqLiteDatabase.query( UserProfile.Users.TABLE_NAME, projection, null, null, null, null, sortOrder );
ArrayList<User> list = new ArrayList<>();
if (cursor.getCount() > 0){
while(cursor.moveToNext()){
User newUser = new User();
int id = cursor.getInt(cursor.getColumnIndexOrThrow(UserProfile.Users._ID)); String user = cursor.getString(cursor.getColumnIndexOrThrow(UserProfile.Users.COLUMN_USERNAME)); String date = cursor.getString(cursor.getColumnIndexOrThrow(UserProfile.Users.COLUMN_DOB)); String gen = cursor.getString(cursor.getColumnIndexOrThrow(UserProfile.Users.COLUMN_GENDER)); String pass = cursor.getString(cursor.getColumnIndexOrThrow(UserProfile.Users.COLUMN_PASSWORD));
newUser.setUserId(id+""); newUser.setUserName(user); newUser.setDateOfBirth(date); newUser.setGender(gen); newUser.setPassword(pass);
list.add(newUser); } }
return list; }
public ArrayList readAllInfo(String userId, String userName){
String selection; String[] args = {""};
if(userId == null){
selection = UserProfile.Users.COLUMN_USERNAME + " LIKE ?"; args[0] = userName; } else { selection = UserProfile.Users._ID + " = ?"; args[0] = userId; }
SQLiteDatabase sqLiteDatabase = getReadableDatabase();
String[] projection = {
UserProfile.Users._ID, UserProfile.Users.COLUMN_USERNAME, UserProfile.Users.COLUMN_DOB, UserProfile.Users.COLUMN_GENDER, UserProfile.Users.COLUMN_PASSWORD };
String sortOrder = UserProfile.Users._ID + " DESC";
Cursor cursor = sqLiteDatabase.query( UserProfile.Users.TABLE_NAME, projection, selection, args, null, null, sortOrder );
ArrayList<User> list = new ArrayList<>();
if (cursor.getCount() > 0){
while(cursor.moveToNext()){
User newUser = new User();
int id = cursor.getInt(cursor.getColumnIndexOrThrow(UserProfile.Users._ID)); String user = cursor.getString(cursor.getColumnIndexOrThrow(UserProfile.Users.COLUMN_USERNAME)); String date = cursor.getString(cursor.getColumnIndexOrThrow(UserProfile.Users.COLUMN_DOB)); String gen = cursor.getString(cursor.getColumnIndexOrThrow(UserProfile.Users.COLUMN_GENDER)); String pass = cursor.getString(cursor.getColumnIndexOrThrow(UserProfile.Users.COLUMN_PASSWORD));
newUser.setUserId(id+""); newUser.setUserName(user); newUser.setDateOfBirth(date); newUser.setGender(gen); newUser.setPassword(pass);
list.add(newUser); } }
return list; } public int deleteInfo(String username){
SQLiteDatabase sqLiteDatabase = getReadableDatabase();
String select = UserProfile.Users._ID + " = ?"; String [] args = {username};
int deleteRows = sqLiteDatabase.delete(UserProfile.Users.TABLE_NAME, select, args);
return deleteRows;
} }
Home 类 :-
package com.mock.denethanjanaperera.mymock;
import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast;
import com.mock.denethanjanaperera.mymock.database.DBHelper;
import java.util.ArrayList;
public class Home extends AppCompatActivity {
private Button login, reg; private EditText uname, password; private DBHelper dbHelper;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_home);
dbHelper = new DBHelper(this);
login = findViewById(R.id.btnLogin); reg = findViewById(R.id.btnReg); uname = findViewById(R.id.etHUname); password = findViewById(R.id.etHPassword);
login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) {
ArrayList<User> list = dbHelper.readAllInfo();
for(User u : list){
if(u.getUserName().equals(uname.getText().toString())){
if(u.getPassword().equals(password.getText().toString())){
Intent intent = new Intent(Home.this, ProfileManagement.class); intent.putExtra("id", u.getUserId()); startActivity(intent); }else{ Toast.makeText(Home.this, "Invalid Username and Password", Toast.LENGTH_SHORT).show(); } } } } });
reg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) {
String user = uname.getText().toString(); String passwrd = password.getText().toString();
if ((user.isEmpty() || passwrd.isEmpty())) { Toast.makeText(Home.this, "Enter Registration Info", Toast.LENGTH_SHORT).show(); } else { dbHelper.addInfo(user, passwrd);
Toast.makeText(Home.this, "User Registered!", Toast.LENGTH_SHORT).show(); } } });
}
}
profilManagemant 类 :-
package com.mock.denethanjanaperera.mymock;
import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.RadioButton;
import com.mock.denethanjanaperera.mymock.database.DBHelper;
import java.util.ArrayList;
public class ProfileManagement extends Activity {
private Button update; private EditText uname, dob, pass; private RadioButton male, female; private DBHelper dbHelper; private String userId;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_profile_management);
dbHelper = new DBHelper(this); Intent intent = getIntent(); userId = intent.getStringExtra("id");
uname = findViewById(R.id.etUuser); dob = findViewById(R.id.etUdob); pass = findViewById(R.id.etUdob); update = findViewById(R.id.btnUpdate); male = findViewById(R.id.radioMale); female = findViewById(R.id.radioFe);
ArrayList<User> list = dbHelper.readAllInfo(userId, null);
for (User u : list){
uname.setText(u.getUserName()); pass.setText(u.getPassword()); dob.setText(u.getDateOfBirth());
if(u.getGender() != null){
if(u.getGender().equals("Male")){
male.setChecked(true); } else { female.setChecked(true); } } }
update.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) {
Intent intent = new Intent(ProfileManagement.this, EditProfile.class); intent.putExtra("id", userId); startActivity(intent); } }); }
}
EditProfile 类 :-
package com.mock.denethanjanaperera.mymock;
import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.Toast;
import com.mock.denethanjanaperera.mymock.database.DBHelper;
import java.util.ArrayList;
public class EditProfile extends AppCompatActivity {
private Button edit, delete, search; private EditText uname, dob, pass; private RadioGroup radioGroup; private RadioButton male, female; private String gender;
private DBHelper dbHelper; private String userId;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_edit_profile);
dbHelper = new DBHelper(this); Intent intent = getIntent(); userId = intent.getStringExtra("id");
Toast.makeText(EditProfile.this, "User Id: " + userId, Toast.LENGTH_SHORT).show();
uname = findViewById(R.id.etEUser); dob = findViewById(R.id.etEdob); pass = findViewById(R.id.etEpassword); edit = findViewById(R.id.btnEdit);
delete = findViewById(R.id.btnDelete); search = findViewById(R.id.btnSearch);
radioGroup = findViewById(R.id.radio); male = findViewById(R.id.radEmale); female = findViewById(R.id.radEfemale);
ArrayList<User> list = dbHelper.readAllInfo(userId, null);
if (!list.isEmpty()) {
for (User u : list) {
uname.setText(u.getUserName()); pass.setText(u.getPassword()); dob.setText(u.getDateOfBirth());
if (u.getGender() != null) {
if (u.getGender().equals("Male")) {
male.setChecked(true); } else { female.setChecked(true); } } } }
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override public void onCheckedChanged(RadioGroup radioGroup, int view) {
if(view == R.id.radEfemale){
gender = "Female"; } else{
gender = "Male"; } } });
search.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) {
ArrayList<User> urs = dbHelper.readAllInfo(null, uname.getText().toString());
for (User u : urs){
userId = u.getUserId(); uname.setText(u.getUserName()); pass.setText(u.getPassword()); dob.setText(u.getDateOfBirth());
if(u.getGender() != null){
if(u.getGender().equals("Male")){
male.setChecked(true); } else { female.setChecked(true); } } } } });
edit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) {
String userName = uname.getText().toString(); String date = dob.getText().toString(); String pwrd = pass.getText().toString(); if(female.isChecked()){
gender = "Female"; } else{
gender = "Male"; }
int count = dbHelper.updateInfo(userId, userName, pwrd, date, gender);
if(count > 0){
Toast.makeText(EditProfile.this, "Updated!", Toast.LENGTH_SHORT).show(); } else{
Toast.makeText(EditProfile.this, "Error in data Sending!", Toast.LENGTH_SHORT).show(); } } });
delete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) {
int count = dbHelper.deleteInfo(userId);
if(count > 0){
Toast.makeText(EditProfile.this, "Deleted!", Toast.LENGTH_SHORT).show(); } else{
Toast.makeText(EditProfile.this, "Something went wrong!", Toast.LENGTH_SHORT).show(); } } });
}
}
User 类 :-
package com.mock.denethanjanaperera.mymock;
public class User {
private String userId; private String userName; private String dateOfBirth; private String gender; private String password;
public User(){ }
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public String getDateOfBirth() { return dateOfBirth; }
public void setDateOfBirth(String dateOfBirth) { this.dateOfBirth = dateOfBirth; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}