«

安卓速通(其实是期末复习~)(第八章+第九章)

ZealSinger 发布于 阅读:122 期末复习


服务Service

概述

服务Service是四大组件之一,是一个长期运行在后台得用户组件,没有用户界面

Service可以和其他组件之间进行交互,一般由Activity启动但是不依赖于Acitivy,当Activity的生命周期结束的时候也不会结束Service,直至Service自己的生命周期结束,也通常被称之为后台服务,所谓后台是一个想对于前台的概念,具体是指其本身的运行不需要依靠用户可视化UI界面,其两大应用场景:后台运行+跨进程访问(当service被其它应用组件启动的时候,即使用户切换到别的应用程序,服务仍然在后台继续运行)

Service的特点
(1)无用户界面,后台运行
(2)长时间运行
(3)有自己的生命周期管理,存在onCreate(),onStartCommand(),onBind(),onUnbind(),onDestory()等回调方法

Service的生命周期

Service有自己的生命周期,但是特别的是,因为启动服务的方式有两种:startService()和bindService()两种方法,不同方式启动的Service生命周期不太一样

 

服务的创建

Service服务本质上就是一个Service的类,所以无论是New->Service在IDE中快速创建服务,还是手动创建一个Service,都是创建了一个Service的子类

public class MyService extends Service {
   @Override
   public void onCreate() {
       super.onCreate();
       // 初始化代码
  }

   @Override
   public int onStartCommand(Intent intent, int flags, int startId) {
       // 执行任务
       return START_STICKY; // 返回 START_STICKY 表示如果服务被意外终止,系统会尝试重启它
  }

   @Nullable
   @Override
   public IBinder onBind(Intent intent) {
       // 返回一个 IBinder 实例,用于与服务交互
       return null; // 如果不支持绑定,则返回 null
  }

   @Override
   public void onDestroy() {
       super.onDestroy();
       // 清理资源
  }
}

需要注意的是,也需要在全局主配置文件中声明<service>标签

<service android:name=".MyService" 
        android:enbale="true"   //表示系统是否能实例化该服务
        android:exported="true"  //表示该服务是否能被其他应用程序中的组件或者进行交互
        />

服务的启动

上面也有说到,启动的方式有两种startService()和bindService()

startService()

程序中通过startService()方法启动的服务将会在后台长期运行,并且启动服务的组件和服务之间没有关联,即使启动服务的组件被销毁,Service服务只要还没结束就不会被销毁,主要就是通过startService()方法启动服务和stopService()方法停止服务

这种方式一般是通过意图启动服务

//启动服务
public void startService() {
   //通过意图启动服务
   Intent intent = new Intent(this, MyService.class);
   startService(intent);
}

 

bindService()

当一个组件通过bindService()的方式启动Service服务,服务会和组件进行绑定,程序允许组件与服务交互且组件一旦退出/销毁/调用unbindService()方法 ,服务就会进行销毁,多个组件可以绑定一个服务 ,函数原型如下

// 第一个参数service 代表要绑定的service
// 第二个参数用于监听调用者和服务之间的连接状态,是一个服务连接类ServiceConnection对象。当调用者与Service连接成功的时候,会回调调用者的onServiceConnected()方法 ; 当调用者和服务之间的连接断开的时候,就会回调调用者的onServiceDisconnected()方法
// 第三个参数表示组件绑定服务时,如果Service还没被创建,是否自动创建Service,0表示不自动创建,BIND_AUTO_CREATE表示自动创建
bindService(Intent service,ServiceConnection conn , int flag)

