本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:该安卓订餐源代码项目向移动应用开发者展示了如何利用Android Studio 4.0或更新版本构建一个完整的订餐应用。项目内容包括用户界面设计、Activity和Fragment管理、数据持久化、网络请求、异步处理、适配器和列表组件使用、权限管理、通知和推送服务、测试策略以及版本控制等关键知识点。通过深入分析和实践这些组件,开发者能够提升其Android开发技能,并理解如何创建一个功能完善的订餐应用。 安卓订餐源代码,基于android studio4.0 以上开发

1. Android Studio开发环境介绍

1.1 Android Studio基本概念

Android Studio是Google官方开发的集成开发环境,专为Android应用开发而设计。它是基于IntelliJ IDEA而开发的,继承了其强大的代码编辑、智能分析等功能,并针对移动开发加入了多项专用工具和优化。

1.2 Android Studio功能特色

Android Studio的功能特色主要体现在其高效的项目管理、强大的代码编辑器、以及直观的用户界面设计工具上。此外,还具备了强大的调试和性能分析工具,帮助开发者快速定位问题。

1.3 安装与配置流程

安装Android Studio需要遵循以下步骤: 1. 访问 Android Studio官方网站 下载安装包。 2. 运行安装程序并遵循提示完成安装。 3. 启动Android Studio后,选择"Standard"安装,它将安装最新的Android SDK及模拟器。 4. 完成初始设置,包括配置SDK和虚拟设备。

完成上述步骤后,您就可以开始配置您的开发环境,设置SDK管理器,下载不同的API级别,并创建您的第一个Android项目了。

2. 用户界面(UI)设计与实现

在移动应用开发中,用户界面(UI)设计是构建强大用户体验的关键环节。UI不仅负责应用的外观和风格,而且直接影响到用户与应用的交互方式。Android Studio提供了丰富的工具和组件来帮助开发者设计和实现直观、响应迅速的用户界面。本章将深入探讨如何使用这些工具和组件,以及如何通过一些高级技术和技巧来优化UI。

2.1 基础UI组件的使用

2.1.1 常用布局的创建与应用

布局是Android UI中用于组织子视图位置关系和大小的结构。一个良好的布局设计可以提高应用的可扩展性和维护性。Android Studio内置了几种常见的布局类型,包括线性布局(LinearLayout)、相对布局(RelativeLayout)、帧布局(FrameLayout)以及网格布局(GridLayout)。

线性布局(LinearLayout)

线性布局是按照水平或垂直方向排列其子元素的布局方式。它是最简单的布局之一,适用于简单的布局需求。

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click Me" />

</LinearLayout>

在上述代码中, LinearLayout 包含一个 TextView 和一个 Button ,它们按照垂直方向排列。 match_parent 属性使得布局充满父容器, wrap_content 属性则让内容自适应大小。

相对布局(RelativeLayout)

相对于 LinearLayout RelativeLayout 允许子视图相互参照位置进行布局,这使得布局的设计更加灵活。

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Top Left Corner"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Click Me"
        android:layout_below="@id/textView"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

在这里, TextView 位于布局的左上角,而 Button 位于 TextView 的下方并且水平居中。通过引用视图的ID来指定位置关系是 RelativeLayout 的一个特点。

2.1.2 文本视图、按钮和图片视图等控件的运用

在布局中,控件是与用户交互的元素,例如文本视图(TextView)、按钮(Button)和图片视图(ImageView)。它们是构建UI不可或缺的部分,提供文本、按钮和图像等内容的展示。

文本视图(TextView)

TextView 用于显示文本,可以进行基本的文本样式设置,如字体大小、颜色等。

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    android:textSize="18sp"
    android:textColor="#FF0000" />

上述代码创建了一个简单的 TextView ,文本内容为"Hello World!",字体大小为18sp,颜色为红色。

按钮(Button)

Button 提供了一个可点击的界面元素,用户可以点击它来触发事件。

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Click Me"
    android:id="@+id/clickButton"
    android:onClick="onButtonClick" />

