Android 应用开发学习笔记(2 of 2,from hitwh)

Android 应用开发

注意!由于文章图片是通过typora一键上传图片实现,该功能还存在bug,容易导致图片顺序混乱,文章(1 of 2)开头提供了原版文章的 pdf 资源下载,推荐下载 pdf 后观看,或评论区联系我获取其他方式的下载链接。

第6章 Intent

1 Intent简介

解决的问题:如何启动其他的Activity并实现它们之间的单/双向通信

Intent简介
  1. Intent是一种组件之间消息传递机制,它是一个动作的完整描述:包含了动作产生组件、接收组件和传递的数据信息。

    image-20230523220618321
  2. Intent主要用途:启动Activity、Service,在Android系统上发布Broadcast消息。

    image-20230523220659742

2 使用Intent启动Activity【显示与隐式】

显示启动
  1. 概念:在Intent中指明要启动的Activity类。

  2. 示例:

    image-20230523220755060
隐式启动
  • 说明:

    • 无需指明具体启动哪一个Activity,而由Android系统根据Intent的动作和数据来决定启动哪一个Activity。

    • 例如:希望启动一个浏览器,却不知道具体应该启动哪一 个Activity,此时则可以使用Intent的隐式启动,由Android 系统决定启动哪一个Activity来接收这个Intent。

    • 隐式启动的可以是Android系统内置的Activity,也可是程序本身的Activity,还可是第三方应用程序的Activity。

  1. 隐式启动示例1:启动浏览器打开一个网址

    image-20230523220904456
  2. 隐式启动示例2:打开播放器播放音乐

    image-20230523221210335
    • 播放音乐例子在7.0报错问题的解决:

      • 报错:FileUriExposedException: …exposed beyond app throughIntent.getData()

      • 原因:按照Android N的要求,如果 file://格式的Uri的Intent离开应 用将导致 FileUriExposedException 异常。若要在应用间共享文件, 应发送content://格式的Uri,并授予Uri临时访问权限,实现此方法的 是FileProvider类。

      • 解决方法:不推荐

        image-20230523221445197
    • 常见的Intent动作

      image-20230523221550582 image-20230523221612126
  3. 隐式启动示例3:程序拨打电话例子

    • 在AndroidManifest.xml添加权限:

      <uses-permission android:name="android.permission.CALL_PHONE" />
      
    • 核心程序代码:

      Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:18908643860"));
      startActivity(intent);
      
    • 权限申请 (6.0以后):代码见后

      image-20230523221918156 image-20230523221938312
    • 添加v4依赖(在安卓开发中,添加 v4 依赖是为了使用 Android Support Library 中的类和方法。Android Support Library 是一组库,可帮助您在旧版 Android 平台上使用最新 API 功能。v4 库是其中的一个库,它包含了许多有用的类和方法,例如 Fragment、ViewPager、NotificationCompat 等等。),此处方法我觉得不会考

      image-20230523225523104 image-20230523225642391

3 Activity间数据传递

总体图
image-20230523225921467
单向传递数据
  1. A 利用Bundle给 B 传递数据:★★★

    image-20230523230044070 image-20230523230034718
  2. 关于Bundle

    • Bundle类是一个存储和管理key-value值对的类,多应用于Activity之间相互传递值。

    • 用法示例:

      image-20230523230131776
    • Bundle类的一些方法:

      clear():清除此Bundle映射中的所有保存的数据。

      clone():克隆当前Bundle

      containsKey(String key):返回指定key的值

      getString(String key):返回指定key的字符

      isEmpty():如果这个捆绑映射为空,则返回true

      putString(String key, String value):插入一个给定key的字符串值

      remove(String key):移除指定key的值

  3. Intent类的putExtras()、getExtras()方法

    • putExtras(Bundle):往Intent中添加一个Bundle对象
    • getExtras():从Intent中取出Bundle对象
  4. 单向传递数据示例1 – 传递普通数据

image-20230523230711647 image-20230523230726499
  1. 单向传递数据示例2 – 传递对象数据

    • 针对前例,如何用一个user对象传递数据:

      image-20230523230828100
    • 关键点在于Bundle如何传递对象数据。

    • Bundle可以传递对象,但前提是这个对象需要序列化

      什么是序列化:序列化是一种用来处理对象流的机制, 以解决如网络传播、磁盘读写等对对象流读写操作时所引发的问题。

    • Bundle的putSerializable()方法,可以存储已经序列化的对象数据(仍然是Key-Value形式);

    • 接收数据时Bundle用getSerializable()方法,获得数据需要强制转化一下原来的对象类型。

    • 具体过程如下:

      (1)先将User类序列化(直接实现Serializable接口即可)

      image-20230523231057425

      (2)发送端:RegisterActivity主要代码

      image-20230523231117861

      (3)接收端:WelcomeActivity主要代码

      image-20230523231249223
双向传递数据
  1. 总图

    image-20230523231358057
  2. A ↔ B 工作原理

image-20230523232344449
  1. B 返回数据给 A 的核心代码框架 ★★★
