国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Android基礎(chǔ)教程——從入門到精通(上)

這篇具有很好參考價(jià)值的文章主要介紹了Android基礎(chǔ)教程——從入門到精通(上)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

  • 本文是對(duì)B站教程 動(dòng)腦學(xué)院 Android教程 學(xué)習(xí)過程中所做的筆記。
  • 文章分為上下兩部分,此文是上部分,下部分鏈接為:Android基礎(chǔ)教程——從入門到精通(下)
  • 源視頻教程并沒有錄制全,本文還補(bǔ)充了 Service 和 網(wǎng)絡(luò)通信 的內(nèi)容
  • 文章介紹詳細(xì),示例代碼豐富,相信跟著本教程可以打下很好的Android基礎(chǔ)。

一、開發(fā)環(huán)境搭建

  1. 安裝android studio
  2. 安裝 sdk(當(dāng)前使用最新版33)

生皮鞣制工藝流程,android,android,gradle,android studio

  1. 手動(dòng)下載gradle

    (更新:弄完之后有時(shí)候沒用,可以再試試掛梯子,換網(wǎng)絡(luò)之類的)

    如果第一次啟動(dòng)AndroidStudio沒有報(bào)錯(cuò)則無需設(shè)置,這里是因?yàn)槲覇?dòng)完之后下載gradle報(bào)錯(cuò):

    could not install gradle distribution from 'https://services.gradle.org/dist
    

    可能是網(wǎng)絡(luò)問題連接不到,所以手動(dòng)下載。

    點(diǎn)擊上面提示的鏈接下載壓縮包,然后解壓到C:\Users\OYMN\.gradle\wrapper\dists\gradle-7.2-bin\2dnblmf4td7x66yl1d74lt32g

    生皮鞣制工藝流程,android,android,gradle,android studio

  2. 安裝模擬器

    使用androidstudio提供的模擬器,或者自行下載第三方安卓模擬器(雷電模擬器)

二、簡(jiǎn)單控件

1. 文本顯示

設(shè)置文本內(nèi)容有兩種方式:

  • 在 XML 文件中通過屬性 android:text 設(shè)置文本
  • 在 Java 代碼中調(diào)用文本視圖對(duì)象的 setText 方法設(shè)置文本

引用字符串資源:

  • 在XML文件中引用(@string/xxx)
  • 在Java代碼中引用(R.string.xxx)

其余設(shè)置文本字體大小,顏色等都是可以通過關(guān)鍵詞+代碼提示很容易就能知道怎么寫,這里就不贅述。

2. 按鈕

Button繼承于TextView,因此它們擁有的屬性都是共通的。

除此之外,Button最重要的是點(diǎn)擊事件。

  • 點(diǎn)擊監(jiān)聽器:通過setOnClickListener方法設(shè)置。按鈕被按住少于500毫秒時(shí),會(huì)觸發(fā)點(diǎn)擊事件。

  • 長(zhǎng)按監(jiān)聽器:通過setOnLongClickListener方法設(shè)置。按鈕被按住超過500毫秒時(shí),會(huì)觸發(fā)長(zhǎng)按事件。

3. 常用布局

(1)線性布局LinearLayout

特點(diǎn):要不水平排列,要不豎直排列,通過orintation進(jìn)行設(shè)置(horiztal為水平,vertical為豎直)

權(quán)重屬性:通過layout_weight來設(shè)置,在線性布局的直接下級(jí)進(jìn)行設(shè)置,表示該下級(jí)布局占據(jù)的寬高比例。

  • layout_width填0dp時(shí),layout_weight表示水平方向的寬度比例。
  • layout_height填0dp時(shí),layout_weight表示垂直方向的高度比例。

(3)相對(duì)布局RelativeLayout

相對(duì)布局中的視圖位置由兩個(gè)因素所影響:

  • 與該視圖平級(jí)的其他視圖
  • 上級(jí)視圖(也就是它歸屬的RelativeLayout)

相對(duì)位置的一些取值:

生皮鞣制工藝流程,android,android,gradle,android studio

(3)網(wǎng)格布局GridLayout

顧名思義該布局適用于表格類型的布局。

4. 圖像顯示

圖片一般放在res/drawable目錄下,設(shè)置圖像顯示一般有兩種方法:

  • 在XML文件中,通過屬性android:src設(shè)置圖片資源,屬性值格式形如 @drawable/不含擴(kuò)展名的圖片名稱。
  • 在Java代碼中,調(diào)用setImageResource方法設(shè)置圖片資源,方法參數(shù)格式形如 R.drawable.不含擴(kuò)展名的圖片名稱。

(1)圖像的縮放問題:

ImageView本身默認(rèn)圖片居中顯示,若要改變圖片的顯示方式,可通過scaleType屬性設(shè)定,該屬性的取值說明如下:

生皮鞣制工藝流程,android,android,gradle,android studio

(2)圖像按鈕ImageButton:

ImageButton是顯示圖片的圖像按鈕,但它繼承自ImageView,而非繼承Button。

ImageButton和Button之間的區(qū)別有:

  • Button既可顯示文本也可顯示圖片,ImageButton只能顯示圖片不能顯示文本。
  • ImageButton上的圖像可按比例縮放,而Button通過背景設(shè)置的圖像會(huì)拉伸變形。
  • Button只能靠背景顯示一張圖片,而ImageButton可分別在前景和背景顯示圖片,從而實(shí)現(xiàn)兩張圖片疊加的效果。

三、Activity

Activity是安卓開發(fā)四大組件之一,非常重要。

1. Activity的啟動(dòng)和結(jié)束

Activity的啟動(dòng)這里指的是跳轉(zhuǎn),從一個(gè)頁面跳轉(zhuǎn)到一個(gè)新的頁面,就相當(dāng)于啟動(dòng)了一個(gè)新的頁面。

示例:

bt.setOnClickListener(new View.OnClickListener(){

    @Override
    public void onClick(View v) {
        Intent intent = new Intent();
        intent.setClass(MainActivity.this, MainActivity2.class);
        startActivity(intent);
    }
});

結(jié)束Activity:調(diào)用 finish()。

2. Activity的生命周期

onCreate:此時(shí)將頁面布局加載到內(nèi)存中,初始化頁面。

onStart:將頁面展示在屏幕。

onResume:此時(shí)頁面能夠和用戶進(jìn)行交互。

onPause:頁面進(jìn)入暫停狀態(tài),無法和用戶進(jìn)行交互。

onStop:頁面不在屏幕顯示。

onDestory:回收Activity占用的資源,徹底銷毀該Activity。

onRestart:onStop狀態(tài)可以轉(zhuǎn)為onRestart狀態(tài)。

onNewIntent:重用已存在的活動(dòng)實(shí)例。如果一個(gè)Activity已經(jīng)啟動(dòng)了,并且存在與當(dāng)前棧,而當(dāng)前棧的啟動(dòng)模式為SingleTask,SingleInstance,SingleTop(此時(shí)在任務(wù)棧頂端),那么再次啟動(dòng)該Activity的話,并不會(huì)重新進(jìn)行onCreate,而是會(huì)執(zhí)行onNewIntent方法。

生皮鞣制工藝流程,android,android,gradle,android studio

3. Activity的啟動(dòng)模式

Android允許在創(chuàng)建Activity時(shí)設(shè)置啟動(dòng)模式,通過啟動(dòng)模式控制Activity的出入棧行為。

(1)靜態(tài)設(shè)置

設(shè)置方式:打開AndroidManifest.xml文件,給activity添加屬性android:launchMode。如以下表示該activity使用standard標(biāo)準(zhǔn)模式,默認(rèn)也是標(biāo)準(zhǔn)模式。

<activity android:name=".JumpFirstActivity" android:launchMode="standard" />

launchMode的取值有:

生皮鞣制工藝流程,android,android,gradle,android studio

生皮鞣制工藝流程,android,android,gradle,android studio

生皮鞣制工藝流程,android,android,gradle,android studio

生皮鞣制工藝流程,android,android,gradle,android studio

生皮鞣制工藝流程,android,android,gradle,android studio

(2)動(dòng)態(tài)設(shè)置

通過 Intent 動(dòng)態(tài)設(shè)置 Activity啟動(dòng)模式:

intent.setFlags();

4. Activity之間傳遞信息

Intent能夠讓Android各組件之間進(jìn)行溝通。

Intent可以完成3部分工作:

  • 表明本次通信從哪里來,往哪里走,要怎么走。
  • 發(fā)送方可以攜帶消息給接收方,接收方可以從收到的Intent解析數(shù)據(jù)。
  • 發(fā)送方如果想要知道接收方的處理結(jié)果,接收方也可以通過Intent返回結(jié)果。