这种方式一般是通过服务连接类调用bindService方法

    //服务连接类
   private class MyServiceConnection implements ServiceConnection {
       @Override
       public void onServiceConnected(ComponentName name, IBinder service) {
           //连接服务时为其中的方法赋值
           myBinder = (MyService.MyServiceProxy) service;
            Log.i("MyService", "onServiceConnected连接服务" + service.toString());
      }

       @Override
       public void onServiceDisconnected(ComponentName name) {

      }
  }
   
       //绑定服务
   public void bindMyService() {
       Intent intent = new Intent(this, MyService.class);
       //没有绑定服务时才绑定服务连接
       if (myServiceConnection == null) {
           //创建服务并且绑定
           myServiceConnection = new MyServiceConnection();
           //绑定服务
           bindService(intent, myServiceConnection, BIND_AUTO_CREATE);
           Toast.makeText(this, "绑定服务", Toast.LENGTH_SHORT).show();
           Log.i("MyService", "绑定服务");
           return ;
      }
       Toast.makeText(this, "服务已经连接", Toast.LENGTH_SHORT).show();
  }

 

服务的通信

Service之间的通信分为了本地服务通信和远程服务通信

本地服务通信

本地服务通信是指应用程序内部的通信

这种方式我们其实上面说到了,也就是bindService()启动Service的时候,调用者组件和Service之间是可以通信的。

其主要原因是,在service被bindService()启动之后,onBInd()方法时候会返回一个IBinder对象这个对象也会被传入给我们bindService()的三个参数中的第二个参数即ServiceConnection对象,我们上面的案例代码也可以看到MyServiceConnection实现ServiceConnection接口后重写的onserviceConnected()方法的入参中第二个参数就是IBinder对象

调用者(绑定服务的组件)就会通过IBinder对象和Service进行通信,组件可以通过ServiceConnection对象中的IBinder对象调用service中的方法从而执行对应的操作

private class MyServiceConnection implements ServiceConnection {
   // 第二个参数就是IBinder对象
       @Override
       public void onServiceConnected(ComponentName name, IBinder service) {
           myBinder = (MyService.MyServiceProxy) service;
            Log.i("MyService", "onServiceConnected连接服务" + service.toString());
      }

       @Override
       public void onServiceDisconnected(ComponentName name) {

      }
  }

 

远程服务通信

所谓远程服务通信,就是两个应用程序之间的通讯。Android中每个应用程序本质上就是一个运行在手机上的进程,所以远程服务通信其实就是进程之间的通信,通过AIDL实现

AIDL是一种接口定义语言,语法格式简单,与Java中的接口定义类似,有如下两个特点

Service和Activity的区别

特性 Activity Service
主要用途 提供用户界面(UI),处理用户交互(如点击、滑动)。 在后台执行长时间运行的任务(如下载、音乐播放),无需直接用户交互。
生命周期 受用户操作直接影响(如启动、切换应用、屏幕旋转)。 独立于界面,可长期运行(即使应用退到后台)。
用户可见性 必须在前台显示界面。 默认无界面;可通过前台服务(Foreground Service)在通知栏显示状态。
启动方式 通过 Intent 显式或隐式启动。 通过 startService()(启动服务)或 bindService()(绑定服务)。
交互方式 直接与用户交互(如按钮点击、输入数据)。 通过回调、广播、绑定接口等方式与应用其他组件通信。
资源优先级 高优先级(系统优先保留可见的 Activity)。 默认低优先级;前台服务优先级与 Activity 相当。
典型场景 登录界面、设置页、商品详情页。 后台下载、音乐播放、位置跟踪、数据同步。
销毁条件 用户主动退出、系统回收内存(如应用进入后台)。 启动服务需手动调用 stopSelf()stopService();绑定服务在所有绑定解除后销毁。

网络编程

Android的网络编程个人感觉就是计网+Java Socket编程 + IO知识 + 特殊组件的杂合,有如上知识体系的其实看起来应该不会很难

通过HTTP访问网络

Android对于HTTP通讯就是通过标准的Java的HttpURLConnection实现基于URL的请求和响应

HTTP协议通讯简介

这个考虑到计网大家都学过,书上也没写多少,就简单写几点

HttpURLConnection的使用

先看一段案例代码,应该很熟悉了