image-20230523232911832 image-20230523232943961
  1. 双向传递数据示例1:A_Activity输入两个数,B_Activity求和并返回值

    image-20230523233316859
    • A_Activity主要代码1

      image-20230523233349370
    • B_Activity主要代码

      image-20230523233415369
    • A_Activity主要代码2 – 关键事件onActivityResult

      image-20230523233434913
  2. 双向传递数据示例2 – 将DatePicker控件单独作为一个activity(自学)

    image-20230523233507199
    • MainActivity主要代码1

      image-20230523233559047
    • MainActivity主要代码2

      image-20230523233615222
    • CalendarActivity主要代码1

      image-20230523233652555
    • CalendarActivity主要代码2

      image-20230523233710202

第7章 Android广播机制

1 什么是广播

概述
  1. 在Android中,广播(Broadcast)是一种广泛运用在应用程序之间传输信息的机制。
  2. 广播消息实质就是将一个Intent对象用sendBroadcast方法 发送出去 (详见7.3节自定义广播)。
  3. 两种类型广播:
    • 系统广播:如系统启动完成了、拨打电话了、收到短信了、 手机没电了等系统发送的消息
    • 自定义广播:将自定义的消息广播给应用程序
BroadcastReceiver
  1. BroadcastReceiver是对发送出来的Broadcast进行过滤并响应的一类组件,是Android系统的四大组件之一。

  2. BroadcastReceiver一般要在AndroidManifest.xml中注册

    image-20230524000054038

2 系统广播

概述
  1. 系统广播就是由Android系统发出的广播。
  2. 如系统启动完成了、系统关闭了、拨打电话了、收到短信 了、手机没电了、新安装了一个应用程序、在耳机口上插 入耳机、设备内存不足、屏幕关闭等等。
系统广播示例1:手机关机时播放关机音乐
  1. 准备工作:新建一个新Module(名为TestBroadcast)

    • 在res文件夹下新建raw文件夹

    • 将音乐文件复制到raw文件夹中

      image-20230524000219653
  2. 创建广播接收器BroadcastReceiver

    image-20230524000316179
    • 生成的MyReceiver.java代码框架:

      image-20230524000435995
  3. 处理广播:在onReceive添加处理代码:

    image-20230524000504211
  4. 注册接收器:(静态注册)

    注册:告诉系统这个BroadcastReceiver要接收哪种广播消息(Intent-filter)

    在AndroidManifest.xml文件中添加注册信息(红色部分)

    image-20230524001107139
  5. 权限设置:有的广播需要权限支持,本例不需要

    如需添加权限,则在AndroidManifest.xml中添加,例如:

    image-20230524001154271
  6. 测试运行: 先运行一下MainActivity,将app安装到手机中(告诉系统有BroadcastReceiver);然后再锁屏,就有音乐放了。

  7. 部分系统广播

    image-20230524001503090
示例2:监听拨打电话(去电)
  1. 给Receiver添加接收拨打电话的广播

    image-20230524001539020
  2. 给app添加处理拨打电话的权限

    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
    
  3. 补充:AS 6.0以后的模拟器

    image-20230524001757288
  4. 修改Receiver代码

    image-20230524001905404

3 自定义广播

主要步骤
  1. 发送广播消息

    在广播发送端(如某个APP的Activity),把信息装入一个Intent对象(含一个自定义的消息标识串,值任意,能保证唯一性即可), 然后sendBroadcast(intent)把 Intent 广播出去。

    image-20230524002120847
  2. 接收广播

    接收端创建Broadcast Receiver

    • 静态注册:在AndroidManifest.xml中注册Receiver,添加intent-filter

      image-20230524002227995
    • 动态注册:代码实现注册

      image-20230524002242248
  3. 接收端处理广播消息:onReceive()方法

    image-20230524002347750
  4. 关于消息广播标识串:

    • 消息广播标识串,自定义值,具有唯一性即可

    • 这个标识在后面有3个地方要用:

      • 发送广播时:用于创建Intent对象,Intent intent = new Intent(“广播标识串”);
      • manifest中注册时用:作为BroadcastReceiver的intent-filter的action name值
      • 处理消息时:在onReceive()方法中作为消息类型的判断用
    • 通常也可以把标识串定义为符号常量,如:

      final String BROADCAST_ACTION_NAME = “如 wust.zz.mybroadcast";
      
自定义广播示例(静态注册)
  1. 总图

    image-20230524002624032
  2. 发送端app:MainActivity主要代码

    image-20230524002640515
  3. 接收端app:MyReceiver主要代码

    image-20230524002700764
  4. MyReceiver配置 (静态注册)

    AndroidManifest.xml文件:红色部分

    image-20230524002733368
  5. 补充:Android8.0自定义广播无法接收问题解决

    • 方法1:使用动态广播代替静态广播

    • 方法2:保留原来的静态广播,但intent但加入Component参数:

      image-20230524002834501

附录:短信黑名单示例