Intent的一些組成元素:

生皮鞣制工藝流程,android,android,gradle,android studio

(1)顯式Intent和隱式Intent

1. 顯式Intent

創(chuàng)建方式:

  • 在Intent的構(gòu)造函數(shù)中指定:

    Intent intent = new Intent(this, NextActivity.class);
    
  • 調(diào)用setClass指定:

    Intent intent = new Intent();
    intent.setClass(this, NextActivity.class);
    
  • 調(diào)用setComponent指定:

    Intent intent = new Intent();
    ComponentName component = new ComponentName(this, NextActivity.class);
    intent.setComponent(component);
    
2. 隱式Intent:

沒有明確指定所要跳轉(zhuǎn)的頁面,而是通過一些動(dòng)作字符串來讓系統(tǒng)自動(dòng)匹配。

通常是App不想向外暴露Activity的名稱,只給出一些定義好的字符串。這些字符串可以自己定義,也有系統(tǒng)定義的。

常見的系統(tǒng)動(dòng)作如下:

生皮鞣制工藝流程,android,android,gradle,android studio

下面以調(diào)用系統(tǒng)撥號(hào)頁面舉例:

String phone = "12345";
Intent intent = new Intent();
//這里表示設(shè)置意圖動(dòng)作為準(zhǔn)備撥號(hào)
intent.setAction(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:" + phone));
startActivity(intent);

如果想要跳轉(zhuǎn)到自己定義的activity:

步驟一:在AndroidManifest.xml找到該activity,添加action和category標(biāo)簽,同時(shí)設(shè)置exported為true,表示允許被其他activity調(diào)用。

生皮鞣制工藝流程,android,android,gradle,android studio

步驟二:調(diào)用過程和上面一樣:

Intent intent = new Intent();
intent.setAction("android.intent.action.activity2");
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);

(2)向下一個(gè)Activity發(fā)送消息:

Intent重載了很多putExtra方法用于傳遞各種類型的信息,包括整數(shù)類型,字符串等。但是顯然通過調(diào)用putExtra方法會(huì)很不好管理,因?yàn)閿?shù)據(jù)都是零碎傳遞。所以Android引入了Bundle,其內(nèi)部是一個(gè)Map,使用起來也和Map一樣。

生皮鞣制工藝流程,android,android,gradle,android studio

示例:

Intent intent = new Intent(this, NextActivity.class);
//通過bundle包裝數(shù)據(jù)
Bundle bundle = new Bundle();
bundle.putString("stringKey", "stringValue");
intent.putExtras(bundle);
startActivity(intent);

然后下一個(gè)Activity就可以通過intent獲取到所想要的數(shù)據(jù)了:

Bundle bundle = getIntent().getExtras();
String stringValue = bundle.getString("stringKey");

(3)向上一個(gè)Activity返回消息:

上一個(gè)頁面跳轉(zhuǎn)到下一個(gè)頁面,同時(shí)攜帶數(shù)據(jù):

private ActivityResultLauncher<Intent> register;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main2);

    findViewById(R.id.bt).setOnClickListener(this);

    //回調(diào)函數(shù),返回到這個(gè)頁面時(shí)所執(zhí)行的程序
    register = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
       			//回調(diào)函數(shù)
                @Override
                public void onActivityResult(ActivityResult result) {
                    if (result != null) {
                        Intent intent = result.getData();
                        if (intent != null && result.getResultCode() == Activity.RESULT_OK) {
                            //獲取到返回的數(shù)據(jù)
                            Bundle bundle = intent.getExtras();
                            //...
                        }
                    }
                }
            });
}

@Override
public void onClick(View v) {
    Intent intent = new Intent(this, MainActivity3.class);
    //跳轉(zhuǎn)下一頁面
    register.launch(intent);

}

下一個(gè)頁面接受到數(shù)據(jù),處理之后返回結(jié)果給上一個(gè)頁面:

Bundle bundle = getIntent().getExtras();
//...頁面進(jìn)行處理
//返回?cái)?shù)據(jù)給上一個(gè)頁面
Bundle bundle = new Bundle();
bundle.putString("stringKey", "stringValue");
intent.putExtras(bundle);
setResult(Activity.RESULT_OK, intent);
finish();

5. Activity獲取一些附加信息

(1)獲取資源信息:

//獲取strings.xml中的字符串資源
String text = getString(R.string.text);
//獲取color.xml中的顏色資源
int black = getColor(R.color.black);

(2)獲取元數(shù)據(jù)信息:

try {
    //獲取包管理器
    PackageManager pm = getPackageManager();
    //獲取當(dāng)前的Activity信息
    ActivityInfo activityInfo = pm.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
    Bundle bundle = activityInfo.metaData;
    String text2 = bundle.getString("text2");

} catch (PackageManager.NameNotFoundException e) {
    e.printStackTrace();
}

四、數(shù)據(jù)存儲(chǔ)

1. 共享參數(shù)SharedPreferences

(1)使用:

sharedPreferences是安卓的一個(gè)輕量級(jí)存儲(chǔ)工具,采用的方式是key-value,以xml文件形式存在,文件路徑為/data/data/應(yīng)用包名/shared_prefs/文件名.xml。


適合場(chǎng)景:

  1. 簡(jiǎn)單且孤立的數(shù)據(jù)
  2. 文本數(shù)據(jù),二進(jìn)制數(shù)據(jù)則不合適
  3. 需要持久化的數(shù)據(jù),也就是重啟APP后數(shù)據(jù)仍然存在且有效。

實(shí)際開發(fā)中,sharedPreferences經(jīng)常用來存儲(chǔ)的數(shù)據(jù)有:APP的個(gè)性化配置信息,用戶使用APP的行為信息等。

sharedPreferences對(duì)數(shù)據(jù)的存儲(chǔ)和讀取類似Map,提供put和set方法。

獲取數(shù)據(jù)可以通過SharedPreferences對(duì)象獲取:

//第一個(gè)參數(shù)表示文件名,第二個(gè)參數(shù)表示私有模式
SharedPreferences shared = getSharedPreferences("fileName", MODE_PRIVATE);
String name = shared.getString("name");

而存儲(chǔ)數(shù)據(jù)則還需要借助Editor類:

SharedPreferences.Editor editor = shared.edit();
editor.putString("name", "oymn");
editor.putInt("age", 20);
editor.commit();

(2)應(yīng)用實(shí)例:記住密碼功能

  1. 聲明一個(gè)共享參數(shù)對(duì)象,并在onCreate中調(diào)用getSharedPreferences方法獲取共享參數(shù)的實(shí)例。
  2. 登錄成功時(shí),如果用戶勾選了“記住密碼”,就使用共享參數(shù)保存手機(jī)號(hào)碼與密碼。

所以在登錄頁面的onCreat方法中添加獲取共享參數(shù)的代碼:

// 從share_login.xml獲取共享參數(shù)對(duì)象
mShared = getSharedPreferences("share_login", MODE_PRIVATE);
// 獲取共享參數(shù)保存的手機(jī)號(hào)碼
String phone = mShared.getString("phone", "");
// 獲取共享參數(shù)保存的密碼
String password = mShared.getString("password", "");
et_phone.setText(phone); // 往手機(jī)號(hào)碼編輯框填寫上次保存的手機(jī)號(hào)
et_password.setText(password); // 往密碼編輯框填寫上次保存的密碼

接著在登錄成功方法中添加保存功能:

// 如果勾選了“記住密碼”,就把手機(jī)號(hào)碼和密碼都保存到共享參數(shù)中
if (isRemember) {
    SharedPreferences.Editor editor = mShared.edit(); // 獲得編輯器的對(duì)象
    editor.putString("phone", et_phone.getText().toString()); // 添加名叫phone的手機(jī)號(hào)碼
    editor.putString("password", et_password.getText().toString()); // 添加名叫password的密碼
    editor.commit(); // 提交編輯器中的修改
}

2. 數(shù)據(jù)庫SQLite

SQLite是安卓的一種小巧的嵌入式數(shù)據(jù)庫,基本使用和思路和Mysql無異。

(1)SQLiteDatabase

java代碼層面借助SQLiteDatabase來對(duì)SQLite進(jìn)行操作。

//創(chuàng)建數(shù)據(jù)庫text.db
SQLiteDatabase db = openOrCreateDatabase(getFileDir() + "/test.db", Context.MODE_PRIVATE, null);

生皮鞣制工藝流程,android,android,gradle,android studio

(2)SQLiteOpenHelper

由于SQLiteDatabase存在局限性,一不小心就會(huì)重復(fù)打開數(shù)據(jù)庫,處理數(shù)據(jù)庫的升級(jí)也不方便;因此Android提供了數(shù)據(jù)庫幫助器SQLiteOpenHelper,幫助開發(fā)者合理使用SQLite。