URL url = new URL("http://example.com");  // 创建URL对象

// 利用url对象的openConnection()方法获取HttpURLConnection连接对象
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// 设置请求方式 常见的有GET POST DELETE PUT
connection.setRequestMethod("GET");
// 设置响应超时时间
connection.setConnectionTimeout(5000);  

// 获取HTTP响应状态码
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
   // 如果响应为200即成功 则获取输入流 准备将结果读取到内存中
   InputStream inputStream = connection.getInputStream();
   // 处理输入流
}
connection.disconnect();

这里需要注意GET请求和POST请求的区别

//GTE请求,参数信息一般放在URL后面,作为路径参数携带,所以GET请求一般比较小,不超过1024字节
private static void sendGetRequest(String baseUrl) {
       try {
           // 定义参数并编码
           Map<String, String> params = new HashMap<>();
           params.put("name", "John Doe");
           params.put("age", "30");
           
           // 拼接URL和编码后的参数 直接采用URL路径后面拼接的方式
           String queryString = buildQueryString(params);
           URL url = new URL(baseUrl + "?" + queryString);
           
           // 创建连接并设置请求方法
           HttpURLConnection conn = (HttpURLConnection) url.openConnection();
           conn.setRequestMethod("GET");
           conn.setConnectTimeout(5000);
           conn.setReadTimeout(5000);

           // 获取响应
           int responseCode = conn.getResponseCode();
           System.out.println("GET Response Code: " + responseCode);

           // 读取响应内容
           try (BufferedReader in = new BufferedReader(
               new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
               String line;
               StringBuilder response = new StringBuilder();
               while ((line = in.readLine()) != null) {
                   response.append(line);
              }
               System.out.println("GET Response Body: " + response);
          }

           conn.disconnect();

      } catch (Exception e) {
           e.printStackTrace();
      }
  }



// POST请求会将参数信息放到请求体中,以键值对的形式存在,可携带量比GET请求大且相对安全
private static void sendPostRequest(String baseUrl) {
       try {
           // 定义参数并编码
           // 利用map采用key-value的结构进行保存参数
           Map<String, String> params = new HashMap<>();
           params.put("name", "John Doe");
           params.put("age", "30");
           String postData = buildQueryString(params);

           // 创建连接并设置请求方法
           URL url = new URL(baseUrl);
           HttpURLConnection conn = (HttpURLConnection) url.openConnection();
           conn.setRequestMethod("POST");
           conn.setDoOutput(true);
           conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
           conn.setConnectTimeout(5000);
           conn.setReadTimeout(5000);

           // 写入请求体
           try (OutputStream os = conn.getOutputStream()) {
               byte[] input = postData.getBytes(StandardCharsets.UTF_8);
               os.write(input, 0, input.length);
          }

           // 获取响应
           int responseCode = conn.getResponseCode();
           System.out.println("POST Response Code: " + responseCode);

           // 读取响应内容
           try (BufferedReader in = new BufferedReader(
               new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
               String line;
               StringBuilder response = new StringBuilder();
               while ((line = in.readLine()) != null) {
                   response.append(line);
              }
               System.out.println("POST Response Body: " + response);
          }

           conn.disconnect();

      } catch (Exception e) {
           e.printStackTrace();
      }
  }

WebView控件的使用

Android默认提供了内置浏览器,使用的是开源的WebKit协议,可以进行搜索和发送邮件,播放视频等等,对于内置浏览器的使用,配套的存在WebView控件进行操作,该控件可以指定URL,还可以加载HTML代码,且支持JS代码,可以利用WBView控件进行网络编程

和其他的控件一样,既可以在XML布局文件中进行编写也可以通过Java/Kotlin对象的方式创建

<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/webview"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   

</WebView>

常规使用

定义好WebView之后,可以在利用其提供的方法进行网络编程操作,常用的方法如下

方法名称 功能描述
loadUrl(String url) 加载指定的URL对应的页面
loadData(String data,String mimeType,String encoding) 用指定的字符串数据加载到浏览器中
loadDtaWithBaseURL(String baseURL,String data,String mimeType,String encoding,String historyUrl) 基于URL加载指定的数据
capturePicture() 创建当前屏幕的快照
goBack() 用于执行后退操作,相当于浏览器的回退按钮
goForward() 用于执行前进操作,相当于浏览器的前进按钮
stopLoading() 用于停止加载当前页面
reload() 用于刷新当前页面

但是需要注意,Androio默认的初始项目是不允许网络访问的,所以需要我们在AndroidManifest.xml全局配置文件中<application>标签中添加一个属性允许网络访问

<application
       android:usesCleartextTraffic="true"
       .........
       />

展示一下部分方法

// 运行如下代码 我们就能很愉快的在Android Studio中快乐的学习安卓
class MainActivity : ComponentActivity() {
   private val TAG = "MainActivityLifecycle"
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       Log.d(TAG, "onCreate called")
       enableEdgeToEdge()
       setContent {
           KtAndroisTheme {
               Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                   Greeting(
                       name = "Android",
                       modifier = Modifier.padding(innerPadding)
                  )
              }
          }
      }
  }
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
   Column(modifier = modifier) {
       // 新增 XML 布局
       AndroidView(
           factory = { context ->
               // 加载 XML 布局
               val view = View.inflate(context, R.layout.webviewtext, null)
               val activity = context as MainActivity
               val webView = view.findViewById<WebView>(R.id.webview)
               Toast.makeText(activity,"准备进入帅哥の博客站,骚等~",Toast.LENGTH_LONG).show()
               webView.loadUrl("http://8.154.40.120:24180/")
               view
          },
           modifier = Modifier.padding(20.dp)
      )
  }
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
   KtAndroisTheme {
       Greeting("Android")
  }
}

