安卓-通讯录
一、實驗目的
練習掌握 Android 軟件開發基本編程技術、Android 系統 SQLite 數據庫的使用、通話、短信的使用等,設計制作一 Android 通訊錄軟件。
二、實驗內容
實現的通訊錄功能和界面可在實驗開發中由每位同學自己設計,但必須使用SQLite 數據庫保存通訊錄信息。以下功能和軟件界面供參考:仿照一般手機通訊錄的使用界面和功能,主要功能可包括: (1)可以添加、刪減聯系人
(2)聯系人的信息包括:姓名、手機號碼、工作單位、群組、電子郵件、手機鈴聲
(3)選擇聯系人后,可以快速進行操作,如:撥打電話發送短信、查看詳細、移出群組、移動分組、刪除聯系人等。查看詳細時顯示手機號碼、群組、和設定的手機鈴聲以及同該聯系人的通話記錄。
(4)在聯系人界面,可以查看各群組。點擊群組,跳出對應聯系人。
(5)有撥號鍵盤,點擊數字將號碼顯示出來,并可以對手機號碼進行刪減
(6)可以發送信息,顯示信息記錄
(7)發送信息時,可以快速選擇現有聯系人。
三、實驗要求
(1)每位同學獨立設計軟件功能、完成軟件的開發與測試。
(2)每位同學獨立完成實驗報告(根據模板),并提交至網絡課堂。
四、數據代碼及運行結果截圖
全部代碼如圖所示
//mainactivity.java 代碼太多了,所以其他文件的代碼就沒有貼上。 import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.app.NotificationCompat; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import android.app.AlertDialog; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.content.Context; import android.content.SharedPreferences; import android.graphics.BitmapFactory; import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.Toast; import com.google.android.material.navigation.NavigationView; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.pan.coursedesign.recyclerview.Person; import com.pan.coursedesign.recyclerview_logs.recording; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.params.HttpMethodParams; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.litepal.crud.DataSupport; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.util.List; import java.util.Map; import java.util.Set; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response;public class MainActivity extends AppCompatActivity {AlertDialog.Builder builder5;//自定義AlertDialog dialog;private int tag = 0;private int DataDownTag = 0;private DrawerLayout mDrawerLayout;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//自定義Toolbar并設置進系統Toolbar toolbar = findViewById(R.id.toolbar);setSupportActionBar(toolbar);//因為滑動菜單不容易被用戶知道,所以加一個折疊按鈕ActionBar actionBar = getSupportActionBar();//得到actionBar的實例(toolbar實現的哦,也就是toolbar對象)if (actionBar != null) { //有的話actionBar.setDisplayHomeAsUpEnabled(true); //顯示一個home按鈕,HomeAsUp這個按鈕的id永遠是HomeactionBar.setHomeAsUpIndicator(R.drawable.ic_menu); //設置一個圖標}//獲取抽屜的實例用于打開和關閉抽屜的事件mDrawerLayout = findViewById(R.id.my_drawer);//默認加載的碎片,text1replaceFragment(new Contacts_Fragment());//啟動彈窗//從文件讀取tag數據SharedPreferences preferences = getSharedPreferences("statata", MODE_PRIVATE);tag = preferences.getInt("tag", 0);if (tag == 0) { //tag沒被修改,顯示彈窗loding();}//設置側面munu的點擊事件NavigationView navigationView = (NavigationView) findViewById(R.id.my_nav_view);navigationView.setCheckedItem(R.id.nav_text1);navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {@Overridepublic boolean onNavigationItemSelected(MenuItem item) {//詳細設計抽屜菜單的點擊事件switch (item.getItemId()) {case R.id.nav_text1:replaceFragment(new Contacts_Fragment());mDrawerLayout.closeDrawer(navigationView);break;case R.id.nav_text2:replaceFragment(new Call_Fragment());mDrawerLayout.closeDrawer(navigationView);break;case R.id.nav_text3:replaceFragment(new Call_logs_Fragment());mDrawerLayout.closeDrawer(navigationView);break;case R.id.nav_text4:replaceFragment(new Help_Fragment());mDrawerLayout.closeDrawer(navigationView);break;case R.id.nav_text5:replaceFragment(new About_Fragment());mDrawerLayout.closeDrawer(navigationView);break;default:break;}return true;}});}//動態替換碎片private void replaceFragment(Fragment fragment) {FragmentManager fragmentManager = getSupportFragmentManager();FragmentTransaction transaction = fragmentManager.beginTransaction();transaction.replace(R.id.DTFragment, fragment);transaction.commit();}// ----toolbar邏輯----//傳統的方法實現菜單的植入(command + O快捷鍵)@Overridepublic boolean onCreateOptionsMenu(Menu menu) {//設置一個menugetMenuInflater().inflate(R.menu.toolbar, menu);return true;}//tab按鍵事件@Overridepublic boolean onOptionsItemSelected(MenuItem item) {//設置menu的選項點擊事件switch (item.getItemId()) {case android.R.id.home://這是內置的哦mDrawerLayout.openDrawer(GravityCompat.START);//將滑動菜單顯示出來,打開抽屜 參數是方式和xml設置的保持一致break;case R.id.DataDown:DataDown();//阿帕奇服務器上下載json數據break;case R.id.DataUp:try {UpData(GetJsonString());//先把數據導出成json文本在作為數據上傳到服務器} catch (JSONException e) {e.printStackTrace();} // Toast.makeText(this,"此功能開發中...",Toast.LENGTH_LONG).show();break;case R.id.settings:try {ExportData();Toast.makeText(this, "導出成功: 內部目錄file文件夾下", Toast.LENGTH_LONG).show();} catch (JSONException e) {e.printStackTrace();}break;case R.id.settings_1:if (Import().isEmpty()) {Toast.makeText(this, "導入失敗,沒有數據", Toast.LENGTH_LONG).show();} else {ImportData();Toast.makeText(this, "導入成功", Toast.LENGTH_LONG).show();}break;case R.id.settings_2:delall();break;default:}return true;}//1.下載時提示框public void DataDown() {//啟動彈窗//從文件讀取tag數據SharedPreferences preferences = getSharedPreferences("DataDown", MODE_PRIVATE);DataDownTag = preferences.getInt("DataDownTag", 0);if (DataDownTag == 0) { //tag沒被修改,顯示彈窗//inflate方法返回的是一個view(自定義那個),View loginview = LayoutInflater.from(this).inflate(R.layout.datadown_dialog_info, null);//loginview里的控件聲明一下CheckBox checkBox = loginview.findViewById(R.id.info_2);Button btnLogin = loginview.findViewById(R.id.datadown);btnLogin.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//把狀態計入文件供下次讀取if (checkBox.isChecked()) {//如果被選中SharedPreferences.Editor editor = getSharedPreferences("DataDown", MODE_MULTI_PROCESS).edit();editor.putInt("DataDownTag", 1);editor.apply();}dialog.dismiss();DownData();}});//設置Dialog和Viewbuilder5 = new AlertDialog.Builder(this);dialog = builder5.setTitle("重要提示").setView(loginview).setCancelable(false).show();} else {DownData();}}//從服務器下載json數據public void DownData() {new Thread(new Runnable() {@Overridepublic void run() {try {OkHttpClient client = new OkHttpClient();Request request = new Request.Builder()// 指定訪問的服務器地址是電腦本機.url("http://172.20.10.5:8081/pro").build();Response response = client.newCall(request).execute();String responseData = response.body().string();parseJSONWithGSON(responseData);//下載json數據導入解析} catch (Exception e) {e.printStackTrace();}}}).start();}//導入json進行解析的方法private void parseJSONWithGSON(String jsonData) {//Log.d("aaaaa---", jsonData);Contacts_Fragment contacts_fragment = (Contacts_Fragment) getSupportFragmentManager().findFragmentById(R.id.DTFragment);Gson gson = new Gson();List<Person> appList = gson.fromJson(jsonData, new TypeToken<List<Person>>() {}.getType());//c=appList;int tag = 0;//遍歷并先把數據存入數據庫for (Person person : appList) {person.setTel(formatInviteCode(person.getTel()));//獲取的數據同樣要處理if (RemoveRepetition(person.getName())) { //只有數據庫沒有時才更新contacts_fragment.Persons.add(person);person.save();tag = 1;}}if (tag == 1) {//只要有一個變就刷新//這個方法是在任務線程執行的,需要去主線程更新ui//加個下載成功的通知DataNotification();MainActivity.this.runOnUiThread(new Runnable() {public void run() {contacts_fragment.load();}});} else {DataFailNotification();//找不到服務器/導入的數據都有}}public Boolean RemoveRepetition(String name) {List<Person> Persons = DataSupport.select("name", "Tel", "Email").where("name like ?", "%" + name + "%").find(Person.class);if (Persons.size() != 0) {return false;//如果數據庫有,不更新}return true;}//下載成功的通知public void DataNotification() {NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);Notification notification = new NotificationCompat.Builder(this, "5996773").setContentTitle("文件下載的通知")//標題內容.setContentText("導入數據成功!")//正文內容.setWhen(System.currentTimeMillis())//指定通知被創建的時間.setSmallIcon(R.mipmap.ic_launcher)//設置通知小圖標.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//設置通知大圖標.setVibrate(new long[]{0, 1000, 1000, 1000})//通知到了讓手機震動.setLights(Color.GREEN, 1000, 1000)//設置手機前置LED燈的顯示效果.setPriority(NotificationCompat.PRIORITY_MAX)//設置通知的重要程度.build();//build完算是構建完成notification對象if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {NotificationChannel channel = new NotificationChannel("5996773", "安卓10的通知哦", NotificationManager.IMPORTANCE_HIGH);channel.enableLights(true);//是否在桌面icon右上角展示小紅點channel.setLightColor(Color.GREEN);//小紅點顏色channel.setShowBadge(false); //是否在久按桌面圖標時顯示此渠道的通知manager.createNotificationChannel(channel);//將channel設置進manager}//id唯一即可manager.notify(1, notification);}//下載失敗的通知public void DataFailNotification() {NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);Notification notification = new NotificationCompat.Builder(this, "5996774").setContentTitle("文件下載的通知")//標題內容.setContentText("文件下載成功,但您不需要導入!")//正文內容.setWhen(System.currentTimeMillis())//指定通知被創建的時間.setSmallIcon(R.mipmap.ic_launcher)//設置通知小圖標.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//設置通知大圖標.setVibrate(new long[]{0, 1000, 1000, 1000})//通知到了讓手機震動.setLights(Color.GREEN, 1000, 1000)//設置手機前置LED燈的顯示效果.setPriority(NotificationCompat.PRIORITY_MAX)//設置通知的重要程度.build();//build完算是構建完成notification對象if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {NotificationChannel channel = new NotificationChannel("5996774", "安卓10的通知哦", NotificationManager.IMPORTANCE_HIGH);channel.enableLights(true);//是否在桌面icon右上角展示小紅點channel.setLightColor(Color.GREEN);//小紅點顏色channel.setShowBadge(false); //是否在久按桌面圖標時顯示此渠道的通知manager.createNotificationChannel(channel);//將channel設置進manager}//id唯一即可manager.notify(2, notification);}//2.上傳json到服務器public void UpData(String body){new Thread(new Runnable(){@Overridepublic void run(){String url="http://172.20.10.5:8081/air";//你要訪問的地址String mesg = null;//定義服務器返回的結果HttpClient httpClient = new HttpClient();//構建HttpClient實例httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(60000); //設置請求超時時間httpClient.getHttpConnectionManager().getParams().setSoTimeout(60000);//設置響應超時時間PostMethod postMethod=new PostMethod(url);//構造PostMethod的實例postMethod.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET,"utf-8");String jsonString="{'data':" + body + "}";//com.alibaba.fastjson.JSONObject意思是導入阿里的parseObject,parseObject第一個參數是json對象Map<String,Object> map = com.alibaba.fastjson.JSONObject.parseObject(jsonString,Map.class);Set<String> set = map.keySet();for(String s : set){postMethod.addParameter(s,map.get(s).toString());}try {httpClient.executeMethod(postMethod);//執行post請求mesg = postMethod.getResponseBodyAsString();//可以對響應回來的報文進行處理//Log.d("aaaa--", mesg);//獲取成功!!!!} catch (HttpException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}finally{if(mesg!=null){//有數據DataupNotification();//文件上傳成功的通知}//關閉連接釋放資源的方法postMethod.releaseConnection();httpClient.getHttpConnectionManager().closeIdleConnections(0);}}}).start();}//上傳成功的通知public void DataupNotification() {NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);Notification notification = new NotificationCompat.Builder(this, "599111").setContentTitle("文件上傳的通知")//標題內容.setContentText("上傳數據成功!")//正文內容.setWhen(System.currentTimeMillis())//指定通知被創建的時間.setSmallIcon(R.mipmap.ic_launcher)//設置通知小圖標.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))//設置通知大圖標.setVibrate(new long[]{0, 1000, 1000, 1000})//通知到了讓手機震動.setLights(Color.GREEN, 1000, 1000)//設置手機前置LED燈的顯示效果.setPriority(NotificationCompat.PRIORITY_MAX)//設置通知的重要程度.build();//build完算是構建完成notification對象if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {NotificationChannel channel = new NotificationChannel("599111", "安卓10的通知哦", NotificationManager.IMPORTANCE_HIGH);channel.enableLights(true);//是否在桌面icon右上角展示小紅點channel.setLightColor(Color.GREEN);//小紅點顏色channel.setShowBadge(false); //是否在久按桌面圖標時顯示此渠道的通知manager.createNotificationChannel(channel);//將channel設置進manager}//id唯一即可manager.notify(3, notification);}//3.導出json到文件public void ExportData() throws JSONException {String str = GetJsonString();save(str);}//導出json文本public String GetJsonString() throws JSONException {//獲取碎片實例以調用碎片中的方法Contacts_Fragment contacts_fragment = (Contacts_Fragment) getSupportFragmentManager().findFragmentById(R.id.DTFragment);List<Person> Persons = contacts_fragment.Persons;//轉換list為jsonJSONArray jsonArray = new JSONArray();JSONObject jsonObject = new JSONObject();//這個巨坑,注意包JSONObject tmpObj = null;int count = Persons.size();for (int i = 0; i < count; i++) {tmpObj = new JSONObject();tmpObj.put("name", Persons.get(i).getName());tmpObj.put("Email", Persons.get(i).getEmail());tmpObj.put("Tel", Persons.get(i).getTel());jsonArray.put(tmpObj);tmpObj = null;}String personInfos = jsonArray.toString(); // 將JSONArray轉換得到String//Log.d("DDDDD",personInfos);return personInfos;}//將一段json文本保存到文件中去public void save(String inputText) {FileOutputStream out = null;BufferedWriter writer = null;//command + alt + t 快速生成try {File file = new File(getCacheDir().getPath(), "data.json");if (file != null) {file.delete();} else {file.createNewFile();}out = openFileOutput("data.json", Context.MODE_PRIVATE); // out = new FileOutputStream(file);// OutputStreamWriter是從字符流到字節流的橋接:字節流搞成了字符流傳遞給緩沖流writer = new BufferedWriter(new OutputStreamWriter(out));// 寫入writer.write(inputText);} catch (IOException e) {e.printStackTrace();} finally {try {if (writer != null) {writer.close();//關閉上層流(緩沖流),下層數據流就自動關了}} catch (IOException e) {e.printStackTrace();}}}//4.從文件導入jsonpublic void ImportData() {String str1 = Import();parseJSONWithGSON(str1);//顯示}//從文件中讀取json文件public String Import() {//準備從文件讀數據FileInputStream in = null;//緩沖流,輸入BufferedReader reader = null;StringBuilder content = new StringBuilder();try {in = openFileInput("data.json");reader = new BufferedReader(new InputStreamReader(in));String line = "";//reader.readLine()是讀取一行while ((line = reader.readLine()) != null) {content.append(line);}} catch (IOException e) {e.printStackTrace();} finally {if (reader != null) {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}return content.toString();}//初始化加載private void loding() {//inflate方法返回的是一個view(自定義那個),View loginview = LayoutInflater.from(this).inflate(R.layout.ui_dialog_info, null);//loginview里的控件聲明一下CheckBox checkBox = loginview.findViewById(R.id.info_2);Button btnLogin = loginview.findViewById(R.id.info_3);btnLogin.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) { // 把狀態計入文件供下次讀取if (checkBox.isChecked()) {//如果被選中SharedPreferences.Editor editor = getSharedPreferences("statata", MODE_MULTI_PROCESS).edit();editor.putInt("tag", 1);editor.apply();}dialog.dismiss();}});//設置Dialog和Viewbuilder5 = new AlertDialog.Builder(this);dialog = builder5.setTitle("重要信息").setView(loginview).setCancelable(false).show();}//5.一鍵刪除public void delall() {DataSupport.deleteAll(Person.class);DataSupport.deleteAll(recording.class);Toast.makeText(this, "刪除全部數據成功!", Toast.LENGTH_SHORT).show();}//處理一下系統獲取聯系人時字符串的-/空格public String formatInviteCode(String str1) {String str2 = "";for (int i = 0; i < str1.length(); i++) {if (Character.isDigit(str1.charAt(i))) {//判斷被索引處的字符是不是數字str2 += str1.charAt(i);}}return str2;} }五、錯誤總結
1、錯誤:
no such column: userName
解決:格式錯誤,類型TEXT前面必須有一個空格!
UserInfoEntry.COLUMN_USER_NAME + " TEXT NOT NULL, "2、類型前面全部都添加了空格,仍舊報錯:
no such column: userName
解決:數據庫沒有更新,更新版本號。在生產應用程序中,可能會修改此方法以更改表格,而不是刪除它,以便不刪除現有數據。
新手學習中,尚不知道如何修改。
//如果更改數據庫架構,則必須增加數據庫版本
3、錯誤:
android.database.CursorIndexOutOfBoundsException: Index -1 requested,
with a size of 1
解決:下標越界,cursor下標從-1開始,而不是0,需要移動,添加if語句。其實還是不太明白,為什么移動到第一個就可以了。
if (cursor.moveToFirst()){ queryPassword=cursor.getString(cursor.getColumnIndex(UserInfoContract.UserInfoEntry.COLUMN_USER_PASSWORD)); }4、錯誤:
android.database.sqlite.SQLiteException: no such column:
錯誤例子:
return mDb.update(DATABASE_TABLE, args, KEY_SSID + "=" + ssid , null) > 0;原因:
在執行數據庫的增刪改查操作時,如:update、query等方法,傳入的參數是字符串類型。
其實這些方法會將參數拼接成sql語句,而字符串類型需要用引號引上
所以上方錯誤的代碼應改為:
六、個人心得體會
SQLite的基本數據類型:Byte,Long,Short,Integer,Float,Double,String,Boolean,byte[]
理論存儲容量為140TB
SQLiteOpenHelper
既然是數據庫的增刪改查,我們首先需要一個數據庫,而數據庫要通過SQLiteOpenHelper的子類生成,所以首先需要建一個類來繼承SQLiteOpenHelper。由于這個類是一個抽象類,我們需要實現他的構造方法和抽象方法。所以:
需要重載的方法
onCreate:創建表和一些基本屬性。
onUpgrade:數據庫需要更新的時候使用,前提是已經存在了一個相同的數據庫,需要新加表,或者原來的表字段要修改。
舉例:現在創建一個名為table1的表:直接執行SQL語句
表的增刪改查
public class DBManger {private Context context;private static DBManger instance;// 操作表的對象,進行增刪改查private SQLiteDatabase writableDatabase;private DBManger(Context context) {this.context = context;DBHelper dbHelper = new DBHelper(context, 1); //通過DBHelper的getWritableDatabase方法得到SQLiteDatabase對象,SQLiteDatabase可以對數據庫表進行操作writableDatabase = dbHelper.getWritableDatabase();}public static DBManger getInstance(Context context) {if (instance == null) {synchronized (DBManger.class) {if (instance == null) {instance = new DBManger(context);}}}return instance;} }增加:
public void add() {ContentValues contentValues = new ContentValues();byte _byte = Byte.MAX_VALUE;contentValues.put("_byte", _byte);long _long = Long.MAX_VALUE;contentValues.put("_long", _long);String _text = "字符串";contentValues.put("_text", _text);short _short = Short.MAX_VALUE;contentValues.put("_short", _short);int _int = Integer.MAX_VALUE;contentValues.put("_int", _int);float _float = Float.MAX_VALUE;contentValues.put("_float", _float);double _double = Double.MAX_VALUE;contentValues.put("_double", _double);boolean _boolean = true;contentValues.put("_boolean", _boolean);byte[] _byteArr = {Byte.MIN_VALUE, Byte.MAX_VALUE};contentValues.put("_blob", _byteArr);writableDatabase.insert("table1", null, contentValues);}刪:刪除_int = Integer.MAX_VALUE的值
public void del() {writableDatabase.delete("table1", "_int = ?", new String[]{Integer.MAX_VALUE + ""}); }改:調用update方法,參數依次:表名、包裹、條件、條件的值
public void update() {ContentValues contentValues = new ContentValues();contentValues.put("_text", "修改后的字符串");writableDatabase.update("table1", contentValues, "_text = ?", new String[]{"字符串"});}查:首先query方法返回了一個Cursor對象,用來定位。
Query的參數:
String table:表名
String[] columns:需要被查詢的列名
String selection:代表只查多少條數據,比如只查10條數據,就直接傳遞一個10就行了。
String[] selectionArgs:代表只查多少條數據,比如只查10條數據,就直接傳遞一個10就行了。
String groupBy:將相同值劃分為一組
String having:條件判斷的參數,不過這個要跟groupby這個參數同時存在,也就是要同時使用。也就是說,在groupby篩選后的數據中,再使用這個所謂的having再篩選一次。
String orderBy:代表排序,可以傳某個字段,比如_byte,這就代表按照這個字段排序,默認是升序,也就是從小到大排序,我們可以這樣傳遞參數,讓其降序排列_byte desc,如果是_byte asc就是升序,由于默認升序,所以這個asc也就省略了。
String limit:代表只查多少條數據,比如只查10條數據,就直接傳遞一個10就行了。
總結
- 上一篇: 实例33:python
- 下一篇: js变量提升_学习笔记:JS中的作用域和