SQLiteOpenHelper的具體使用步驟如下:

  • 步驟一,新建一個(gè)繼承自SQLiteOpenHelper的數(shù)據(jù)庫操作類,按提示重寫onCreate和onUpgrade兩個(gè)方法。其中,onCreate方法只在第一次打開數(shù)據(jù)庫時(shí)執(zhí)行,在此可以創(chuàng)建表結(jié)構(gòu);而onUpgrade方法在數(shù)據(jù)庫版本升高時(shí)執(zhí)行,在此可以根據(jù)新舊版本號(hào)變更表結(jié)構(gòu)。
  • 步驟二,為保證數(shù)據(jù)庫安全使用,需要封裝幾個(gè)必要方法,包括獲取單例對(duì)象、打開數(shù)據(jù)庫連接、關(guān)閉數(shù)據(jù)庫連接,說明如下:
    • 獲取單例對(duì)象:確保在App運(yùn)行過程中數(shù)據(jù)庫只會(huì)打開一次,避免重復(fù)打開引起錯(cuò)誤。
    • 打開數(shù)據(jù)庫連接:SQLite有鎖機(jī)制,即讀鎖和寫鎖的處理;故而數(shù)據(jù)庫連接也分兩種,讀連接可調(diào)用getReadableDatabase方法獲得,寫連接可調(diào)用getWritableDatabase獲得。
    • 關(guān)閉數(shù)據(jù)庫連接:數(shù)據(jù)庫操作完畢,調(diào)用數(shù)據(jù)庫實(shí)例的close方法關(guān)閉連接。
  • 步驟三, 提供對(duì)表記錄增加、刪除、修改、查詢的操作方法。能被SQLite直接使用的數(shù)據(jù)結(jié)構(gòu)是ContentValues類,它類似于映射Map,也提供了put和get方法存取鍵值對(duì)。
    • 區(qū)別之處在于:ContentValues的鍵只能是字符串,不能是其他類型。ContentValues主要用于增加記錄和更新記錄,對(duì)應(yīng)數(shù)據(jù)庫的insert和update方法。
    • 記錄的查詢操作用到了游標(biāo)類Cursor,調(diào)用query和rawQuery方法返回的都是Cursor對(duì)象,若要獲取全部的查詢結(jié)果,則需根據(jù)游標(biāo)的指示一條一條遍歷結(jié)果集合。Cursor的常用方法可分為3類,說明如下:

生皮鞣制工藝流程,android,android,gradle,android studio

(3)代碼舉例:

public class UserDBHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "user.db";   //數(shù)據(jù)庫名稱
    private static final int DB_VERSION = 1;   //數(shù)據(jù)庫的版本號(hào)
    private static UserDBHelper helper = null;   //單例
    private SQLiteDatabase sdb = null;  //數(shù)據(jù)庫實(shí)例
    public static final String TABLE_NAME = "user_info";   //表名

    public UserDBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    public UserDBHelper(Context context, int version) {
        super(context, DB_NAME, null, version);
    }

    //通過單例模式獲取 UserDBHelper 的唯一實(shí)例
    public static synchronized UserDBHelper getInstance(Context context, int version) {
        if (version > 0 && helper == null) {
            helper = new UserDBHelper(context, version);
        } else if (helper == null) {
            helper = new UserDBHelper(context);
        }

        return helper;
    }

    //打開讀連接
    public SQLiteDatabase openReadLink() {
        if (sdb == null || !sdb.isOpen()) {
            sdb = helper.getReadableDatabase();
        }

        return sdb;
    }

    //打開寫連接
    public SQLiteDatabase openWriteLink() {
        if (sdb == null || !sdb.isOpen()) {
            sdb = helper.getWritableDatabase();
        }

        return sdb;
    }

    //關(guān)閉數(shù)據(jù)庫連接
    public void closeLink() {
        if (sdb != null && sdb.isOpen()) {
            sdb.close();
            sdb = null;
        }
    }

    //創(chuàng)建數(shù)據(jù)庫,執(zhí)行建表語句
    @Override
    public void onCreate(SQLiteDatabase db) {
        //先刪除已存在表
        String drop_sql = "drop table if exists " + TABLE_NAME + ";";
        db.execSQL(drop_sql);

        //創(chuàng)建表
        String create_sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
                + "_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
                + "name VARCHAR NOT NULL," + "age INTEGER NOT NULL,"
                + "height INTEGER NOT NULL," + "weight FLOAT NOT NULL,"
                + "married INTEGER NOT NULL," + "update_time VARCHAR NOT NULL"
                //演示數(shù)據(jù)庫升級(jí)時(shí)要先把下面這行注釋
                + ",phone VARCHAR" + ",password VARCHAR"
                + ");";

        db.execSQL(create_sql);
    }

    //修改表結(jié)構(gòu)
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if (newVersion > 1) {
            //Android的ALTER命令不支持一次添加多列,只能分多次添加
            String alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN phone VARCHAR;";
            db.execSQL(alter_sql);

            alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "password VARCHAR;";
            db.execSQL(alter_sql); // 執(zhí)行完整的SQL語
        }
    }

    //根據(jù)指定條件刪除記錄
    public int delete(String condition) {
        return sdb.delete(TABLE_NAME, condition, null);
    }

    //刪除全部記錄
    public int deleteAll() {
        return sdb.delete(TABLE_NAME, "1=1", null);
    }

    //根據(jù)條件查詢記錄
    public List<UserInfo> query(String condition) {
        String sql = String.format("select rowid,_id,name,age,height,weight,married,update_time," +
                "phone,password from %s where %s;", TABLE_NAME, condition);
        //執(zhí)行查詢語句,該語句返回結(jié)果集的游標(biāo)
        Cursor cursor = sdb.rawQuery(sql, null);

        ArrayList<UserInfo> userInfos = new ArrayList<>();

        //循環(huán)取出游標(biāo)指向的結(jié)果集
        while (cursor.moveToNext()) {
            UserInfo userInfo = new UserInfo();
            userInfo.name = cursor.getString(2);
            userInfo.age = cursor.getInt(3);
            userInfos.add(userInfo);
        }

        cursor.close();
        return userInfos;
    }

    //往表里添加一條記錄
    public long insert(UserInfo userinfo) {
        ArrayList<UserInfo> userInfos = new ArrayList<>();
        userInfos.add(userinfo);
        return insert(userInfos);
    }

    //往表里添加多條記錄
    public long insert(List<UserInfo> userInfos) {

        long result = -1;

        for (UserInfo userInfo : userInfos) {
            //如果名字相同,則更新記錄
            if (userInfo.name != null && userInfo.name.length() > 0) {
                String condition = String.format("name = '%s'", userInfo.name);
                List<UserInfo> dbUserInfoList = query(condition);
                if (dbUserInfoList != null && dbUserInfoList.size() > 0) {
                    update(userInfo, condition);
                    //返回其id
                    result = dbUserInfoList.get(0).id;
                    continue;
                }
            }
            //其余情況則說明記錄不重復(fù),添加新紀(jì)錄
            ContentValues cv = new ContentValues();
            cv.put("name", userInfo.name);
            cv.put("age", userInfo.age);
            result = sdb.insert(TABLE_NAME, "", cv);
            if(result == -1){
                return result;
            }
        }

        return result;
    }

    //根據(jù)指定條件更新表記錄
    public int update(UserInfo userInfo, String condition) {

        ContentValues cv = new ContentValues();
        cv.put("name", userInfo.name);
        cv.put("age", userInfo.age);

        return sdb.update(TABLE_NAME, cv, condition, null);
    }

}

(4)優(yōu)化記住密碼:

上面通過SharedPreferences存儲(chǔ)密碼的方式還是存在一定的局限性,該方式只能記住一個(gè)用戶的登錄信息,當(dāng)下一個(gè)用戶登錄后,上一個(gè)用戶的信息將會(huì)被覆蓋。正確的記住密碼功能應(yīng)該是輸入手機(jī)號(hào)自動(dòng)補(bǔ)充密碼,因此,可以考慮使用數(shù)據(jù)庫來進(jìn)行存儲(chǔ)。

主要的改造如下:

  1. 聲明一個(gè)數(shù)據(jù)庫的helper對(duì)象,在Activity的OnResume方法中獲取數(shù)據(jù)庫連接,在OnPause方法中關(guān)閉數(shù)據(jù)庫連接。
private UserDBHelper helper;

@Override
protected void onResume() {
    super.onResume();
    //獲取數(shù)據(jù)庫幫助器實(shí)例 (此處是單例,所以不怕重復(fù)獲取)
    helper = UserDBHelper.getInstance(this, 1);
    //恢復(fù)頁面時(shí)則獲取連接
    helper.openWriteLink();
}