概述
  1. 监听SMS短信信息(SMS,Short Messaging Service)

    • 如果对方发送的短信包含“@echo”串,就自动回发“denyyou!”信息,并将该号码列为黑名单
  2. BroadcastReceiver采用动态注册

    image-20230524003122649
MainActivity界面(activity_main.xml)
<ToggleButton
    android:id="@+id/toggleButton1" android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:layout_alignParentLeft="true" 
    android:layout_alignParentTop="true" 
    android:layout_marginLeft="74dp" 
    android:layout_marginTop="44dp"
    android:text="监控SMS" 
    android:textOff="SMS监控已关闭" 
    android:textOn="SMS监控已开启" />
SMSReceiver代码
image-20230524003441180 image-20230524003456563
获取短信说明
image-20230524003530986
AndroidManifest.xml添加SMS权限
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
MainActivity主要代码
image-20230524003622369

第8章 Service

1 什么是Service

  1. Service是Android系统四大组件之一,它是一种长生命周期的,没有可视化界面,运行于后台的一种服务程序。

    image-20230524003756058

2 Service类型

Started启动的
  1. Started启动的:常用于应用程序内部

  2. Started形式是指当一个应用组件(如Activity)通过startService()方法 开启的服务。一旦开启,该服务就可以无限期地在后台运行,哪怕开启它的组件被销毁。

  3. 基本特点:

    • 在应用程序中定义service组件。

    • 服务通过调用startService(intent)启动,stopService(intent)结束。

    • 在服务内部可以调用stopSelf() 来自己停止。

    • 通常,开启的服务执行一个单独的操作且并不向调用者返回一个结果。

    • 无论调用了多少次startService(),只需调用一次stopService()来停止。

Bound绑定的
  1. Bound绑定的:用于应用程序之间
  2. Bound形式是指一个应用组件通过调用 bindService() 方法与服务绑定。调用 unbindService()关闭连接。
  3. Bound形式的服务一旦启用,调用者就与服务绑定在一起,调用者一旦退出,服务也就终止。
  4. 多个组件可以同时绑定到一个服务,但当全部绑定解除后,服务就被销毁
Service生命周期
image-20230524004359260

3 本地服务示例

概述
  1. 示例图

    image-20230524004459973
  2. 主要步骤:

    • 准备工作
    • 创建MusicService
    • 主程序
    • 功能改进
准备工作
  1. 在res文件夹下新建raw文件夹

  2. 将音乐文件复制到raw文件夹中

    image-20230524004556330
创建MusicService
image-20230524004619386
  • MusicService代码框架

    image-20230524004704822
  • MusicService代码

    image-20230524004824291
主程序(主要代码)
image-20230524004853476
功能改进(自学)
  • 增加暂停、重播、结束(停止服务)功能

    image-20230524005011423
  • 思路:通过在Intent传递参数值给Service

    • 假设参数值:
      • 1 – 播放
      • 2 – 暂停
      • 3 – 重播
      • 0 – 结束(内部结束服务)
      • -1 – 退出(外部结束服务和Activity)
  • 主界面activity_main.xml

    image-20230524005045959
  • MainActivity主要代码

    image-20230524005102406 image-20230524005120549 image-20230524005152005
  • MusicService主要代码

    image-20230524005218900 image-20230524005228814