在这个例子中, Button 的文本内容是"Click Me",并且它有一个ID @+id/clickButton ,这样可以在Java代码中通过ID来引用该按钮。 android:onClick 属性是一个事件处理器,当按钮被点击时,将调用名为 onButtonClick 的方法。

图片视图(ImageView)

ImageView 用于在应用中显示图片。

<ImageView
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:src="@drawable/my_image"
    android:scaleType="centerInside" />

在此代码段中, ImageView 被设置为显示宽度和高度为100dp的图片,并且图片来自于资源文件夹中的 my_image scaleType 属性定义了图片的缩放和位置方式, centerInside 表示图片按比例缩放,保持图片完整显示在视图的内部。

2.2 高级UI组件的定制

2.2.1 Material Design风格的实现方法

Material Design是由Google提出的一套设计语言,旨在为用户提供统一、美观、直观且互动性强的用户体验。在Android Studio中,实现Material Design风格可以通过多种方式进行。

主题的使用

应用Material Design风格,首先需要在应用的主题中启用相关属性。可以在 styles.xml 文件中设置应用的主题为Material Design风格:

<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/purple_500</item>
    <item name="colorPrimaryDark">@color/purple_700</item>
    <item name="colorAccent">@color/purple_200</item>
</style>

这段代码继承了 Theme.MaterialComponents.Light.DarkActionBar ,并将主题中的颜色属性进行了自定义。

组件的使用

Material Design提供了丰富的组件,例如浮动按钮(FloatingActionButton)、卡片视图(CardView)等,使用这些组件可以轻松地为应用添加现代界面。

<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="16dp"
        app:srcCompat="@android:drawable/ic_dialog_email" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

在这里, FloatingActionButton 被添加到了 CoordinatorLayout 中,作为主界面的一个可交互元素。

2.2.2 动画和过渡效果的集成

动画和过渡效果可以增加应用的视觉吸引力,并引导用户关注应用中的重要元素和动作。

XML动画

Android Studio支持通过XML定义动画资源,然后在代码中调用它们。

<!-- res/anim/fade_in.xml -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:fromAlpha="0.0"
    android:toAlpha="1.0" />
<!-- res/anim/fade_out.xml -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:fromAlpha="1.0"
    android:toAlpha="0.0" />

在上述XML文件中,定义了两个简单的淡入和淡出动画。这些动画可以通过 ObjectAnimator AnimatorSet 在代码中被调用和控制。

过渡效果

从Android Lollipop版本开始,系统引入了Activity的过渡效果。使用 TransitionManager 可以方便地添加和管理这些效果。

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    final View myView = findViewById(R.id.my_view);
    myView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            View newFragmentView = getLayoutInflater().inflate(R.layout.new_fragment, null);
            Fragment newFragment = new NewFragment();
            newFragment.setArguments(getIntent().getExtras());
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.replace(R.id.content_frame, newFragment);
            transaction.addToBackStack(null);
            transaction.commit();

            TransitionManager.beginDelayedTransition(contentFrame, new AutoTransition());
            myView.setVisibility(View.GONE);
            newFragmentView.setVisibility(View.VISIBLE);
        }
    });
}

在这个例子中,当点击 myView 时,执行一个Fragment的替换动画。通过调用 TransitionManager.beginDelayedTransition 方法并传递正确的容器视图,可以在界面上看到平滑的过渡动画。

2.3 UI组件的响应式设计

2.3.1 响应式布局的设计原则

响应式设计是指让应用能够适应不同大小和分辨率的屏幕。通过使用响应式布局,开发者可以确保用户界面在不同设备上均有良好的显示效果和用户体验。

布局权重(layout_weight)

使用 LinearLayout 时,可以通过分配权重给子视图来灵活地定义子视图大小的比例。

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:weightSum="3">

    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Button 1"
        android:layout_weight="1" />

    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Button 2"
        android:layout_weight="2" />

</LinearLayout>