@Override
protected void onPause() {
    super.onPause();
    //暫停頁面時(shí)就斷開連接
    helper.closeLink();
}
  1. 登錄成功后,如果用戶勾選了記住密碼功能,則保存到數(shù)據(jù)庫。也就是在loginSuccess方法中添加如下:
if (isRemember) {
    UserInfo info = new UserInfo(); // 創(chuàng)建一個(gè)用戶信息對(duì)象
    info.phone = et_phone.getText().toString();
    info.password = et_password.getText().toString();
    info.update_time = DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss");
    mHelper.insert(info); // 往用戶數(shù)據(jù)庫添加登錄成功的用戶信息
}
  1. 用戶進(jìn)行登錄時(shí),根據(jù)輸入手機(jī)號(hào)自動(dòng)查找密碼:
// 根據(jù)手機(jī)號(hào)碼查詢指定記錄
public UserInfo queryByPhone(String phone) {
    UserInfo info = null;
    List<UserInfo> infoList = query(String.format("phone='%s'", phone));
    if (infoList.size() > 0) { // 存在該號(hào)碼的登錄信息
    info = infoList.get(0);
    }
    return info;
}

3. 存儲(chǔ)卡

(1)私有空間和公有空間

為了更規(guī)范地管理手機(jī)存儲(chǔ)空間,Android從7.0開始將存儲(chǔ)卡劃分為私有存儲(chǔ)和公共存儲(chǔ)兩大部分,也就是分區(qū)存儲(chǔ)方式,系統(tǒng)給每個(gè)App都分配了默認(rèn)的私有存儲(chǔ)空間。App在私有空間上讀寫文件無須任何授權(quán),但是若想在公共空間讀寫文件,則要在AndroidManifest.xml里面添加下述的權(quán)限配置。

<!-- 存儲(chǔ)卡讀寫 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAG"/>

但是即使App聲明了完整的存儲(chǔ)卡操作權(quán)限,系統(tǒng)仍然默認(rèn)禁止該App訪問公共空間。打開手機(jī)的系統(tǒng)設(shè)置界面,進(jìn)入到具體應(yīng)用的管理頁面,會(huì)發(fā)現(xiàn)該應(yīng)用的存儲(chǔ)訪問權(quán)限被禁止了。

既然存儲(chǔ)卡分為公共空間和私有空間兩部分,它們的空間路徑獲取也就有所不同。若想獲取公共空間的存儲(chǔ)路徑,調(diào)用的是Environment.getExternalStoragePublicDirectory方法;若想獲取應(yīng)用私有空間的存儲(chǔ)路徑,調(diào)用的是getExternalFilesDir方法。

 //獲取系統(tǒng)的公共存儲(chǔ)路徑
String publicPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString();

//獲取系統(tǒng)的私有存儲(chǔ)路徑
String privatePath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString();

boolean isLegacy = true;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
    //Android10的存儲(chǔ)空間默認(rèn)采用分區(qū)方式,這里是判斷是使用傳統(tǒng)方式還是分區(qū)方式
    isLegacy = Environment.isExternalStorageLegacy();
}

(2)在存儲(chǔ)卡上讀寫文件

文本文件的讀寫借助IO流 FileOutputStream(寫文件)和 FileInputStream(讀文件)

// 把字符串保存到指定路徑的文本文件
public static void saveText(String path, String txt) {
    // 根據(jù)指定的文件路徑構(gòu)建文件輸出流對(duì)象
    try (FileOutputStream fos = new FileOutputStream(path)) {
   		fos.write(txt.getBytes()); // 把字符串寫入文件輸出流
    } catch (Exception e) {
    	e.printStackTrace();
    }
}
// 從指定路徑的文本文件中讀取內(nèi)容字符串
public static String openText(String path) {
    String readStr = "";
    // 根據(jù)指定的文件路徑構(gòu)建文件輸入流對(duì)象
    try (FileInputStream fis = new FileInputStream(path)) {
        byte[] b = new byte[fis.available()];
        fis.read(b); // 從文件輸入流讀取字節(jié)數(shù)組
        readStr = new String(b); // 把字節(jié)數(shù)組轉(zhuǎn)換為字符串
    } catch (Exception e) {
    	e.printStackTrace();
    }
    return readStr; // 返回文本文件中的文本字符串
}

(3)在存儲(chǔ)卡上讀寫 圖片文件

文本文件可以轉(zhuǎn)化為對(duì)字符串的讀寫,而圖像的讀寫就需要借助專門的位圖工具Bitmap處理。不同圖像來源獲取Bitmap的方式不同,有三種:

  1. 從指定資源文件中獲?。篸ecodeResource,例如從資源文件img.png獲取位圖對(duì)象:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img);
  1. 從指定路徑下獲取:decodeFile,但是要注意從Android10開始,該方法只能獲取私有空間下的圖片,公共空間下獲取不了。
Bitmap bitmap = BitmapFactory.decodeFile("C:\\Users\\OYMN\\Pictures\\onepunch.jpg");
  1. 從指定的輸入流中獲取,比如使用IO流打開圖片文件,然后作為參數(shù)傳入decodeStream:
public static Bitmap openImage(String path) {
    Bitmap bitmap = null; // 聲明一個(gè)位圖對(duì)象
    // 根據(jù)指定的文件路徑構(gòu)建文件輸入流對(duì)象
    try (FileInputStream fis = new FileInputStream(path)) {
    	bitmap = BitmapFactory.decodeStream(fis); // 從文件輸入流中解碼位圖數(shù)據(jù)
    } catch (Exception e) {
    	e.printStackTrace();
    }
    return bitmap; // 返回圖片文件中的位圖數(shù)據(jù)
}

獲取到圖片之后就可以通過ImageView的setImageBitmap進(jìn)行設(shè)置了。

有多種讀取圖片的方式,但是寫圖片只有一種方式。通過Bitmap的compress方法將位圖數(shù)據(jù)壓縮到文件輸出流:

public static void saveImage(String path, Bitmap bitmap){
    //根據(jù)文件路徑構(gòu)建文件輸出流
    try(FileOutputStream fos = new FileOutputStream()){
        //將位圖數(shù)據(jù)壓縮到文件輸出流
        bitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
    }catch(Exception e){
        e.printStackTrace();
    }
}

以下演示一下完整的文件讀寫操作:

// 獲取當(dāng)前App的私有下載目錄
String path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() +
"/";
// 從指定的資源文件中獲取位圖對(duì)象
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.huawei);
String file_path = path + DateUtil.getNowDateTime("") + ".jpeg";
FileUtil.saveImage(file_path, bitmap); // 把位圖對(duì)象保存為圖片文件
tv_path.setText("圖片文件的保存路徑為:\n" + file_path);
// 獲取當(dāng)前App的私有下載目錄
mPath = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/";
// 獲得指定目錄下面的所有圖片文件
mFilelist = FileUtil.getFileList(mPath, new String[]{".jpeg"});
if (mFilelist.size() > 0) {
// 打開并顯示選中的圖片文件內(nèi)容
String file_path = mFilelist.get(0).getAbsolutePath();
tv_content.setText("找到最新的圖片文件,路徑為"+file_path);
// 顯示存儲(chǔ)卡圖片文件的第一種方式:直接調(diào)用setImageURI方法
//iv_content.setImageURI(Uri.parse(file_path)); // 設(shè)置圖像視圖的路徑對(duì)象
// 第二種方式:先調(diào)用BitmapFactory.decodeFile獲得位圖,再調(diào)用setImageBitmap方法
//Bitmap bitmap = BitmapFactory.decodeFile(file_path);
//iv_content.setImageBitmap(bitmap); // 設(shè)置圖像視圖的位圖對(duì)象
// 第三種方式:先調(diào)用FileUtil.openImage獲得位圖,再調(diào)用setImageBitmap方法
Bitmap bitmap = FileUtil.openImage(file_path);
iv_content.setImageBitmap(bitmap); // 設(shè)置圖像視圖的位圖對(duì)象

4. 應(yīng)用組件Application

Application是Android的一大組件,在App運(yùn)行期間只有一個(gè)Application對(duì)象貫穿整個(gè)應(yīng)用的生命周期。因此,Application適合保存全局變量,主要是以下三類數(shù)據(jù):

  • 會(huì)頻繁讀取的信息:如用戶名,手機(jī)號(hào)碼等

  • 不方便通過intent傳遞的數(shù)據(jù),如位圖對(duì)象,非字符串的集合對(duì)象等。

  • 容易因頻繁分配內(nèi)存而導(dǎo)致內(nèi)存泄漏的對(duì)象,如Handler處理器實(shí)例等。

    生皮鞣制工藝流程,android,android,gradle,android studio

通過Application實(shí)現(xiàn)對(duì)全局內(nèi)存的讀寫:

  1. 先繼承Application,并獲取唯一實(shí)例:
public class MyApplication extends Application {

    private static MyApplication myApplication;   //Application唯一實(shí)例

    public Map<String, String> map = new HashMap<>();   //當(dāng)作全局變量,用來存儲(chǔ)數(shù)據(jù)

    public static MyApplication getInstance(){
        return myApplication;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        // 在打開應(yīng)用時(shí)對(duì)靜態(tài)的應(yīng)用實(shí)例賦值
        myApplication = this;
    }
}

  1. 在AndroidManifest.xml 通過name屬性添加該Application

生皮鞣制工藝流程,android,android,gradle,android studio

  1. 接下來就可以通過該Application在整個(gè)App中存取數(shù)據(jù)了:

如在MainActivity6存儲(chǔ)數(shù)據(jù):

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main6);

    //存儲(chǔ)數(shù)據(jù)
    MyApplication myApplication = MyApplication.getInstance();
    myApplication.map.put("myKey", "myValue");

    //跳轉(zhuǎn)到MainActivity5
    View bt5 = findViewById(R.id.bt5);
    bt5.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(MainActivity6.this, MainActivity5.class);
            startActivity(intent);
        }
    });

}