4 广播交互

  • 在Activity中点击Button后启动Service

    public void onClick(View v) {
          
          
        Intent intent = new Intent(this, CounterService.class);
        intent.putExtra("counter", counter); //counter用来计数
        startService(intent);
    }
    
  • CounterService.java

    public class CounterService extends Service {
          
           
        int counter; 
        @Override
        public IBinder onBind(Intent intent) {
          
          
            // TODO Auto-generated method stub
            return null;
        } 
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
          
          
            // TODO Auto-generated method stub
            counter = intent.getIntExtra("counter", 0);
            new Timer().schedule(new TimerTask() {
          
          
                @Override
                public void run() {
          
          
                    // TODO Auto-generated method stub
                    Intent counterIntent = new Intent();
                    counterIntent.putExtra("counter", counter);
                    counterIntent.setAction("com.example.counter.COUNTER_ACTION");
                    sendBroadcast(counterIntent);
                    counter++;
                }
            }
            return START_STICKY;
        } 
    }
    
  • 在Service的onStartCommand()中启动一个定时器,每隔1秒钟counter计数加1,通过广播发送将counter发送出去,在Activity中的CounterReceiver收到广播后取出counter,将counter通过handler 发送出去

    // 广播接收器,定义在Activity中 
    class CounterReceiver extends BroadcastReceiver {
          
          
    	Handler handler;
    	CounterReceiver(Handler handler){
          
          
    		this. handler = handler;
    	}
        @Override
        public void onReceive(Context context, Intent intent) {
          
          
            counter = intent.getIntExtra("counter", 0);
    	    handler.sendEmptyMessage(counter);
        }
    }
    //运行程序,点击按钮开始计时
    
  • 在Activity中通过Handler设置到Button上,Button要设计为Activity的全局量, 并在onCreat中初始化:

    CounterReceiver receiver;
    Button start;
    Handler handler = new Handler(){
          
          
    	 @Override
    	 public void handleMessage(Message msg){
          
          
    		start. setText(String.valueOf (msg.what)) 
    		super.handleMessage(msg); 
    	} 
    }
    
  • 在Activity的onCreat中

    Receiver = new CounterReceiver(handler)
    start = (Button)findViewById(R.id.start)
    
  • 示例图:

    image-20230524010953805
  • 在程序运行过程遇到一个问题,在这里说明一下

    • 广播类是在Activity里定义的,是Activity的内部类,这个内部类在使用静态注册的时候,会发生程序运行崩溃,原因是内部广播类如果使用静态注册,必须是静态内部类。
    • 但是如果是静态内部类,只能访问外部类的静态成员变量,所以内部广播类推荐使用动态注册方式。
    • 而且这类广播一般只在程序内部使用,没有必须在进程结束以后继续接收广播。
  • 通过广播实现Activity和Service的交互简单容易实现。

    • 缺点是发送不广播受系统制约,系统会优先发送系统级的广播,自定义的广播接收器可能会有延迟。
    • 在广播里也不能有耗时操作,否则会导致程序无响应。
  • Activity中动态注册receiver ,如放在onCreat中:

    IntentFilter filter = new IntentFilter();
    filter.addAction("com.example.counter.COUNTER_ACTION ");
    BroadcastReceiver receiver = new MyReceiver();
    registerReceiver( receiver , filter);
    
  • 此外Activity销毁的时候,注销(关闭)广播:

    unregisterReceiver(receiver);
    
  • 进一步学习:有关远程服务的创建和调用

    参考:

    • http://www.apkbus.com/android-131250-1-1.html
    • http://liangruijun.blog.51cto.com/3061169/653344/

第9章 1+简单数据存储和访问(简单)(SharedPreference)

0 简单数据存储

  1. 应用程序一般允许用户自己定义配置信息,如界面背景颜色、字体大小和字体颜色等。
  2. 使用SharedPreferences保存用户的自定义配置信息,并在程序启动时自动加载这些自定义的配置信息。

1 简单存储

概述
  1. 用户在界面上的输入的信息,在Activity关闭时进行保存。当应用程序重新开启时,保存信息将被读取出来,并重新呈现在用户界面上
SharedPreferences
  1. SharedPreferences是一种轻量级的数据保存方式。

    • 通过SharedPreferences可以将NVP(Name/Value Pair,名称/值对)保存在Android的文件系统中,而且SharedPreferences完全屏蔽的对文件系统的操作过程。
    • 开发人员仅是通过调用SharedPreferences对NVP进行保存和读取。
  2. SharedPreferences不仅能够保存数据,还能够实现不同应用程序间的数据共享

    • SharedPreferences支持三种访问模式
      • 私有(MODE_PRIVATE):仅有创建程序有权限对其进行读取或写入。
      • 全局读(MODE_WORLD_READABLE):不仅创建程序可以对其进行读取或写入,其他应用程序也读取操作的权限,但没有写入操作的权限。
      • 全局写(MODE_WORLD_WRITEABLE):创建程序和其他程序都可以对其进行写入操作,但没有读取的权限。
  3. 在使用SharedPreferences前,先定义SharedPreferences的访问模式。

    • 下面的代码将访问模式定义为私有模式:

      public static int MODE = MODE_PRIVATE;
      
    • 有的时候需要将SharedPreferences的访问模式设定为即可以全局读,也可以全局写,这样就需要将两种模式写成下面的方式:

      public static int MODE = Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE;
      
  4. 定义SharedPreferences的名称

    • 这个名称与在Android文件系统中保存的文件同名。因此,只要具有相同的SharedPreferences名称的NVP内容,都会保存在同一个文件中

      public static final String PREFERENCE_NAME = "SaveSetting";
      
    • 为了可以使用SharedPreferences,需要将访问模式和SharedPreferences名称作为参数,传递到getSharedPreferences()函数,并获取到SharedPreferences对象

      SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCE_NAME, MODE);
      
  5. 修改SharedPreferences

    • 在获取到SharedPreferences对象后,则可以通过SharedPreferences.Editor类对SharedPreferences进行修改,最后调用commit()函数保存修改内容。

    • SharedPreferences广泛支持各种基本数据类型,包括整型、布尔型、浮点型和长型等等。

      SharedPreferences.Editor editor = sharedPreferences.edit();
      editor.putString("Name", "Tom");
      editor.putInt("Age", 20);
      editor.putFloat("Height", 1.67);	//这里PPT没有float的内容,猜测是这样
      editor.commit();
      
  6. 读取SharedPreferences

    • 如果需要从已经保存的SharedPreferences中读取数据,同样是调用getSharedPreferences()函数,并在函数的第1个参数中指明需要访问的SharedPreferences名称,最后通过get<Type>()函数获取保存在SharedPreferences中的NVP。

      SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCE_NAME, MODE);
      String name = sharedPreferences.getString("Name","Default Name");
      int age = sharedPreferences.getInt("Age", 20);
      float height = sharedPreferences.getFloat("Height",);
      
      • get<Type>()函数的第1个参数是NVP的名称。
      • 第2个参数是在无法获取到数值的时候使用的缺省值。