在这个布局中,两个按钮将平分父容器的宽度,但是第二个按钮会是第一个按钮宽度的两倍。

媒体查询(Media Queries)

除了在布局中使用权重,还可以通过媒体查询来实现响应式设计。媒体查询可以根据不同的屏幕尺寸和分辨率应用不同的样式规则。

<!-- res/values/styles.xml -->
<style name="ResponsiveTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="android:layout_width">wrap_content</item>
    <item name="android:layout_height">wrap_content</item>
</style>

<!-- res/values-large/styles.xml -->
<style name="ResponsiveTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="android:layout_width">match_parent</item>
    <item name="android:layout_height">match_parent</item>
</style>

在上述代码中, styles.xml 中的样式会被用于小屏幕设备,而 values-large/styles.xml 中的样式会被用于大屏幕设备。通过为不同尺寸的设备定义不同的样式,可以让UI更好地适应不同的屏幕。

2.3.2 媒体查询与多屏幕适配技巧

为了更精细地控制不同屏幕尺寸下的UI表现,可以通过创建不同的资源文件夹,如 layout-small layout-normal layout-large layout-xlarge ,并将对应的布局文件放入相应的文件夹中。

<!-- res/layout-small/activity_main.xml -->
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- Content for small screens -->
</LinearLayout>

<!-- res/layout-normal/activity_main.xml -->
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- Content for normal screens -->
</LinearLayout>

通过这种方式,Android系统会根据设备的屏幕尺寸自动选择合适的布局文件进行加载,从而实现更精确的多屏幕适配。

响应式布局设计是一个持续的过程,需要开发者在设计和开发阶段都不断测试和优化布局在不同设备上的表现。通过合理利用布局权重、媒体查询和多屏幕适配技巧,可以创建出既美观又实用的用户界面。

3. Activity和Fragment管理实践

3.1 Activity生命周期的管理

3.1.1 Activity状态转换及管理策略

在Android开发中,Activity是用户与应用交互的主要界面,它的生命周期状态转换在应用运行期间扮演着关键角色。Activity生命周期包含多种状态,包括创建、启动、恢复、暂停、停止和销毁。理解和管理这些状态的转换对于保证应用的稳定性和用户体验至关重要。

  • 创建(Created) : 当Activity第一次被创建时,系统调用 onCreate() 方法。在此阶段,开发者应进行必要的初始化,如加载布局、初始化成员变量等。
  • 启动(Started) : onStart() 方法在Activity变得对用户可见时被调用。它紧跟在 onCreate() 之后或当Activity从停止状态恢复时调用。
  • 恢复(Resumed) : onResume() 方法在Activity准备好和用户交互时被调用。此时Activity位于栈顶,拥有用户焦点。
  • 暂停(Paused) : onPause() 方法在Activity即将失去焦点,且另一个Activity将要启动前被调用。在这里应该暂停或保存那些不需要的操作。
  • 停止(Stopped) : onStop() 方法在Activity不再对用户可见时被调用,即它被另一个Activity覆盖了。
  • 销毁(Destroyed) : onDestroy() 方法在Activity被销毁前调用,这是清理资源或保存数据的最后机会。

状态管理策略

  • 管理资源 : 在 onPause() onStop() 中合理地释放和管理资源,如关闭网络连接、停止后台线程等。
  • 保存状态 : 在 onSaveInstanceState() 中保存必要的状态信息,以防止系统因配置更改(如屏幕旋转)导致Activity重建时数据丢失。
  • 生命周期感知 : 使用生命周期感知组件(如 ViewModel LiveData )来处理业务逻辑和UI更新,避免在生命周期方法中进行复杂的逻辑处理。

3.1.2 Intent和数据传递