在MainActivity5中獲取數(shù)據(jù):

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main5);

    TextView tv = findViewById(R.id.tv);
    tv.setText(MyApplication.getInstance().map.get("myKey"));  //成功獲取到數(shù)據(jù)
}

5. 實(shí)戰(zhàn):購(gòu)物車

五、內(nèi)容共享

1. 在應(yīng)用之間共享數(shù)據(jù)

接下來將介紹Android的四大組件之一ContentProvider,通過ContentProvider封裝內(nèi)部數(shù)據(jù)的外部訪問接口,實(shí)現(xiàn)不同應(yīng)用能夠互相傳輸數(shù)據(jù)。

和ContentProvider搭配使用的還有:ContentResolver(內(nèi)容解析器),ContentObserver(內(nèi)容觀察器)。

上面提到的SQLite可以操作自身的數(shù)據(jù)庫,而ContentProvider則是作為中間接口,通過SQLiteOpenHelper和SQLiteDatabase間接操控?cái)?shù)據(jù)庫,實(shí)現(xiàn)為其他應(yīng)用提供數(shù)據(jù)的功能。

生皮鞣制工藝流程,android,android,gradle,android studio

使用舉例如下:

  1. 創(chuàng)建一個(gè)UserInfoProvider,用來提供用戶信息給外界應(yīng)用

    在彈出的右鍵菜單中依次選擇New→Other→Content Provider

    此時(shí)會(huì)自動(dòng)修改兩處地方:

    (1)一是在AndroidManifest.xml中添加該P(yáng)rovider的配置信息:

    生皮鞣制工藝流程,android,android,gradle,android studio

    (2)二是創(chuàng)建的這個(gè)Provider會(huì)繼承ContentProvider,并重寫了一些方法。

Server端代碼:

public class UserInfoProvider extends ContentProvider {

    //這里是上面實(shí)現(xiàn)的dbHelper,用來操作本地?cái)?shù)據(jù)庫
    private UserDBHelper userDBHelper;

    //初始化
    @Override
    public boolean onCreate() {
        //初始化 dbHelper
        userDBHelper = UserDBHelper.getInstance(getContext());

        return true;
    }

    //插入
    //uri格式:content://com.example.secondandroidapp.UserInfoProvider/user
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        //使用sqlite插入數(shù)據(jù)
        SQLiteDatabase db = userDBHelper.getWritableDatabase();
        db.insert(UserDBHelper.TABLE_NAME, null, values);

        return uri;
    }

    //查詢
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {

        SQLiteDatabase db = userDBHelper.getReadableDatabase();
        return db.query(UserDBHelper.TABLE_NAME, projection, selection, selectionArgs, null, null, null);
    }
    
    //刪除
	@Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int count = 0;
        switch (uriMatcher.match(uri)) {
            //這種是uri不帶參數(shù):"content://com.example.secondandroidapp.UserInfoProvider/user"
            case USER:
                // 獲取SQLite數(shù)據(jù)庫的寫連接
                SQLiteDatabase db = userDBHelper.getWritableDatabase();
                // 執(zhí)行SQLite的刪除操作,并返回刪除記錄的數(shù)目
                count = db.delete(UserDBHelper.TABLE_NAME, selection,
                        selectionArgs);
                db.close();
                break;
            //這種是uri帶參數(shù):"content://com.example.secondandroidapp.UserInfoProvider/user/2"
            case USERS:
                String id = uri.getLastPathSegment();
                SQLiteDatabase db2 = userDBHelper.getWritableDatabase();
                count = db2.delete(UserDBHelper.TABLE_NAME, "id = ?", new String[]{id});
                db2.close();
                break;
        }
        return count;
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}
  1. 利用ContentProvider只實(shí)現(xiàn)服務(wù)端App的數(shù)據(jù)封裝,如果客戶端App想訪問對(duì)方的內(nèi)部數(shù)據(jù),就要通過內(nèi)容解析器ContentResolver訪問。

    生皮鞣制工藝流程,android,android,gradle,android studio

ContentProvider的Uri結(jié)構(gòu)如下:content://authority/data_path/id

Client的代碼如下:

public class MainActivity7 extends AppCompatActivity {

    private static Uri ContentUri = Uri.parse("content://com.example.secondandroidapp.UserInfoProvider/user");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main7);

        Button insertButton = findViewById(R.id.insertButton);
        insertButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ContentValues values = new ContentValues();
                values.put("name", "陳鴻榮");
                values.put("age", "20");
                //獲取到ContentResolver之后調(diào)用插入方法進(jìn)行插入
                getContentResolver().insert(ContentUri, values);
            }
        });

        Button deleteButton = findViewById(R.id.deleteButton);
        deleteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // content://com.example.secondandroidapp.UserInfoProvider/user/2
                Uri uri = ContentUris.withAppendedId(ContentUri, 2);
                int count = getContentResolver().delete(uri, null, null);
            }
        });
    }
}

出于安全考慮,Android11需要事先聲明需要訪問的其他應(yīng)用:

在AndroidManifest.xml中添加如下:

<queries>
    <!--服務(wù)端應(yīng)用包名 -->
    <package android:name="com.example.secondandroidapp"/>
    
    <!--或者直接指定authorities-->
    <!-- <provider android:authorities="com.example.secondandroidapp.UserInfoProvider"/>   -->
</queries>

2. 使用內(nèi)容組件獲取通訊信息

(1)運(yùn)行時(shí)動(dòng)態(tài)申請(qǐng)權(quán)限

在上面講公共存儲(chǔ)空間與私有存儲(chǔ)空間提到,App若想訪問存儲(chǔ)卡的公共空間,就要在AndroidManifest.xml里面添加下述的權(quán)限配置。

<!-- 存儲(chǔ)卡讀寫 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAG" />

然而即使App聲明了完整的存儲(chǔ)卡操作權(quán)限,從Android 7.0開始,系統(tǒng)仍然默認(rèn)禁止該App訪問公共空間,必須到設(shè)置界面手動(dòng)開啟應(yīng)用的存儲(chǔ)卡權(quán)限才行。盡管此舉是為用戶隱私著想,可是人家咋知道要手工開權(quán)限呢?就算用戶知道,去設(shè)置界面找到權(quán)限開關(guān)也頗費(fèi)周折。為此Android支持在Java代碼中處理權(quán)限,處理過程分為3個(gè)步驟:

  1. 檢查App是否開啟了指定權(quán)限:

    調(diào)用ContextCompat的checkSelfPermission方法

  2. 請(qǐng)求系統(tǒng)彈窗,以便用戶選擇是否開啟權(quán)限:

    調(diào)用ActivityCompat的requestPermissions方法,即可命令系統(tǒng)自動(dòng)彈出權(quán)限申請(qǐng)窗口。

  3. 判斷用戶的權(quán)限選擇結(jié)果,是開啟還是拒絕:

    重寫活動(dòng)頁面的權(quán)限請(qǐng)求回調(diào)方法onRequestPermissionsResult,在該方法內(nèi)部處理用戶的權(quán)限選擇結(jié)果

動(dòng)態(tài)申請(qǐng)權(quán)限有兩種方式:餓漢式 和 懶漢式。

接下來通過獲取通訊權(quán)限和短信權(quán)限來進(jìn)行舉例說明:

首先是懶漢式:當(dāng)需要某種權(quán)限的時(shí)候再去申請(qǐng)