image-20250516230755968

然后是加载本地HTML代码和直接解析HTML代码段

// 这是直接加载HTML代码段
// 运行如下代码 我们就能很愉快的在Android Studio中快乐的学习安卓
class MainActivity : ComponentActivity() {
   private val TAG = "MainActivityLifecycle"
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       Log.d(TAG, "onCreate called")
       enableEdgeToEdge()
       setContent {
           KtAndroisTheme {
               Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                   Greeting(
                       name = "Android",
                       modifier = Modifier.padding(innerPadding)
                  )
              }
          }
      }
  }
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
   Column(modifier = modifier) {
       // 新增 XML 布局
       AndroidView(
           factory = { context ->
               // 加载 XML 布局
               val view = View.inflate(context, R.layout.webviewtext, null)
               val activity = context as MainActivity
               val webView = view.findViewById<WebView>(R.id.webview)
              Toast.makeText(activity,"加载一下我们的HTML代码",Toast.LENGTH_LONG).show()
               val html = "<!DOCTYPE html>\n" +
               "<html lang=\"en\">\n" +
                       "<head>\n" +
                       "   <meta charset=\"UTF-8\">\n" +
                       "   <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
                       "   <title>WebView Study</title>\n" +
                       "</head>\n" +
                       "<body>\n" +
                       "   <h1>这是H1</h1>\n" +
                       "   <h2>这是H2</h2>\n" +
                       "   <div>content</div>\n" +
                       "</body>\n" +
                       "</html>";
               webView.loadData(html, "text/html", "UTF-8");
               view
          },
           modifier = Modifier.padding(20.dp)
      )
  }
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
   KtAndroisTheme {
       Greeting("Android")
  }
}
// 这是直接加载HTML文件 只有Greeting不一样 所以这里只贴这一部分的代码

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
   Column(modifier = modifier) {
       // 新增 XML 布局
       AndroidView(
           factory = { context ->
               // 加载 XML 布局
               val view = View.inflate(context, R.layout.webviewtext, null)
               val activity = context as MainActivity
               val webView = view.findViewById<WebView>(R.id.webview)
              Toast.makeText(activity,"加载一下我们的本地HTML代码",Toast.LENGTH_LONG).show()
              // 查阅资料发现访问本地HTML和HTML文件位置有关 这里就先没有展示了 使用loadUrl方法,传入文件路径即可
               view
          },
           modifier = Modifier.padding(20.dp)
      )
  }
}