Intent在Activity间传递数据和请求操作中扮演着重要角色。它是一个消息传递对象,用来在不同组件之间传递数据或启动新的Activity。

  • 启动Activity : 使用 startActivity() Intent 对象来启动另一个Activity。 java Intent intent = new Intent(CurrentActivity.this, TargetActivity.class); startActivity(intent);
  • 数据传递 : 通过 Intent putExtra() 方法传递数据,使用 getIntent().getExtras() 在目标Activity中获取数据。 java // 在CurrentActivity中传递数据 intent.putExtra("key", value); // 在TargetActivity中获取数据 Bundle extras = getIntent().getExtras(); if (extras != null) { String value = extras.getString("key"); }

Intent的高级用法

  • 返回数据 : 在目标Activity中使用 setResult() 方法返回结果。 java Intent resultIntent = new Intent(); resultIntent.putExtra("result", "data"); setResult(Activity.RESULT_OK, resultIntent); finish();
  • 隐式Intent : 通过指定动作、数据和类别来启动符合特定要求的Activity,而不需要明确指定启动哪个Activity。 xml <intent-filter> <action android:name="com.example.action.SOME_ACTION" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter>

理解并合理运用Activity的生命周期和Intent机制对于开发出高效且用户体验良好的Android应用至关重要。在处理不同的生命周期事件时,正确地保存和恢复数据、管理资源是保证应用性能和稳定性的关键。

3.2 Fragment的生命周期和交互

3.2.1 Fragment的创建与附加

Fragment是一种可以嵌入到Activity中的模块化组件,它可以有自己的布局和生命周期,是Android支持复杂界面设计的关键技术之一。

创建与附加过程

  • 创建Fragment类并在 onCreateView() 中加载布局。
  • 在Activity中使用 FragmentManager FragmentTransaction 来添加、移除Fragment。 java MyFragment myFragment = new MyFragment(); FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.add(R.id.fragment_container, myFragment); fragmentTransaction.commit();

Fragment同样具有自己的生命周期方法,如 onCreate() , onCreateView() , onActivityCreate() , onStart() , onResume() , onPause() , onStop() , onDestroyView() , onDestroy() , 和 onDetach() 。正确管理这些生命周期方法对于维护Fragment状态和避免内存泄漏是必不可少的。

3.2.2 Fragment与Activity间的通信

Fragment和Activity之间的通信,主要通过定义接口或使用 findFragmentById() findFragmentByTag() 方法来实现。

  • 定义接口 : 在Fragment中定义接口,并在Activity中实现该接口,通过回调方法传递数据或事件。 java // 在Fragment中 public interface OnFragmentInteractionListener { void onFragmentInteraction(Uri uri); } OnFragmentInteractionListener mListener; public void onAttach(Context context) { super.onAttach(context); try { mListener = (OnFragmentInteractionListener) context; } catch (ClassCastException e) { throw new ClassCastException(context.toString() + " must implement OnFragmentInteractionListener"); } }
  • 使用findFragmentById : 在Activity中通过FragmentManager查找Fragment实例。 java MyFragment fragment = (MyFragment) getSupportFragmentManager().findFragmentById(R.id.my_fragment); if (fragment != null) { fragment.sendDataToActivity(data); }

通信策略

  • 事件传递 : Fragment可以发送事件到Activity,Activity根据事件执行相应操作。
  • 数据请求 : Activity可以请求Fragment提供数据,Fragment响应请求返回数据。

Fragment的设计允许应用界面更加灵活和可重用。在实际开发中,合理使用Fragment可以大幅度提高代码的可维护性和应用的可扩展性。

3.3 Activity和Fragment的高级应用

3.3.1 动态添加和替换Fragment