public class PermissionUtil {

    //檢查權(quán)限,返回true表示完全啟用權(quán)限,返回false則表示為完全啟用所有權(quán)限
    public static boolean checkPermission(Activity activity, String[] permissions, int requestCode){

        //Android6.0之后采取動(dòng)態(tài)權(quán)限管理
        if(Build.VERSION.SDK_INT > Build.VERSION_CODES.M){
            int check = PackageManager.PERMISSION_GRANTED;  // 0

            for (String permission : permissions) {
                check = ContextCompat.checkSelfPermission(activity, permission);
                if(check != PackageManager.PERMISSION_GRANTED){
                    break;
                }
            }
            //如果未開啟該權(quán)限,則請(qǐng)求系統(tǒng)彈窗,好讓用戶選擇是否開啟權(quán)限
            if(check != PackageManager.PERMISSION_GRANTED){
                //請(qǐng)求權(quán)限
                ActivityCompat.requestPermissions(activity, permissions, requestCode);
                return false;
            }

            return true;
        }

        return false;
    }

    //檢查權(quán)限數(shù)組,返回true表示都已經(jīng)授權(quán)
    public static boolean checkGrant(int[] grantResults) {

        if(grantResults != null){
            for (int grant : grantResults) {
                if(grant != PackageManager.PERMISSION_GRANTED){
                    return false;
                }
            }
            return true;
        }

        return false;
    }
}

通過兩個(gè)按鈕模擬分別獲取權(quán)限:

public class PermissionLazyActivity extends AppCompatActivity {

    //通訊錄的讀寫權(quán)限
    private static final String[] PERMISSION_CONTACT = {
            Manifest.permission.READ_CONTACTS,
            Manifest.permission.WRITE_CONTACTS
    };

    //短信的讀寫權(quán)限
    private static final String[] PERMISSION_SMS = {
            Manifest.permission.SEND_SMS,
            Manifest.permission.RECEIVE_SMS
    };

    private static final int REQUEST_CODE_CONTACTS = 1;
    private static final int REQUEST_CODE_SMS = 2;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_permission_lazy);

        //獲取通訊錄權(quán)限
        findViewById(R.id.btn_contact).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PermissionUtil.checkPermission(PermissionLazyActivity.this, PERMISSION_CONTACT, REQUEST_CODE_CONTACTS);
            }
        });

        //獲取短信權(quán)限
        findViewById(R.id.btn_sms).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PermissionUtil.checkPermission(PermissionLazyActivity.this, PERMISSION_SMS, REQUEST_CODE_SMS);
            }
        });
    }

    // 用戶選擇權(quán)限結(jié)果后會(huì)調(diào)用該回調(diào)方法
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode){
            case REQUEST_CODE_CONTACTS:
                if(PermissionUtil.checkGrant(grantResults)){
                    Log.d("hhh", "通訊錄獲取成功");
                }else{
                    Log.d("hhh", "通訊錄獲取失敗");
                    //跳轉(zhuǎn)到設(shè)置界面
                    jumpToSettings();
                }
                break;
            case REQUEST_CODE_SMS:
                if(PermissionUtil.checkGrant(grantResults)){
                    Log.d("hhh", "短信權(quán)限獲取成功");
                }else{
                    Log.d("hhh", "短信權(quán)限獲取失敗");
                    //跳轉(zhuǎn)到設(shè)置界面
                    jumpToSettings();
                }
                break;
        }
    }

    //跳轉(zhuǎn)到設(shè)置界面
    private void jumpToSettings(){
        Intent intent = new Intent();
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.fromParts("package", getPackageName(), null));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}

另外還需要在AndroidManifest.xml中配置:(在低版本中只需要配置這些信息即可,高版本就需要上面的動(dòng)態(tài)申請(qǐng)權(quán)限)

<!--    開啟通訊錄權(quán)限-->
    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>

<!--    開啟短信收發(fā)權(quán)限-->
    <uses-permission android:name="android.permission.SEND_SMS"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS"/>

效果如下:

懶漢式:在頁面打開之后就一次性需要用戶獲取所有權(quán)限。

public class PermissionHungryActivity extends AppCompatActivity {

    //所需全部讀寫權(quán)限
    private static final String[] PERMISSIONS = {
            Manifest.permission.READ_CONTACTS,
            Manifest.permission.WRITE_CONTACTS,
            Manifest.permission.SEND_SMS,
            Manifest.permission.RECEIVE_SMS
    };

   	//
    private static final int REQUEST_CODE_ALL = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_permission_lazy);

        //檢查是否擁有所有所需權(quán)限
        PermissionUtil.checkPermission(this, PERMISSIONS, REQUEST_CODE_ALL);
    }

    // 用戶選擇權(quán)限結(jié)果后會(huì)調(diào)用該回調(diào)方法
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode){
            case REQUEST_CODE_ALL:
                if(PermissionUtil.checkGrant(grantResults)){
                    Log.d("hhh", "所有權(quán)限獲取成功");
                }else{
                    //部分權(quán)限獲取失敗
                    for (int i = 0; i < grantResults.length; i++) {
                        if(grantResults[i] != PackageManager.PERMISSION_GRANTED){
                            //判斷是什么權(quán)限獲取失敗
                            switch (permissions[i]){
                                case Manifest.permission.WRITE_CONTACTS:
                                case Manifest.permission.READ_CONTACTS:
                                    Log.d("hhh", "通訊錄獲取失敗");
                                    jumpToSettings();
                                    break;
                                case Manifest.permission.SEND_SMS:
                                case Manifest.permission.RECEIVE_SMS:
                                    Log.d("hhh", "短信權(quán)限獲取失敗");
                                    jumpToSettings();
                                    break;
                            }
                        }
                    }
                }
                break;
        }
    }

    //跳轉(zhuǎn)到設(shè)置界面
    private void jumpToSettings(){
        Intent intent = new Intent();
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        intent.setData(Uri.fromParts("package", getPackageName(), null));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}

(2)使用ContentResolver讀寫聯(lián)系人

手機(jī)中通訊錄的主要表結(jié)構(gòu)有:

raw_contacts表:

生皮鞣制工藝流程,android,android,gradle,android studio

data表:記錄了用戶的通訊錄所有數(shù)據(jù),包括手機(jī)號(hào),顯示名稱等,但是里面的mimetype_id表示不同的數(shù)據(jù)類型,這與表mimetypes表中的id相對(duì)應(yīng),raw_contact_id 與上面的 raw_contacts表中的 id 相對(duì)應(yīng)。

生皮鞣制工藝流程,android,android,gradle,android studio

mimetypes表:

生皮鞣制工藝流程,android,android,gradle,android studio

所以,插入步驟如下:

  • 首先往raw_contacts表中插入一條數(shù)據(jù)得到id
  • 接著由于一個(gè)聯(lián)系人有姓名,電話號(hào)碼,郵箱,因此需要分三次插入data表中,將raw_contact_id和上面得到的id進(jìn)行關(guān)聯(lián)

下面是往通訊錄插入和查詢聯(lián)系人的代碼:

public class ContactActivity extends AppCompatActivity implements View.OnClickListener {

    private EditText et_contact_name;
    private EditText et_contact_phone;
    private EditText et_contact_email;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contact);
        et_contact_name = findViewById(R.id.et_contact_name);
        et_contact_phone = findViewById(R.id.et_contact_phone);
        et_contact_email = findViewById(R.id.et_contact_email);
        findViewById(R.id.btn_add_contact).setOnClickListener(this);
        findViewById(R.id.btn_read_contact).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_add_contact:
                // 創(chuàng)建一個(gè)聯(lián)系人對(duì)象
                Contact contact = new Contact();
                contact.name = et_contact_name.getText().toString().trim();
                contact.phone = et_contact_phone.getText().toString().trim();
                contact.email = et_contact_email.getText().toString().trim();

                // 方式一,使用ContentResolver多次寫入,每次一個(gè)字段