示例

通过SimplePreferenceDemo示例介绍具体说明SharedPreferences的文件保存位置和保存格式。

  1. 下图是SimplePreferenceDemo示例的用户界面

    用户在界面上的输入的信息,将通过SharedPreferences在Activity关闭时进行保存。当应用程序重新开启时,保存在SharedPreferences的信息将被读取出来,并重新呈现在用户界面上。

    image-20230524102128850
  2. 文件信息

    • SimplePreferenceDemo示例运行后,通过FileExplorer查看/data/data下的数据,Android为每个应用程序建立了与包同名的目录,用来保存应用程序产生的数据,这些数据包括文件、SharedPreferences文件和数据库等。

    • SharedPreferences文件就保存在/data/data/<package name>/shared_prefs目录下。

    • 在本示例中,shared_prefs目录下生成了一个名为SaveSetting.xml的文件:

      image-20230524102435288
      • 这个文件就是保存SharedPreferences的文件,文件大小为170字节,在Linux下的权限为“-rw-rw-rw”

        在Linux系统中,文件权限分别描述了创建者、同组用户和其他用户对文件的操作限制。

        • x表示可执行,r表示可读,w表示可写,d表示目录,-表示普通文件。因此,“-rw-rw-rw”表示SaveSetting.xml可以被创建者、同组用户和其他用户进行读取和写入操作,但不可执行。
        • 产生这样的文件权限与程序人员设定的SharedPreferences的访问模式有关,“-rw-rw-rw”的权限是“全局读+全局写”的结果。
        • 如果将SharedPreferences的访问模式设置为私有,则文件权限将成为“-rw-rw —”,表示仅有创建者和同组用户具有读写文件的权限。
    • SaveSetting.xml文件是以XML格式保存的信息,内容如图如下

      <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
      <map>
      	<float name="Height" value="1.81" />
      	<string name="Name">Tom</string>
      	<int name="Age" value="20" />
      </map>
      
  3. 具体功能与相关代码

    • SimplePreferenceDemo示例在onStart()函数中调用loadSharedPreferences()函数,读取保存在SharedPreferences中的姓名、年龄和身高信息,并显示在用户界面上。

      • 当Activity关闭时,在onStop()函数调用saveSharedPreferences(),保存界面上的信息

      • SimplePreferenceDemo.java的完整代码:

        package edu.hrbeu.SimplePreferenceDemo;
         
        import android.app.Activity;
        import android.content.Context;
        import android.content.SharedPreferences;
        import android.os.Bundle;
        import android.widget.EditText;
         
        public class SimplePreferenceDemo extends Activity {
                  
                  
        	
        	private EditText nameText;
        	private EditText ageText;
        	private EditText heightText;
        	public static final String PREFERENCE_NAME = "SaveSetting";
        	public static int MODE = Context.MODE_WORLD_READABLE + Context.MODE_WORLD_WRITEABLE;
        	
        	@Override
        	public void onCreate(Bundle savedInstanceState) {
                  
                  
        		super.onCreate(savedInstanceState);
        		setContentView(R.layout.main);
        		nameText = (EditText)findViewById(R.id.name);
        		ageText = (EditText)findViewById(R.id.age);
        		heightText = (EditText)findViewById(R.id.height);
        	}
           
            @Override
        	public void onStart(){
                  
                  
        		super.onStart();
        		loadSharedPreferences();
        	}
        	@Override
        	public void onStop(){
                  
                  
        		super.onStop();	
        		saveSharedPreferences();
        	}
            
        	private void loadSharedPreferences(){
                  
                  
        	  	SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCE_NAME, MODE);
        		String name = sharedPreferences.getString("Name","Tom");
        		int age = sharedPreferences.getInt("Age", 20);
        		float height = sharedPreferences.getFloat("Height",);
         		nameText.setText(name);
        		ageText.setText(String.valueOf(age));
        		heightText.setText(String.valueOf(height));    	
        	}
            
            private void saveSharedPreferences(){
                  
                  
        		SharedPreferences sharedPreferences = getSharedPreferences(PREFERENCE_NAME, MODE);
        		SharedPreferences.Editor editor = sharedPreferences.edit();
        		    
        		editor.putString("Name", nameText.getText().toString());
        		editor.putInt("Age", Integer.parseInt(ageText.getText().toString()));
        		editor.putFloat("Height", Float.parseFloat(heightText.getText().toString()));
        		editor.commit();
        	}
        }
        
    • 示例SharePreferenceDemo将说明如何读取其他应用程序保存的SharedPreferences数据。

      • 下图是SharePreferenceDemo示例的用户界面。

      • 示例将读取SimplePreferenceDemo示例保存的信息,并在程序启动时显示在用户界面上。

        image-20230524103140761
    • SharePreferenceDemo示例的核心代码

      image-20230524103217095 image-20230524110121702
  4. 访问其他应用程序的SharedPreferences必须满足三个条件:

    • 共享者需要将SharedPreferences的访问模式设置为全局读或全局写。
    • 访问者需要知道共享者的包名称和SharedPreferences的名称,以通过Context获得SharedPreferences对象。
    • 访问者需要确切知道每个数据的名称和数据类型,用以正确读取数据。