image-20250516232410323

支持JS代码

默认情况下,WebView是不支持JS代码的,为了使其支持,需要对webView对象进行一定的配置,这里需要用到WebSettings对象

WebSettings setttings =  webview.getSettings() // 获取webview的WebSettings对象
settings.setJavaScriptEnable() // 设置支持JS代码

这样配置之后,能支持绝大部分的JS代码,但是还有一些类似弹窗JS代码 window.alert()这种还是无法支持的,所以还是需要再加一个配置,这个配置是webview对象进行的配置,而不是webSettings对象

webview.setWebChromeClient(new WebChromeClient());

JSON数据解析

网络传输中的数据传输形式,尤其是HTTP网络传输,数据绝大部分是以JSO的格式进行传输的,JSON数据格式不可直接展示在程序界面上,所以我们需要对JSON格式的数据进行解析从而获得我们需要的样式(考试无关知识:这个过程称之为JSON的反序列化)

如下是一个标准的大部分数据类型都覆盖的JSON格式如下 整体采用 key-value 的形式 key需要打双引发 ,value可以是number数字类型,String字符串类型,array数组类型等等

{
 // 用户信息对象
 "user_info": {
   // 基础信息
   "basic": {
     "username": "Alice",      // 字符串类型 - 用户名
     "age": 28,                // 数字类型 - 年龄
     "is_member": true,        // 布尔类型 - 是否是会员
     "registration_date": "2023-10-01T15:30:00Z"  // 字符串类型 - ISO 8601日期格式
  },
   
   // 地址信息(嵌套对象)
   "address": {
     "street": "123 Tech Valley Rd",  // 字符串类型 - 街道
     "city": "San Francisco",         // 字符串类型 - 城市
     "coordinates": [37.7749, -122.4194]  // 数组类型 - 经纬度坐标(数字)
  },
   
   // 联系方式(混合类型)
   "contact": {
     "email": "alice@example.com",    // 字符串类型 - 邮箱
     "phone_numbers": [               // 数组类型 - 电话号码
       "+1-555-0100",
       "+1-555-0101"
    ],
     "emergency_contact": null       // null类型 - 未设置紧急联系人
  },
   
   // 高级属性
   "metadata": {
     "premium_level": 3,             // 数字类型 - 会员等级
     "subscription_ids": [101, 205, 307],  // 数组类型 - 订阅ID集合
     "preferences": {                // 嵌套对象 - 用户偏好设置
       "theme": "dark",              // 字符串类型 - 主题偏好
       "notifications_enabled": false // 布尔类型 - 是否启用通知
    }
  }
},
 
 // 系统元数据
 "system": {
   "api_version": 2.1,      // 浮点数类型 - API版本号
   "environment": "production" // 字符串类型 - 运行环境
}
}

对于JSON数据的解析,市面上已经有了很多方案了,例如Java自带的JSONObject和JSONArray ; 阿里开源的Fast-Json 和 Fast-Json2 ;谷歌开源的Gson等等,书上主要讲的是Java自带的JSONObject和JSONArray 以及谷歌开源的Gson

直接看代码吧

/*
分别采用Java自带的方式 和 GSON 统一对 数据 [{\"name\": \"lisi\", \"age\": 25}, {\"name\": \"jason\", \"age\": 20}] 进行解析
*/