//                 addContacts(getContentResolver(), contact);

                // 方式二,批處理方式
                // 每一次操作都是一個(gè) ContentProviderOperation,構(gòu)建一個(gè)操作集合,然后一次性執(zhí)行
                // 好處是,要么全部成功,要么全部失敗,保證了事務(wù)的一致性
                addFullContacts(getContentResolver(), contact);

                Toast.makeText(this, "添加聯(lián)系人成功!", Toast.LENGTH_SHORT).show();
                break;

            case R.id.btn_read_contact:
                readPhoneContacts(getContentResolver());
                break;
        }
    }

    //往通訊錄添加一個(gè)聯(lián)系人信息(姓名,號(hào)碼,郵箱)
    private void addContacts(ContentResolver contentResolver, Contact contact) {
        //得到rawContentId
        ContentValues values = new ContentValues();
        //插入記錄得到id
        Uri uri = contentResolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
        long rawContentId = ContentUris.parseId(uri);

        //插入名字
        ContentValues name = new ContentValues();
        //關(guān)聯(lián)上面得到的聯(lián)系人id
        name.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);
        //關(guān)聯(lián)聯(lián)系人姓名的類型
        name.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
        //關(guān)聯(lián)聯(lián)系人姓名
        name.put(ContactsContract.Data.DATA2, contact.name);
        contentResolver.insert(ContactsContract.Data.CONTENT_URI, name);

        //插入電話號(hào)碼
        ContentValues phone = new ContentValues();
        //關(guān)聯(lián)上面得到的聯(lián)系人id
        phone.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);
        //關(guān)聯(lián)聯(lián)系人電話號(hào)碼的類型
        phone.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
        //關(guān)聯(lián)聯(lián)系人電話號(hào)碼
        phone.put(ContactsContract.Data.DATA1, contact.phone);
        //指定該號(hào)碼是家庭號(hào)碼還是工作號(hào)碼 (家庭)
        phone.put(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
        contentResolver.insert(ContactsContract.Data.CONTENT_URI, phone);

        //插入郵箱
        ContentValues email = new ContentValues();
        //關(guān)聯(lián)上面得到的聯(lián)系人id
        email.put(ContactsContract.Contacts.Data.RAW_CONTACT_ID, rawContentId);
        //關(guān)聯(lián)聯(lián)系人郵箱的類型
        email.put(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE);
        //關(guān)聯(lián)聯(lián)系人郵箱
        email.put(ContactsContract.Data.DATA1, contact.email);
        //指定該號(hào)碼是家庭郵箱還是工作郵箱
        email.put(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_WORK);
        contentResolver.insert(ContactsContract.Data.CONTENT_URI, email);
    }

    //事務(wù)操作,四個(gè)插入操作一次性提交
    private void addFullContacts(ContentResolver contentResolver, Contact contact) {
        //創(chuàng)建一個(gè)插入聯(lián)系人主記錄的內(nèi)容操作器
        ContentProviderOperation op_main = ContentProviderOperation
                .newInsert(ContactsContract.RawContacts.CONTENT_URI)
                //沒有實(shí)際意義,不加這個(gè)會(huì)報(bào)錯(cuò)(不加這個(gè)導(dǎo)致沒有創(chuàng)建ContentValue,導(dǎo)致報(bào)錯(cuò))
                .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
                .build();
        //創(chuàng)建一個(gè)插入聯(lián)系人姓名記錄的內(nèi)容操作器
        ContentProviderOperation op_name = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                //將第0個(gè)操作的id,即raw_contacts中的id作為data表中的raw_contact_id
                .withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.Data.DATA2, contact.name)
                .build();
        //創(chuàng)建一個(gè)插入聯(lián)系人電話號(hào)碼記錄的內(nèi)容操作器
        ContentProviderOperation op_phone = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                //將第0個(gè)操作的id,即raw_contacts中的id作為data表中的raw_contact_id
                .withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.Data.DATA1, contact.phone)
                .withValue(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE)
                .build();

        //創(chuàng)建一個(gè)插入聯(lián)系人郵箱記錄的內(nèi)容操作器
        ContentProviderOperation op_email = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
                //將第0個(gè)操作的id,即raw_contacts中的id作為data表中的raw_contact_id
                .withValueBackReference(ContactsContract.Contacts.Data.RAW_CONTACT_ID, 0)
                .withValue(ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.Data.DATA1, contact.email)
                .withValue(ContactsContract.Data.DATA2, ContactsContract.CommonDataKinds.Phone.TYPE_WORK)
                .build();

        //全部放在集合中一次性提交
        ArrayList<ContentProviderOperation> operations = new ArrayList<>();
        operations.add(op_main);
        operations.add(op_name);
        operations.add(op_phone);
        operations.add(op_email);

        try {
            //批量提交四個(gè)操作
            contentResolver.applyBatch(ContactsContract.AUTHORITY, operations);
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    //讀取聯(lián)系人
    @SuppressLint("Range")
    private void readPhoneContacts(ContentResolver contentResolver) {
        //先查詢r(jià)aw_contacts表,再根據(jù)raw_contacts_id表 查詢data表
        Cursor cursor = contentResolver.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{ContactsContract.RawContacts._ID}, null, null, null);
        while(cursor.moveToNext()){
            int rawContactId = cursor.getInt(0);
            Uri uri = Uri.parse("content://com.android.contacts/contacts/" + rawContactId + "/data");
            Cursor dataCursor = contentResolver.query(uri, new String[]{ContactsContract.Contacts.Data.MIMETYPE, ContactsContract.Contacts.Data.DATA1, ContactsContract.Contacts.Data.DATA2}, null, null, null);
            Contact contact = new Contact();
            while (dataCursor.moveToNext()) {
                String data1 = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.Contacts.Data.DATA1));
                String mimeType = dataCursor.getString(dataCursor.getColumnIndex(ContactsContract.Contacts.Data.MIMETYPE));
                switch (mimeType) {
                    //是姓名
                    case ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE:
                        contact.name = data1;
                        break;

                    //郵箱
                    case ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE:
                        contact.email = data1;
                        break;

                    //手機(jī)
                    case ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE:
                        contact.phone = data1;
                        break;
                }
            }

            dataCursor.close();

            // RawContacts 表中出現(xiàn)的 _id,不一定在 Data 表中都會(huì)有對(duì)應(yīng)記錄
            if (contact.name != null) {
                Log.d("hhh", contact.toString());
            }
        }
        cursor.close();
    }

}

頁面如下:

(3)使用ContentObserver監(jiān)聽短信

ContentResolver獲取數(shù)據(jù)采用的是主動(dòng)查詢方式,有查詢就有數(shù)據(jù),沒查詢就沒數(shù)據(jù)。ContentResolver能夠?qū)崟r(shí)獲取新增的數(shù)據(jù),最常見的業(yè)務(wù)場(chǎng)景是短信驗(yàn)證碼。為了替用戶省事,App通常會(huì)監(jiān)控手機(jī)剛收到的短信驗(yàn)證碼,并自動(dòng)填寫驗(yàn)證碼輸入框。這時(shí)就用到了內(nèi)容觀察器ContentObserver,事先給目標(biāo)內(nèi)容注冊(cè)一個(gè)觀察器,目標(biāo)內(nèi)容的數(shù)據(jù)一旦發(fā)生變化,就馬上觸發(fā)觀察器的監(jiān)聽事件,從而執(zhí)行開發(fā)者預(yù)先定義的代碼。

生皮鞣制工藝流程,android,android,gradle,android studio

示例代碼如下:(記得在Manifest.xml中開啟權(quán)限和動(dòng)態(tài)開啟權(quán)限)文章來源地址http://www.zghlxwxcb.cn/news/detail-781755.html

public class MonitorSmsActivity extends AppCompatActivity {

    private SmsGetObserver mObserver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_monitor_sms);
        
        // 給指定Uri注冊(cè)內(nèi)容觀察器,一旦發(fā)生數(shù)據(jù)變化,就觸發(fā)觀察器的onChange方法
        Uri uri = Uri.parse("content://sms");
        
        // notifyForDescendents:
        // false :表示精確匹配,即只匹配該Uri,true :表示可以同時(shí)匹配其派生的Uri
        mObserver = new SmsGetObserver(this);
        getContentResolver().registerContentObserver(uri, true, mObserver);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //取消注冊(cè)
        getContentResolver().unregisterContentObserver(mObserver);
    }

    private static class SmsGetObserver extends ContentObserver {

        private final Context mContext;

        public SmsGetObserver(Context context) {
            super(new Handler(Looper.getMainLooper()));
            this.mContext = context;
        }

        //回調(diào)
        @SuppressLint("Range")
        @Override
        public void onChange(boolean selfChange, @Nullable Uri uri) {
            super.onChange(selfChange, uri);
            // onChange會(huì)多次調(diào)用,收到一條短信會(huì)調(diào)用兩次onChange
            // mUri===content://sms/raw/20
            // mUri===content://sms/inbox/20
            // 安卓7.0以上系統(tǒng),點(diǎn)擊標(biāo)記為已讀,也會(huì)調(diào)用一次
            // mUri===content://sms
            // 收到一條短信都是uri后面都會(huì)有確定的一個(gè)數(shù)字,對(duì)應(yīng)數(shù)據(jù)庫的_id,比如上面的20
            if (uri == null) {
                return;
            }
            if (uri.toString().contains("content://sms/raw") ||
                    uri.toString().equals("content://sms")) {
                return;
            }

            // 通過內(nèi)容解析器獲取符合條件的結(jié)果集游標(biāo)
            Cursor cursor = mContext.getContentResolver().query(uri, new String[]{"address", "body", "date"}, null, null, "date DESC");
            if (cursor.moveToNext()) {
                // 短信的發(fā)送號(hào)碼
                String sender = cursor.getString(cursor.getColumnIndex("address"));
                // 短信內(nèi)容
                String content = cursor.getString(cursor.getColumnIndex("body"));
                Log.d("ning", String.format("sender:%s,content:%s", sender, content));
            }
            cursor.close();
        }
    }
}