2 文件存储

概述
  1. Android使用的是基于Linux的文件系统。
  2. 程序开发人员可以建立和访问程序自身的私有文件。
  3. 也可以访问保存在资源目录中的原始文件和XML文件。
  4. 还可以在SD卡等外部存储设备中保存文件。
内部存储
  1. 在内部存储器上进行文件写入和读取

    image-20230524103912679
  2. Android系统允许应用程序创建仅能够自身访问的私有文件,文件保存在设备的内部存储器上,在Linux系统下的/data/data/<package name>/files目录中。

  • Android系统不仅支持标准Java的IO类和方法,还提供了能够简化读写流式文件过程的函数。
  • 主要介绍的两个函数:
    • openFileOutput()
    • openFileInput()
  1. openFileOutput()函数

    • openFileOutput()函数为写入数据做准备而打开的应用程序私文件,如果指定的文件不存在,则创建一个新的文件。

    • openFileOutput()函数的语法格式如下:

      public FileOutputStream openFileOutput(String name, int mode)
      
      • 第1个参数是文件名称,这个参数不可以包含描述路径的斜杠。
      • 第2个参数是操作模式。
    • 函数的返回值是FileOutputStream类型。

    • Android系统支持四种文件操作模式:

      image-20230524104208645
    • 使用openFileOutput()函数建立新文件的示例代码如下:

      String FILE_NAME = "fileDemo.txt";  //定义了建立文件的名称fileDemo.txt
      FileOutputStream fos = openFileOutput(FILE_NAME,Context.MODE_PRIVATE)  //使用openFileOutput()函数以私有模式建立文件
      String text =Some data”;
      fos.write(text.getBytes());  //调用write()函数将数据写入文件
      fos.flush();  //调用flush()函数将所有剩余的数据写入文件
      fos.close();  //调用close()函数关闭FileOutputStream
      
      • 为了提高文件系统的性能,一般调用write()函数时,如果写入的数据量较小,系统会把数据保存在数据缓冲区中,等数据量累积到一定程度时再一次性的写入文件中。
      • 由上可知,在调用close()函数关闭文件前,务必要调用flush()函数,将缓冲区内所有的数据写入文件。
  2. openFileInput()函数

    • openFileInput()函数为读取数据做准备而打开应用程序私文件。

    • openFileInput()函数的语法格式如下:

      public FileInputStream openFileInput (String name)
      
      • 第1个参数也是文件名称,同样不允许包含描述路径的斜杠。
    • 使用openFileInput ()函数打开已有文件的示例代码如下:

      String FILE_NAME = "fileDemo.txt";
      FileInputStream fis = openFileInput(FILE_NAME);
       
      byte[] readBytes = new byte[fis.available()];
      while(fis.read(readBytes) != -1){
              
              
      }
      
      • 上面的两部分代码在实际使用过程中会遇到错误提示,因为文件操作可能会遇到各种问题而最终导致操作失败,因此代码应该使用try/catch捕获可能产生的异常。
  3. InternalFileDemo示例用来演示在内部存储器上进行文件写入和读取。

    • InternalFileDemo示例用户界面如图:

      image-20230524104916033
    • InternalFileDemo示例的核心代码:

      OnClickListener writeButtonListener = new OnClickListener() {
              
               
      	@Override
      	public void onClick(View v) {
              
              
      		FileOutputStream fos = null;	
      		try {
              
              
      		      if (appendBox.isChecked()){
              
              
      		           fos = openFileOutput(FILE_NAME,Context.MODE_APPEND);
      	                        }
                                         else {
              
              
      		          fos = openFileOutput(FILE_NAME,Context.MODE_PRIVATE);
      		      }
      		String text = entryText.getText().toString();
      		fos.write(text.getBytes());
      		labelView.setText("文件写入成功,写入长度:"+text.length());
      		entryText.setText("");
                                    } catch (FileNotFoundException e) {
              
              
                                                     e.printStackTrace();
      		}
      		catch (IOException e) {
              
              
      			e.printStackTrace();
      		}
      		finally{
              
              
      			if (fos != null){
              
              
      				try {
              
              
      					fos.flush();
      					fos.close();
      				} catch (IOException e) {
              
              
      					e.printStackTrace();
      				}
      			}
      		}
      	}
      };
      OnClickListener readButtonListener = new OnClickListener() {
              
               
      @Override
       	public void onClick(View v) {
              
              
       		displayView.setText("");
       		FileInputStream fis = null;
       		try {
              
              
      		            fis = openFileInput(FILE_NAME);
      		            if (fis.available() == 0){
              
              
      			   return;
      	                               }
      		             byte[] readBytes = new byte[fis.available()];
      		             while(fis.read(readBytes) != -1){
              
              
      		             }
      		             String text = new String(readBytes);
      		             displayView.setText(text);
      		              labelView.setText("文件读取成功,文件长度:"+text.length());
                                    } catch (FileNotFoundException e) {
              
              
                                                     e.printStackTrace();
      		}
      		catch (IOException e) {
              
              
      			e.printStackTrace();
      		}
      	}
      };
      
      • 程序运行后,在/data/data/edu.hrbeu.InternalFileDemo/files/目录下,找到了新建立的fileDemo.txt文件。

      • fileDemo.txt文件:

        image-20230524105441595
        • fileDemo.txt从文件权限上进行分析,“-rw-rw—”表明文件仅允许文件创建者和同组用户读写,其他用户无权使用。
        • 文件的大小为9个字节,保存的数据为“Some data”