在Android开发中,动态添加和替换Fragment是构建动态界面的重要手段。它允许开发者在运行时根据需要添加或替换Fragment,实现更加丰富的交互效果。

  • 动态添加Fragment : 可以通过定义一个容器(如FrameLayout),然后在运行时向其中添加Fragment。 java MyFragment newFragment = new MyFragment(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.add(R.id.fragment_container, newFragment); transaction.commit();

  • 替换Fragment : 替换Fragment时,先移除容器中的旧Fragment,然后添加新Fragment。 java MyNewFragment newFragment = new MyNewFragment(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // 将事务添加到返回栈 transaction.commit();

3.3.2 Activity栈管理

Activity栈是管理Activity实例和用户导航流的一种方式。通过管理Activity栈,开发者可以控制应用的导航流程,优化用户体验。

  • 启动新Activity : 启动新Activity时,它会被添加到栈中。按返回键时,最新添加的Activity会从栈中弹出。
  • 管理栈 : 使用 onBackPressed() 方法来管理返回栈的行为。 java @Override public void onBackPressed() { if (getFragmentManager().getBackStackEntryCount() > 0) { getFragmentManager().popBackStack(); } else { super.onBackPressed(); } }

  • 清空栈 : 当需要从一个Activity跳转到另一个完全不相关的Activity时,可能需要清空整个返回栈。 java Intent intent = new Intent(this, NewActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent);

合理使用Activity栈,可以在应用中创建流畅和直观的导航体验。动态添加和替换Fragment,以及对Activity栈进行有效管理,是构建高效能应用界面的关键。

通过本章的介绍,我们已经深入了解了Activity和Fragment的生命周期、数据传递和高级应用。接下来的章节将会探讨数据持久化技术,这是构建可靠Android应用的另一块基石。

4. 数据持久化技术(SQLite、SharedPreferences)

4.1 SQLite数据库的集成与操作

4.1.1 SQLite数据库的基本操作

SQLite是一个轻量级的关系数据库管理系统,被广泛用于Android开发中。它不需要一个单独的服务器进程或系统,因此不需要配置或管理,非常适合在移动设备上使用。要集成SQLite到Android应用中,首先需要了解如何创建数据库和表,以及进行基本的增删改查操作。

在Android中创建和管理SQLite数据库通常涉及到以下步骤:

  1. 创建一个继承自 SQLiteOpenHelper 的辅助类。
  2. 在辅助类中,定义 onCreate() onUpgrade() 方法来处理数据库的创建和版本更新。
  3. 使用 SQLiteDatabase 实例执行SQL语句来创建和操作数据库表。

以下是一个简单的例子,展示如何创建一个SQLite数据库和一个表,并执行基本操作:

public class DatabaseHelper extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "mydatabase.db";
    private static final int DATABASE_VERSION = 1;
    private static final String TABLE_NAME = "mytable";
    private static final String COLUMN_ID = "_id";
    private static final String COLUMN_NAME = "name";
    private static final String COLUMN_AGE = "age";

    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String createTableQuery = "CREATE TABLE " + TABLE_NAME + "(" +
            COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            COLUMN_NAME + " TEXT, " +
            COLUMN_AGE + " INTEGER);";
        db.execSQL(createTableQuery);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
        onCreate(db);
    }

    public void insertData(String name, int age) {
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(COLUMN_NAME, name);
        values.put(COLUMN_AGE, age);
        db.insert(TABLE_NAME, null, values);
        db.close();
    }

    public void updateData(int id, String newName) {
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(COLUMN_NAME, newName);
        db.update(TABLE_NAME, values, COLUMN_ID + " = ?", new String[] { String.valueOf(id) });
        db.close();
    }

    public ArrayList<String> getAllData() {
        ArrayList<String> list = new ArrayList<>();
        SQLiteDatabase db = this.getReadableDatabase();
        String selectQuery = "SELECT * FROM " + TABLE_NAME;
        Cursor cursor = db.rawQuery(selectQuery, null);
        if (cursor.moveToFirst()) {
            do {
                list.add(cursor.getString(cursor.getColumnIndex(COLUMN_NAME)));
            } while (cursor.moveToNext());
        }
        cursor.close();
        db.close();
        return list;
    }
}

4.1.2 高级查询及事务处理

除了基础的CRUD(创建、读取、更新、删除)操作外,SQLite还支持复杂的查询和事务处理。例如,可以使用 JOIN 语句来合并多个表的数据,使用 ORDER BY GROUP BY 来对查询结果进行排序和分组。事务处理允许执行多个操作,这些操作要么全部成功,要么全部不发生,这对于维护数据一致性非常关键。