3. 在應(yīng)用之間共享文件

(1)使用相冊(cè)圖片發(fā)送彩信

(2)借助FileProvider發(fā)送彩信

(3)借助FileProvider安裝應(yīng)用

到了這里,關(guān)于Android基礎(chǔ)教程——從入門到精通(上)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 黑客教程,從零基礎(chǔ)入門到精通

    黑客教程,從零基礎(chǔ)入門到精通

    學(xué)前感言: 1.這是一條堅(jiān)持的道路,三分鐘的熱情可以放棄往下看了. 2.多練多想,不要離開了教程什么都不會(huì)了.最好看完教程自己獨(dú)立完成技術(shù)方面的開發(fā). 3.有時(shí)多google,baidu,我們往往都遇不到好心的大神,誰會(huì)無聊天天給你做解答. 4.遇到實(shí)在搞不懂的,可以先放放,以后再來解決

    2024年02月01日
    瀏覽(21)
  • 網(wǎng)絡(luò)安全入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通

    網(wǎng)絡(luò)安全入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通

    1.入行網(wǎng)絡(luò)安全這是一條堅(jiān)持的道路,三分鐘的熱情可以放棄往下看了。 2.多練多想,不要離開了教程什么都不會(huì)了,最好看完教程自己獨(dú)立完成技術(shù)方面的開發(fā)。 3.有時(shí)多百度,我們往往都遇不到好心的大神,誰會(huì)無聊天天給你做解答。 4.遇到實(shí)在搞不懂的,可以先放放,

    2024年01月18日
    瀏覽(23)
  • 網(wǎng)絡(luò)安全入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通!

    網(wǎng)絡(luò)安全入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通!

    網(wǎng)絡(luò)安全是一個(gè)龐大而不斷發(fā)展的領(lǐng)域,它包含多個(gè)專業(yè)領(lǐng)域,如網(wǎng)絡(luò)防御、網(wǎng)絡(luò)攻擊、數(shù)據(jù)加密等。介紹網(wǎng)絡(luò)安全的基本概念、技術(shù)和工具,逐步深入,幫助您成為一名合格的網(wǎng)絡(luò)安全從業(yè)人員。 1.計(jì)算機(jī)基礎(chǔ)知識(shí) 了解了計(jì)算機(jī)的硬件、軟件、操作系統(tǒng)和網(wǎng)絡(luò)結(jié)構(gòu)等基礎(chǔ)知

    2024年04月13日
    瀏覽(26)
  • AI教程視頻《AI illustrator入門到精通》零基礎(chǔ)自學(xué)教程教學(xué)

    AI教程視頻《AI illustrator入門到精通》零基礎(chǔ)自學(xué)教程教學(xué)

    歡迎您關(guān)注沉睡者IT,點(diǎn)擊上面關(guān)注我,↑↑↑ 聽說關(guān)注我的小伙伴們都發(fā)了財(cái),趕緊關(guān)注吧 AI教程視頻講座簡(jiǎn)介: AI教程視頻《AI illustrator入門到精通》零基礎(chǔ)自學(xué)教程教學(xué)內(nèi)容介紹: 一套最適合小白的AI教程,AI課程是從零基礎(chǔ)開始講解的,前面章節(jié)學(xué)習(xí)基本功能工具,后

    2024年02月01日
    瀏覽(22)
  • 黑客入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通,看完這一篇就夠了

    黑客入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通,看完這一篇就夠了

    想要成為黑客,卻苦于沒有方向,不知道從何學(xué)起,下面這篇 黑客入門 教程可以幫你實(shí)現(xiàn)自己的黑客夢(mèng)想,如果想學(xué),可以繼續(xù)看下去,文章有點(diǎn)長(zhǎng),希望你可以耐心看到最后 1、?Web安全相關(guān)概念(2周) ?·熟悉基本概念(SQL注入、上傳、XSS、?、CSRF、一句話木馬等)。?通過

    2024年02月03日
    瀏覽(15)
  • Python入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通,看完這一篇就夠了

    Python入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通,看完這一篇就夠了

    本文羅列了了python零基礎(chǔ)入門到精通的詳細(xì)教程,內(nèi)容均以知識(shí)目錄的形式展開。 Typora軟件下載 Typora基本使用 Typora補(bǔ)充說明 編程與編程語言 計(jì)算機(jī)的本質(zhì) 計(jì)算機(jī)五大組成部分 計(jì)算機(jī)三大核心硬件 操作系統(tǒng) 文件的概念 計(jì)算機(jī)內(nèi)部數(shù)據(jù)原理 編程語言發(fā)展史 編程語言的分類

    2023年04月19日
    瀏覽(46)
  • Flask入門教程(非常詳細(xì)),從零基礎(chǔ)入門到精通,看完這一篇就夠了

    Flask入門教程(非常詳細(xì)),從零基礎(chǔ)入門到精通,看完這一篇就夠了

    目錄 Flask入門 運(yùn)行方式 URL與函數(shù)的映射(動(dòng)態(tài)路由) PostMan的使用 查詢參數(shù)的獲取 上傳文件 其它參數(shù) url_for 函數(shù) 響應(yīng)-重定向 響應(yīng)-響應(yīng)內(nèi)容 響應(yīng)-自定義響應(yīng) Flask模板 模板介紹 模板的使用 模板-傳參 模板使用url_for函數(shù) 過濾器介紹 Jinja模板自帶過濾器 流程控制-選擇結(jié)構(gòu) 流程

    2024年02月05日
    瀏覽(74)
  • Spark入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通,看完這一篇就夠了

    Spark入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通,看完這一篇就夠了

    文章目錄 引言 1. Spark 基礎(chǔ) 1.1 Spark 為何物 1.2 Spark VS Hadoop 1.3 Spark 優(yōu)勢(shì)及特點(diǎn) 1.3.1 優(yōu)秀的數(shù)據(jù)模型和豐富計(jì)算抽象 1.3.2 完善的生態(tài)圈-fullstack 1.3.3 spark的特點(diǎn) 1.4 Spark 運(yùn)行模式 2. Spark Core 2.1 RDD詳解 2.1.1 RDD概念 2.1.2 RDD屬性 2.1.3 RDD API 2.1.3.1 RDD 的創(chuàng)建方式 2.1.3.2 RDD 算子 2.1.4 RDD

    2024年02月04日
    瀏覽(55)
  • Golang入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通,看完這一篇就夠了

    Golang入門教程(非常詳細(xì))從零基礎(chǔ)入門到精通,看完這一篇就夠了

    文章目錄 一、golang 簡(jiǎn)介 1. go 語言特點(diǎn) 2. go 語言應(yīng)用領(lǐng)域 3. 使用 go 語言的公司有哪些 二、安裝 golang 1. golang 下載安裝 2. 配置環(huán)境變量 三、golang 開發(fā)工具 1. 安裝 VSCode 2. 下載所需插件 四、第一個(gè) golang 應(yīng)用 1. main 包的含義 2. 示例 Go 是一個(gè)開源的編程語言,它能讓構(gòu)造簡(jiǎn)單

    2024年02月04日
    瀏覽(52)
  • 網(wǎng)絡(luò)安全入門教程(非常詳細(xì)),從零基礎(chǔ)入門到精通,看完這一篇就夠了

    網(wǎng)絡(luò)安全入門教程(非常詳細(xì)),從零基礎(chǔ)入門到精通,看完這一篇就夠了

    “沒有網(wǎng)絡(luò)安全就沒有國(guó)家安全”。當(dāng)前,網(wǎng)絡(luò)安全已被提升到國(guó)家戰(zhàn)略的高度,成為影響國(guó)家安全、社會(huì)穩(wěn)定至關(guān)重要的因素之一。 網(wǎng)絡(luò)安全行業(yè)特點(diǎn) 1、就業(yè)薪資非常高,漲薪快 2021年獵聘網(wǎng)發(fā)布網(wǎng)絡(luò)安全行業(yè)就業(yè)薪資行業(yè)最高人均33.77萬! 2、人才缺口大,就業(yè)機(jī)會(huì)多

    2023年04月10日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包