外部存储
  1. Android的外部存储设备指的是SD卡(Secure Digital Memory Card),是一种广泛使用于数码设备上的记忆卡。不是所有的Android手机都有SD卡,但Android系统提供了对SD卡的便捷的访问方法。

  2. SD卡适用于保存大尺寸的文件或者是一些无需设置访问权限的文件,可以保存录制的大容量的视频文件和音频文件等。

    • SD卡使用的是FAT(File Allocation Table)的文件系统,不支持访问模式和权限控制,但可以通过Linux文件系统的文件访问权限的控制保证文件的私密性。
    • Android模拟器支持SD卡,但模拟器中没有缺省的SD卡,开发人员须在模拟器中手工添加SD卡的映像文件。
  3. 使用<Android SDK>/tools目录下的mksdcard工具创建SD卡映像文件,命令如下:

    mksdcard -l SDCARD 256M E:\android\sdcard_file
    
    • 第1个参数-1表示后面的字符串是SD卡的标签,这个新建立的SD卡的标签是SDCARD。
    • 第2个参数256M表示SD卡的容量是256兆。
    • 最后一个参数表示SD卡映像文件的保存位置,上面的命令将映像保存在E:\android目录下sdcard_file文件中。在CMD中执行该命令后,则可在所指定的目录中找到生产的SD卡映像文件。
  4. 如果希望Android模拟器启动时能够自动加载指定的SD卡,还需要在模拟器的“运行设置”(Run Configurations)中添加SD卡加载命令。

    • SD卡加载命令中只要指明映像文件位置即可。

    • SD卡加载命令:

      image-20230524105723436
  5. 测试SD卡映像是否正确加载

    • 在模拟器启动后,使用FileExplorer向SD卡中随意上传一个文件,如果文件上传成功,则表明SD卡映像已经成功加载。

    • 向SD卡中成功上传了一个测试文件test.txt,文件显示在/sdcard目录下

      image-20230524105808587
  6. 编程访问SD卡

    • 首先需要检测系统的/sdcard目录是否可用。
    • 如果不可用,则说明设备中的SD卡已经被移除,在Android模拟器则表明SD卡映像没有被正确加载。
    • 如果可用,则直接通过使用标准的Java.io.File类进行访问。
  7. 将数据保存在SD卡

    • 通过“生产随机数列”按钮生产10个随机小数。
    • 通过“写入SD卡”按钮将生产的数据保存在SD卡的目录下。
    • SDcardFileDemo示例说明了如何将数据保存在SD卡。
  8. SDcardFileDemo示例

    • 用户界面

      image-20230524110010947
    • SDcardFileDemo示例运行后,在每次点击“写入SD卡”按钮后,都会在SD卡中生产一个新文件,文件名各不相同。

      • SD卡中生产的文件:

        image-20230524110044850
    • SDcardFileDemo示例的核心代码

      image-20230524110219083 image-20230524110238366