public void deleteData(int id) {
    SQLiteDatabase db = this.getWritableDatabase();
    db.delete(TABLE_NAME, COLUMN_ID + " = ?", new String[] { String.valueOf(id) });
    db.close();
}

public void beginTransaction() {
    SQLiteDatabase db = this.getWritableDatabase();
    db.beginTransaction();
    try {
        // Perform complex operations here
        // ...
        db.setTransactionSuccessful();
    } catch (Exception e) {
        // Handle exceptions
    } finally {
        db.endTransaction();
        db.close();
    }
}

在处理事务时, beginTransaction() 方法开启一个新的事务,然后可以执行多个数据库操作。如果所有操作都成功执行,调用 setTransactionSuccessful() 来标记事务的成功。最后,通过 endTransaction() 结束事务,如果事务之前没有被标记为成功,则所有操作都不会被提交到数据库。

4.2 SharedPreferences的存储机制

4.2.1 数据的存取方法

SharedPreferences 是一种轻量级的存储类,适用于保存应用的私有简单数据,如设置或首选项。它通过键值对的方式来存储数据,使用非常方便。尽管它不是一种数据库技术,但 SharedPreferences 提供了一种快速存储少量数据的方法,非常适合存储简单的配置信息。

要使用 SharedPreferences 存储数据,可以调用 getSharedPreferences() 方法获取 SharedPreferences 实例,然后使用 edit() 方法获取 SharedPreferences.Editor 实例进行数据的存储。

SharedPreferences prefs = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("name", "Alice");
editor.putInt("age", 30);
editor.apply(); // 使用apply()异步保存数据,使用commit()同步保存数据

SharedPreferences 中读取数据也非常简单。可以使用 getString() , getInt() , getBoolean() 等方法,根据存储数据时使用的键来检索数据。

String name = prefs.getString("name", "DefaultName");
int age = prefs.getInt("age", 0);

4.2.2 SharedPreferences与数据安全

虽然 SharedPreferences 便于存储简单的数据,但在使用时也要注意数据安全。当存储敏感信息时,应该考虑加密存储,以防止数据泄露。另外, SharedPreferences 默认是应用私有的,不能被其他应用访问。但是,如果有其他应用拥有相同的用户ID,那么这些应用可以访问彼此的私有偏好设置文件。

使用 Context.MODE_PRIVATE 可以保证数据仅对当前应用可见,这是默认模式。如果要使数据对其他应用共享,可以使用 MODE_WORLD_READABLE MODE_WORLD_WRITEABLE ,但这两个模式已经被弃用,并且不推荐使用,因为它们可能导致安全漏洞。

4.3 数据持久化进阶技巧

4.3.1 Room持久化库的应用

Room是Android官方推荐的架构组件之一,用于数据库持久化操作。Room提供了一个抽象层,使得数据库操作更加简洁和易于理解。Room基于SQLite数据库,但添加了注解和编译时验证功能,大大简化了数据库操作。

要使用Room,首先需要定义三个主要组件:

  1. 数据库类(使用 @Database 注解标记的抽象类)
  2. 实体类(代表表)
  3. 数据访问对象(DAO,包含访问数据库的抽象方法)

以下是一个简单的Room数据库使用示例:

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

@Entity
public class User {
    @PrimaryKey
    public int uid;
    public String name;
}

@Dao
public interface UserDao {
    @Insert
    void insert(User user);
}
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
    AppDatabase.class, "database-name").build();
User user = new User(0, "John");
db.userDao().insert(user);

Room通过注解来自动处理数据的存储,开发者只需要关注数据模型和接口定义即可。编译时,Room会检查SQL查询的正确性和数据访问代码的正确性,使得开发者可以及早发现错误。

4.3.2 数据库升级与迁移策略

随着应用版本更新,可能会涉及到数据库结构的更改。在Android开发中,数据库升级需要谨慎处理,以防止数据丢失。Room提供了灵活的迁移策略,例如使用 @Migration 注解和 RoomDatabase.Migration 类来处理数据库版本更新。