// Java自带方式
public class OrgJsonExample {
   public static void main(String[] args) {
       String jsonStr = "[{\"name\": \"lisi\", \"age\": 25}, {\"name\": \"jason\", \"age\": 20}]";

       try {
           // 将其解析为两个JSON数组格式 即{\"name\": \"lisi\", \"age\": 25} 和 {\"name\": \"jason\", \"age\": 20}
           JSONArray jsonArray = new JSONArray(jsonStr);
           // 对上述两个Json分别解析
           for (int i = 0; i < jsonArray.length(); i++) {
               JSONObject personObj = jsonArray.getJSONObject(i); // 拿出第i个对象
               String name = personObj.getString("name"); // getString(key)和getInt(key)通过key获取对应的value
               int age = personObj.getInt("age");
               System.out.println("Name: " + name + ", Age: " + age);
          }
      } catch (Exception e) {
           System.err.println("解析失败: " + e.getMessage());
      }
  }
}

//GSON方式

// 将json封装为一个对象 这个是GSON库必须要求的
public class Person {
   private String name;
   private int age;

   // 必须有无参构造方法
   public Person() {}

   // Getter/Setter 方法(或使用 Lombok)
   public String getName() { return name; }
   public void setName(String name) { this.name = name; }
   public int getAge() { return age; }
   public void setAge(int age) { this.age = age; }
}

public class GsonExample {
   public static void main(String[] args) {
       String jsonStr = "[{\"name\": \"lisi\", \"age\": 25}, {\"name\": \"jason\", \"age\": 20}]";
       Gson gson = new Gson();

       try {
           // 直接将JSON对应的类封装,fromJson第一个参数为要解析的JSON数据,第二个为内部定义的一个TypeToken对象,需要一个泛型,泛型即JSON对应的类对象类型
           List<Person> people = gson.fromJson(jsonStr, new TypeToken<List<Person>>(){}.getType());
           for (Person person : people) {
               System.out.println("Name: " + person.getName() + ", Age: " + person.getAge());
          }
      } catch (Exception e) {
           System.err.println("解析失败: " + e.getMessage());
      }
  }
}

Handler机制

在Android中,应用程序启动的时候,主程序其实就是UI程序,负责管理UI界面中的程序和事件分发,例如当我们界面上有一个Button按钮的时候,点击发送一个网络IO事件等耗时操作,将操作结果回显到UI界面上,这样其实会导致UI界面必须等着操作完成,程序阻塞出现假死现象,在正常的web服务中,我们可以开一个子线程完成这个事情,但是在Android中是不允许的,因为Androdi的UI更新操作只能由主线程执行

所以这里需要一个回调机制,子线程等待耗时操作完成,完成之后回调告知主线程,从而委托主线程完成UI的更新,Android提供了这种回调机制即Handler

Handler中包括四个主要的对象:Message,Handler,MessageQueue,Looper

可以将将Handler相关的原理机制形象的描述为以下情景:
Handler 快递员兼配送员 1. 接收用户寄件请求(发送消息) 2. 将包裹派送到目的地(处理消息)
Message 包裹 包含寄件人信息(目标 Handler)、内容(数据)和标签(what、arg 等)
MessageQueue 传送带 按顺序存放包裹(消息队列),先进先出
Looper 传送带操作员 持续从传送带上取包裹,并分发给对应的配送员(Handler)处理
Thread 快递公司分站点 主线程站点默认有操作员(Looper)和传送带;子线程站点需手动配置操作员和传送带



子线程更新主线程 UI
用户寄件(子线程发送消息)

你在子线程(分站点 A)想更新主线程(分站点 B)的 UI。

你打包一个 Message(包裹),填写收件地址为主线程的 Handler,并放入传送带(MessageQueue)。

传送带运作(Looper 循环处理)

主线程的传送带操作员(Looper)不断检查传送带(MessageQueue)。

发现新包裹后,操作员将其交给收件地址指定的配送员(主线程的 Handler)。

包裹派送(消息处理)

配送员(Handler)拆开包裹(解析 Message),根据内容更新 UI(如刷新 TextView)。

image-20250517001917003

Android Java Kotlin 期末复习 编程