资源文件
  1. 程序开发人员可以将程序开发阶段已经准备好的原始格式文件和XML文件分别存放在/res/raw和/res/xml目录下,供应用程序在运行时进行访问。

    • 原始格式文件可以是任何格式的文件,例如视频格式文件、音频格式文件、图像文件和数据文件等等,在应用程序编译和打包时,/res/raw目录下的所有文件都会保留原有格式不变。
    • /res/xml目录下的XML文件,一般用来保存格式化的数据,在应用程序编译和打包时会将XML文件转换为高效的二进制格式,应用程序运行时会以特殊的方式进行访问。
  2. ResourceFileDemo示例

    • ResourceFileDemo示例演示了如何在程序运行时访问资源文件。

      • 当用户点击“读取原始文件”按钮时,程序将读取/res/raw/raw_file.txt文件,并将内容显示在界面上。
      image-20230524110426768
      • 当用户点击“读取XML文件”按钮时,程序将读取/res/xml/people.xml文件,并将内容显示在界面上。

        image-20230524110532679
    • 读取原始格式文件,首先需要调用getResource()函数获得资源对象,然后通过调用资源对象的openRawResource()函数,以二进制流的形式打开指定的原始格式文件。在读取文件结束后,调用close()函数关闭文件流。

      • ResourceFileDemo示例中关于读取原始格式文件的核心代码如下:

        Resources resources = this.getResources();
        InputStream inputStream = null;
        try {
                  
                  
        	inputStream = resources.openRawResource(R.raw.raw_file);    
        	byte[] reader = new byte[inputStream.available()]; 
        	while (inputStream.read(reader) != -1) {
                  
                   
        	}
            displayView.setText(new String(reader,"utf-8")); 
        } catch (IOException e) {
                  
                  
        	Log.e("ResourceFileDemo", e.getMessage(), e); 
        } finally {
                  
                  
        	if (inputStream != null) {
                  
                   
        		try {
                  
                  
        			inputStream.close(); 
        		} catch (IOException e) {
                  
                   } 
        	}
        }
        
        • 代码第8行的new String(reader,“utf-8”),表示以UTF-8的编码方式,从字节数组中实例化一个字符串。
      • 程序开发人员需要确定/res/raw/raw_file.txt文件使用的是UTF-8编码方式,否则程序运行时会产生乱码。确认的方法:

        • 右击raw_file.txt文件。

        • 选择“Properties”打开raw_file.txt文件的属性设置框。

        • 在Resource栏下的Text file encoding中,选择“Other:UTF-8”。

          image-20230524111134519
    • /res/xml目录下的XML文件会转换为一种高效的二进制格式

      • 如何在程序运行时读取/res/xml目录下的XML文件

        • 首先在/res/xml目录下创建一个名为people.xml的文件。
        • XML文件定义了多个<person>元素,每个<person>元素都包含三个属性name、age和height,分别表示姓名、年龄和身高。
      • /res/xml/people.xml文件代码如下:

        <people> 
        	<person name="李某某" age="21" height="1.81" /> 
        	<person name="王某某" age="25" height="1.76" /> 
        	<person name="张某某" age="20" height="1.69" /> 
        </people>
        
    • 读取XML格式文件,首先通过调用资源对象的getXml()函数,获取到XML解析器XmlPullParser。XmlPullParser是Android平台标准的XML解析器,这项技术来自一个开源的XML解析API项目XMLPULL。

      • ResourceFileDemo示例中关于读取XML文件的核心代码如下:

        image-20230524111511617 image-20230524111534940 image-20230524111552026
      • XmlPullParser的XML事件类型

        image-20230524111618923

第9章 2++数据存储和访问(SQLite示例)

image-20230524111841475

1 ListView显示数据库数据(ShowActivity)

ListView 2个关键点
  1. 自定义layout布局;

  2. 使用SimpleCursorAdapter适配器填充:(用于显示数据库数据)

    该适配器允许将一个Cursor(查询结果集)的数据列绑定到ListView自定 义布局中的TextView 或 ImageView组件上(详见后面代码)

    image-20230524112403877
  3. 代码详解:

    • ListView自定义layout布局,新建一个名为listview.xml布局:

      image-20230524112444563
    • 使用SimpleCursorAdapter填充ListView,SimpleCursorAdapter基本方法:

      image-20230524112546497
    • ShowAcitvity的OnCreate()主要代码:

      image-20230524112839870

2 添加记录功能(InsertActivity)

image-20230524112956668
  1. ShowActivity“添加”按钮

    image-20230524113108634
  2. InsertActivity“保存”按钮

    image-20230524113144450 image-20230524113333413
  3. ShowActivity接收回传信息(下面代码与onCreate并列)

    image-20230524113435762

3 给ListView添加ContextMenu菜单:删除+编辑(UpdateActivity)

主要步骤
  1. 第1步:新建菜单资源

    • 在res下新建menu目录

    • 在menu目录下新建一个manage.xml文件,代码:

      image-20230524113633187
  2. 第2步:创建上下文菜单(下面代码放在ShowActivity中,与onCreate并列)

    image-20230524113707414
  3. 第3步:添加上下文菜单选中项方法(下面代码放在ShowActivity中,与onCreateContextMenu并列)

    image-20230524113945191
  4. 第4步:将上下文菜单注册到ListView上(完)(下面代码放在ShowActivity中onCreate中)

    image-20230524114028238
  5. 后续删除、修改操作分析:

    • 两个操作的关键点:

      • 如何得到选中行的id值以及其他字段值(name、age)。
      • 删除:根据id值作为删除记录的条件。
      • 修改:将id值及其他字段的值传到UpdateActivity显示和修改。
    • 关键点代码:

      image-20230524165040061
    • 具体代码:

      • 删除操作 delete(MenuItem item) 方法

        image-20230524165116701
      • 修改操作

        image-20230524165252238

        UpdateActivity:oncreat()代码

        image-20230524165506477 image-20230524165526980 image-20230524165550979
      • ShowActivity接收回传信息

        image-20230524165623811

猜你喜欢

转载自blog.csdn.net/m0_56942491/article/details/131734570