@Database(entities = {User.class}, version = 2)
public abstract class AppDatabase extends RoomDatabase {
    // ...
}

// Migration from version 1 to version 2
static class Migration1To2 extends Migration {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        // Since we didn't alter the table, no need to do anything here.
    }
}

在上面的例子中,从版本1升级到版本2没有更改表结构,所以迁移函数为空。如果有结构变更,比如添加新列,可以在迁移函数中添加相应的SQL语句来更新数据库结构。

static class Migration2To3 extends Migration {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE User ADD COLUMN lastModified INTEGER");
    }
}

在应用升级过程中,Room会自动根据版本号和提供的迁移类来执行升级操作,这极大地简化了数据库升级流程,并确保了数据的完整性和安全性。

5. 网络请求库使用(Retrofit、OkHttp)

5.1 Retrofit的封装与调用

Retrofit是一个类型安全的HTTP客户端,它提供了简洁的API,用于执行同步或异步的HTTP请求。Retrofit将HTTP API转换成Java接口,简化了REST网络调用的过程。

5.1.1 Retrofit的基本使用方法

在项目中集成Retrofit,首先需要在 build.gradle 文件中添加依赖:

dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // 使用Gson转换器
}

然后创建一个接口定义API:

public interface ApiService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
}

配置Retrofit实例:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .addConverterFactory(GsonConverterFactory.create())
        .build();

ApiService service = retrofit.create(ApiService.class);

最后发起网络请求:

Call<List<Repo>> repos = service.listRepos("octocat");
repos.enqueue(new Callback<List<Repo>>() {
    @Override
    public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
        if (response.isSuccessful()) {
            List<Repo> repos = response.body();
            // 处理获取到的仓库列表数据
        }
    }

    @Override
    public void onFailure(Call<List<Repos>> call, Throwable t) {
        // 请求失败的处理
    }
});

5.1.2 自定义转换器和适配器

Retrofit默认使用Gson转换器将JSON数据转换为Java对象,如果需要使用其他库,可以自定义转换器。例如使用Moshi:

dependencies {
    implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
}

替换Gson为Moshi:

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .addConverterFactory(MoshiConverterFactory.create())
        .build();

适配器允许在请求发出之前进行自定义操作。创建一个 CallAdapter.Factory

public class LiveDataCallAdapterFactory extends CallAdapter.Factory {
    @Override
    public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
        if (getRawType(returnType) != LiveData.class) {
            return null;
        }
        if (!(returnType instanceof ParameterizedType)) {
            throw new IllegalArgumentException("LiveData return type must be parameterized");
        }
        Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
        Class<?> rawObservableType = getRawType(observableType);
        if (rawObservableType != Response.class) {
            throw new IllegalArgumentException("type must be a resource");
        }
        if (!(observableType instanceof ParameterizedType)) {
            throw new IllegalArgumentException("Resource must be parameterized");
        }
        return new LiveDataCallAdapter<>(observableType);
    }
}

build.gradle 添加依赖,并注册到Retrofit:

dependencies {
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
}
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(new LiveDataCallAdapterFactory())
        .build();

现在 ApiService 可以返回 LiveData 类型数据。

5.2 OkHttp的高级用法

OkHttp是一个高效的HTTP客户端,它支持HTTP/2和连接池等特性,是Retrofit的底层默认网络请求库。

5.2.1 OkHttp的拦截器使用

拦截器允许我们检查或修改请求或响应,可以用来添加认证信息、记录日志等。

创建一个拦截器:

public class LoggingInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        // 日志请求信息
        System.out.println(String.format("Sending request %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));

        long startNs = System.nanoTime();
        Response response;
        try {
            response = chain.proceed(request);
        } catch (Exception e) {
            // 日志请求失败
            throw e;
        }

        long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
        // 日志响应信息
        System.out.println(String.format("Received response for %s in %.1fms%n%s",
                response.request().url(), tookMs, response.headers()));

        return response;
    }
}

在构建OkHttpClient时添加拦截器:

OkHttpClient client = new OkHttpClient.Builder()
        .addInterceptor(new LoggingInterceptor())
        .build();

5.2.2 连接复用和网络监听

OkHttp支持连接池复用,可以在多个请求之间复用底层Socket连接,减少延迟。同时,OkHttp允许监听网络事件。

OkHttpClient client = new OkHttpClient.Builder()
        .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES))
        .addNetworkInterceptor(new ConnectivityInterceptor(context))
        .build();

ConnectivityInterceptor 示例:

public class ConnectivityInterceptor implements Interceptor {
    private Context context;

    public ConnectivityInterceptor(Context context) {
        this.context = context;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        if (!isInternetConnected()) {
            throw new NoConnectivityException();
        }

        Request.Builder builder = chain.request().newBuilder();
        return chain.proceed(builder.build());
    }

    private boolean isInternetConnected() {
        // 检查网络连接
    }
}

5.3 网络请求优化策略

5.3.1 缓存机制与数据同步

Retrofit和OkHttp都支持缓存机制。合理的缓存策略可以减少网络请求,提高应用性能。

在Retrofit构建器中启用缓存:

OkHttpClient client = new OkHttpClient.Builder()
        .cache(new Cache(new File(context.getCacheDir(), "http-cache"), 10 * 1024 * 1024))
        .addInterceptor(new CacheInterceptor(context))
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

CacheInterceptor 可以定义缓存策略,比如检查网络状态,决定是否使用缓存。

5.3.2 网络请求与用户界面的交互处理

对于耗时的网络请求,良好的用户体验是至关重要的。我们可以使用 LiveData 结合Retrofit,这样UI可以在数据变化时自动更新。

LiveData<Resource<List<Repo>>> reposLiveData = new MutableLiveData<>();

public void getRepos(String user) {
    reposLiveData.setValue(Resource.loading(null));
    Call<List<Repo>> repos = service.listRepos(user);
    repos.enqueue(new Callback<List<Repo>>() {
        @Override
        public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
            if (response.isSuccessful()) {
                List<Repo> repos = response.body();
                reposLiveData.setValue(Resource.success(repos));
            }
        }

        @Override
        public void onFailure(Call<List<Repo>> call, Throwable t) {
            reposLiveData.setValue(Resource.error("Network call has failed for a reason.", null));
        }
    });
}

Resource 是一个简单的封装类,可以代表加载状态、成功状态或错误状态。

public class Resource<T> {
    @NonNull public final Status status;
    @Nullable public final T data;
    @Nullable public final String message;

    private Resource(@NonNull Status status, @Nullable T data, @Nullable String message) {
        this.status = status;
        this.data = data;
        this.message = message;
    }

    public static <T> Resource<T> loading(@Nullable T data) {
        return new Resource<>(Status.LOADING, data, null);
    }

    public static <T> Resource<T> success(@Nullable T data) {
        return new Resource<>(Status.SUCCESS, data, null);
    }

    public static <T> Resource<T> error(String msg, @Nullable T data) {
        return new Resource<>(Status.ERROR, data, msg);
    }
}

接下来,UI组件可以通过观察 reposLiveData 来响应数据变化,并据此更新UI。

本章通过Retrofit和OkHttp两个流行的网络请求库,深入探讨了网络请求封装、调用、优化和用户界面交互的实践策略。通过理论与示例代码相结合,读者应能更好地理解和运用这两种库,并在实际项目中提高网络通信的效率和稳定性。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:该安卓订餐源代码项目向移动应用开发者展示了如何利用Android Studio 4.0或更新版本构建一个完整的订餐应用。项目内容包括用户界面设计、Activity和Fragment管理、数据持久化、网络请求、异步处理、适配器和列表组件使用、权限管理、通知和推送服务、测试策略以及版本控制等关键知识点。通过深入分析和实践这些组件,开发者能够提升其Android开发技能,并理解如何创建一个功能完善的订餐应用。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