本人想把Android所要运用到的所有的东西都好好的梳理一遍,那今个儿就和大伙说说消息推送;想必大家都玩过小米手机,细心的人会发现,小米手机时不时的就会发一些通知告诉你要系统升级了、某某App有新版本了等等,你说他们是咋弄的呢?其实呀,往简单点说,他们运用的就是消息推送,当然,他们那种也许会很复杂,但下面所和大家探索的消息推送已经够我们用的了!!

那么,废话就不多说了,接下来就和大家一起探索android开发之消息推送!

第一步:下载SDK

下载网址:http://docs.jpush.io/guideline/android_guide/

下载完成并解压之后,你会看到如下图所示:

说明:doc是说明文档、example是示例代码、libs是工具包、AndroidManifest.xml是示例配置文件,在该文件中详细配置了如何在应用中集成JPush的配置方式、ChangeLog.txt是更新日志。

第二步:注册

登录Jpush官网注册用户,申请AppKey:

第三步:集成项目

1、添加jar包和so动态库

  • 解压缩 jpush-sdk_v1.x.y.zip 集成压缩包
  • 复制 libs/jpush-sdk-release1.x.y.jar 到工程 libs/ 目录下
  • 复制 libs/armeabi/libjpush.so 到工程 libs/armeabi 目录下
  • 如果您的项目有 libs/armeabi-v7a 这个目录,请把 libjpush.so 也复制一份到这个目录。
  • 如果在libs文件夹下面没有armeabi和armeabi-v7a这两个文件夹怎么办?自己创建同名的文件夹就可以了,然后再把jar和so动态库复制过来就可以了。
  • 如果你使用的是idea工具开发的话,导入so库,需要这样:
    1、打开项目下的build.gradle

2、配置 AndroidManifest.xml

根据 SDK 压缩包里的 AndroidManifest.xml 样例文件,来配置应用程序项目的 AndroidManifest.xml 。

主要步骤为:

  • 复制备注为 “Required” 的部分
  • 将备注为替换包名的部分,替换为当前应用程序的包名
  • 将AppKey替换为在Portal上注册该应用的的Key,例如(9fed5bcb7b9b87413678c407)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="您应用的包名"android:versionCode="173"android:versionName="1.7.3"><uses-sdk android:minSdkVersion="7" android:targetSdkVersion="17" /><!-- Required 自定义用来收发消息的相关权限 -->               <permission
        android:name="您应用的包名.permission.JPUSH_MESSAGE"android:protectionLevel="signature" /><!-- Required  一些系统要求的权限,如访问网络等--><uses-permission android:name="您应用的包名.permission.JPUSH_MESSAGE" /><uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" /><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.WAKE_LOCK" /><uses-permission android:name="android.permission.READ_PHONE_STATE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.VIBRATE" /><uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>  <uses-permission android:name="android.permission.WRITE_SETTINGS" /><!-- Optional for location --><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" /><uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /><application
        android:icon="@drawable/ic_launcher"android:label="@string/app_name"><!-- Required SDK核心功能--><activity
            android:name="cn.jpush.android.ui.PushActivity"android:theme="@android:style/Theme.Translucent.NoTitleBar"android:configChanges="orientation|keyboardHidden" ><intent-filter><action android:name="cn.jpush.android.ui.PushActivity" /><category android:name="android.intent.category.DEFAULT" /><category android:name="您应用的包名" /></intent-filter></activity><!-- Required  SDK核心功能--><service
            android:name="cn.jpush.android.service.DownloadService"android:enabled="true"android:exported="false" ></service><!-- Required SDK 核心功能--><service
            android:name="cn.jpush.android.service.PushService"android:enabled="true"android:exported="false"><intent-filter><action android:name="cn.jpush.android.intent.REGISTER" /><action android:name="cn.jpush.android.intent.REPORT" /><action android:name="cn.jpush.android.intent.PushService" /><action android:name="cn.jpush.android.intent.PUSH_TIME" /></intent-filter></service><!-- Required SDK核心功能--><receiver
            android:name="cn.jpush.android.service.PushReceiver"android:enabled="true" ><intent-filter android:priority="1000"><action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_PROXY" />   <!--Required  显示通知栏 --><category android:name="您应用的包名" /></intent-filter><intent-filter><action android:name="android.intent.action.USER_PRESENT" /><action android:name="android.net.conn.CONNECTIVITY_CHANGE" /></intent-filter><!-- Optional --><intent-filter><action android:name="android.intent.action.PACKAGE_ADDED" /><action android:name="android.intent.action.PACKAGE_REMOVED" /><data android:scheme="package" /></intent-filter></receiver><!-- Required SDK核心功能--><receiver android:name="cn.jpush.android.service.AlarmReceiver" /><!-- User defined.    用户自定义的广播接收器--><receiver
            android:name="您自己定义的Receiver"android:enabled="true"><intent-filter><action android:name="cn.jpush.android.intent.REGISTRATION" /> <!--Required  用户注册SDK的intent--><action android:name="cn.jpush.android.intent.UNREGISTRATION" />  <action android:name="cn.jpush.android.intent.MESSAGE_RECEIVED" /> <!--Required  用户接收SDK消息的intent--><action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED" /> <!--Required  用户接收SDK通知栏信息的intent--><action android:name="cn.jpush.android.intent.NOTIFICATION_OPENED" /> <!--Required  用户打开自定义通知栏的intent--><action android:name="cn.jpush.android.intent.CONNECTION" /><!-- 接收网络变化 连接/断开 since 1.6.3 --><category android:name="您应用的包名" /></intent-filter></receiver><!-- Required  . Enable it you can get statistics data with channel --><meta-data android:name="JPUSH_CHANNEL" android:value="developer-default"/><meta-data android:name="JPUSH_APPKEY" android:value="您的Appkey" /> <!--  </>值来自开发者平台取得的AppKey--></application>
</manifest>

上面配置文件中详细说明了如何进行配置,”您的Appkey” “您应用的包名” “您自己定义的Receiver” 出现这三个字段的地方均需要开发者根据自己的应用进行响应的替换。”Required”标识的地方是必须要在配置文件中配置的。

3、开发自定义广播接收器

配置文件中有一个广播接收器,什么作用呢?主要就是接收极光推送服务器发送的广播。

那么,接下来让我们一起登录官网阅读文档:http://docs.jpush.io/client/android_api/#receiver

这个是极光推送官网对于这个Receiver的功能的定义。如果开发者不进行这个广播接收的定义,它的功能是固定的。当开发者对于该广播接收者进行定义它的功能就很灵活了。当接收到推送推送的消息之后,让客户端如何进行响应,完全由开发者自己决定。
至于这个Receiver的配置已经固定,开发者只需要实现功能即可。

每个 Receiver action 详细解释:

自定义的Receiver的代码如下:

/*** Created by ZaneLove on 2015/3/16.* 自定义接收器* 如果不定义这个 Receiver,则:* 1) 默认用户会打开主界面* 2) 接收不到自定义消息*/
public class MyReceiver extends BroadcastReceiver {private static final String TAG = "JPush";@Overridepublic void onReceive(Context context, Intent intent) {Bundle bundle = intent.getExtras();
//        Log.e(TAG, "[MyReceiver] onReceive - " + intent.getAction() + ", extras: " + printBundle(bundle));if (JPushInterface.ACTION_REGISTRATION_ID.equals(intent.getAction())) {String regId = bundle.getString(JPushInterface.EXTRA_REGISTRATION_ID);Log.e(TAG, "[MyReceiver] 接收Registration Id : " + regId);} else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) {Log.e(TAG, "[MyReceiver] 接收到推送下来的自定义消息: " + bundle.getString(JPushInterface.EXTRA_MESSAGE));processCustomMessage(context, bundle);} else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) {Log.e(TAG, "[MyReceiver] 接收到推送下来的通知");int notifactionId = bundle.getInt(JPushInterface.EXTRA_NOTIFICATION_ID);Log.e(TAG, "[MyReceiver] 接收到推送下来的通知的ID: " + notifactionId);} else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) {Log.e(TAG, "[MyReceiver] 用户点击打开了通知");//打开自定义的Activity,当然这个并不是强制要求的,当用户点击推送消息是,可以不做处理的Intent i = new Intent(context, TestActivity.class);i.putExtras(bundle);i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP );context.startActivity(i);} else if (JPushInterface.ACTION_RICHPUSH_CALLBACK.equals(intent.getAction())) {Log.e(TAG, "[MyReceiver] 用户收到到RICH PUSH CALLBACK: " + bundle.getString(JPushInterface.EXTRA_EXTRA));//在这里根据 JPushInterface.EXTRA_EXTRA 的内容处理代码,比如打开新的Activity, 打开一个网页等..} else if(JPushInterface.ACTION_CONNECTION_CHANGE.equals(intent.getAction())) {boolean connected = intent.getBooleanExtra(JPushInterface.EXTRA_CONNECTION_CHANGE, false);Log.w(TAG, "[MyReceiver]" + intent.getAction() +" connected state change to "+connected);//这个获取的是当前登录用户的连接状态。这里的连接状态是指用户与Jpush推送服务器的连接状态。} else {Log.e(TAG, "[MyReceiver] Unhandled intent - " + intent.getAction());}}// 打印所有的 intent extra 数据 这里只是打印获取的数据的方法private static String printBundle(Bundle bundle) {StringBuilder sb = new StringBuilder();for (String key : bundle.keySet()) {if (key.equals(JPushInterface.EXTRA_NOTIFICATION_ID)) {sb.append("\nkey:" + key + ", value:" + bundle.getInt(key));}else if(key.equals(JPushInterface.EXTRA_CONNECTION_CHANGE)){sb.append("\nkey:" + key + ", value:" + bundle.getBoolean(key));}else {sb.append("\nkey:" + key + ", value:" + bundle.getString(key));}}return sb.toString();}/***send msg to MainActivity*这个方法是获取到推送的消息之后再次发送广播到其他的广播接收器当中,*目的就是传递获取的广播数据,进行相应的处理,实现相应的功能,开发者在这里*完全可以自己实现自己想要的功能。不一定非要使用这种方式。**/private void processCustomMessage(Context context, Bundle bundle) {/*if (MainActivity.isForeground) {String message = bundle.getString(JPushInterface.EXTRA_MESSAGE);String extras = bundle.getString(JPushInterface.EXTRA_EXTRA);Intent msgIntent = new Intent(MainActivity.MESSAGE_RECEIVED_ACTION);msgIntent.putExtra(MainActivity.KEY_MESSAGE, message);if (!ExampleUtil.isEmpty(extras)) {try {JSONObject extraJson = new JSONObject(extras);if (null != extraJson && extraJson.length() > 0) {msgIntent.putExtra(MainActivity.KEY_EXTRAS, extras);}} catch (JSONException e) {}}context.sendBroadcast(msgIntent);//发送广播}*/}
}

4、设置别名和标签

登录:http://docs.jpush.io/client/android_api/#api_1,先看看Jpush官网对于别名和标签的描述:

其实说白了,我的理解就是让jpush官网的消息推送服务器知道它要把消息推送给哪一个或者哪一些人手机app中。极光推送服务器就是通过别名和标签区别每一个使用开发者开发的app的用户的。有了这样的区别或者说是标识,激光推送就可以定向发送,甚至也可以批量发送。
理解了这一点,那么就可以着手设置别名和标签了。

public static void setAliasAndTags(Context context,String alias,Set tags,TagAliasCallback callback)
这个接口同时设置别名和标签。public static void setAlias(Context context, String alias, TagAliasCallback callback)
这个接口用于设置别名。public static void setTags(Context context, Set tags, TagAliasCallback callback)
这个接口用于设置标签。 这三个接口都是jpush的工具包中的JPushInterface类下的静态方法,可以直接调用。

官网示例代码:

//这行代码即实现了设置别名和标签。由于别名和标签都是用于给
//Jpush服务器区分用户的,不一定非要同时设置别名和标签。也就是说
//只设置了别名也是可以的。并且也不要求别名和标签的唯一性。
//开发者可以根据自己的需要进行设置。
//例如 我只设置别名,并且别名是唯一的,只用给jpush服务器给哪些别名发送推送就可以了。
//或者说设置了别名并且唯一,但是根据标签进行了分组,同一个标签下面包含了多个别名,按照标签就可以实现群发功能了。
JPushInterface.setAliasAndTags(getApplicationContext(), “aliasAndTag”, null, mAliasCallback);/**
*TagAliasCallback类是JPush开发包jar中的类,用于
*设置别名和标签的回调接口,成功与否都会回调该方法
*同时给定回调的代码。如果code=0,说明别名设置成功。
**/
private final TagAliasCallback mAliasCallback = new TagAliasCallback() {@Overridepublic void gotResult(int code, String alias, Set<String> tags) {String logs ;switch (code) {case 0:logs = "Set tag and alias success";Log.i(TAG, logs);break;case 6002:logs = "Failed to set alias and tags due to timeout. Try again after 60s.";Log.i(TAG, logs);if (ExampleUtil.isConnected(getApplicationContext())) {mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_ALIAS, alias), 1000 * 60);} else {Log.i(TAG, "No network");}break;default:logs = "Failed with errorCode = " + code;Log.e(TAG, logs);}ExampleUtil.showToast(logs, getApplicationContext());}
};

那么,问题来了?何时设置别名呢?

如果设置别名或者标签,并且想要设置唯一性,可以在使用app的用户成功登录之后进行设置。此时有关用户的登录名已经有了,可以给定用户的登录名或者userId这样具有唯一性的标识作为别名或者标签。
JPush官网并没有强制别名的设置时机,所以开发者根据需要进行设置即可。在本人开发的应用当中,是在用户自己根据用户名和密码成功登录之后,从后台获取到用户的信息之后,用userId来设置别名的。因为userId每个用户是不同的,具备唯一性,即使是群发,那么可以从app把要发送的用户的userId发送给后台,后台再把群发的userId提交给jpush服务器,然后jpush服务器向app推送消息。这样就完成了消息的推送,并且只推送到想要发送的用户的app端。经过这样的流程,想必各位已经知道了别名的用途了。

因此,假设MainActivity 就是启动之后的主界面,代码如下:

package com.zanelove.jpushdemo.jpush;import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Window;
import android.widget.Toast;
import cn.jpush.android.api.JPushInterface;
import cn.jpush.android.api.TagAliasCallback;import java.util.Set;public class MainActivity extends Activity {private static final int  GET_MSG_SUC = 0;private String TAG = MainActivity.class.getName();private static final int MSG_SET_ALIAS = 1001;private Handler handler = new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case GET_MSG_SUC:/***这里设置了别名,是因为了我开发的app中,是在这里获取的用户登录的信息*并且此时已经获取了用户的userId,然后就可以用用户的userId来设置别名了*/Log.e(TAG, "触发广播,接收到广播信息,说明必备数据获取成功");setAlias(19940316);//设置极光推送的别名break;}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {requestWindowFeature(Window.FEATURE_NO_TITLE);super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView() {//判断当前手机网络状态的工具类,没什么特殊的,网上很多,就不详细写了if(!(0==0)){//友好提示}else {// JPush初始化JPushInterface.setDebugMode(false); // 设置true 开启日志,发布时需关闭日志false即可JPushInterface.init(this); // 初始化 Jpush 固定的方法,JPushInterface类中的静态方法Message message = Message.obtain();message.what = 0;handler.sendMessage(message);}}@Overrideprotected void onPause() {JPushInterface.onPause(MainActivity.this);super.onPause();}@Overrideprotected void onResume() {
//      极光推送服务会恢复正常工作JPushInterface.onResume(MainActivity.this);super.onResume();}/*** Jpush设置*/private void setAlias(int userId) {String alias = String.valueOf(userId);// 调用JPush API设置AliasmHandler2.sendMessage(mHandler2.obtainMessage(MSG_SET_ALIAS, alias));Log.d(TAG, "设置Jpush推送的别名alias=" + alias);}@SuppressLint("HandlerLeak")private final Handler mHandler2 = new Handler() {//专门用了一个Handler对象处理别名的注册问题@Overridepublic void handleMessage(android.os.Message msg) {super.handleMessage(msg);Log.d(TAG, "设置激光推送的别名-mHandler2");JPushInterface.setAliasAndTags(getApplicationContext(), (String) msg.obj, null, mAliasCallback);Toast.makeText(MainActivity.this, "成功", Toast.LENGTH_SHORT).show();}};private final TagAliasCallback mAliasCallback = new TagAliasCallback() {@Overridepublic void gotResult(int code, String alias, Set<String> tags) {String logs;switch (code) {case 0:logs = "Set tag and alias success极光推送别名设置成功";Log.e(TAG, logs);break;case 6002:logs = "Failed to set alias and tags due to timeout. Try again after 60s.极光推送别名设置失败,60秒后重试";Log.e(TAG, logs);mHandler2.sendMessageDelayed(mHandler2.obtainMessage(MSG_SET_ALIAS, alias), 1000 * 60);break;default:logs = "极光推送设置失败,Failed with errorCode = " + code;Log.e(TAG, logs);break;}}};
}

这里只是示例了别名设置的时机和别名的注册。是不是很简单?确实不难。可能有人会问怎么没有Receiver广播接收器?这个开发者自定义的广播接收器我们已经在配置文件中配置好了是不是?广播接收器有两种配置方式,一种是在配置文件中进行配置,另一种实在代码中进行注册,并在onDestory中注销。但是jpush给定的AndroidManifest.xml示例中已经对开发者自定义的广播接收器进行了配置,所以在MainActivity中并没有对并广播接收器的注册。

当jpush服务器推送消息到客户端app,配置好的广播接收器会自动的响应,执行onReceive方法,根据类型进行处理。

至此全部完成了对jpush消息推送的处理工作了,当然,jpush的功能还有很多,例如统计分析 API、清除通知 API、
设置允许推送时间 API、设置通知静默时间 API、通知栏样式定制 API、设置保留最近通知条数 API、富文本页面 Javascript 回调API、本地通知API等这些功能开发者都可以进行设置,把这些功能添加到自己的应用当中。
关于这些功能,请参考jpush官网api:http://docs.jpush.io/client/android_api/#api_8

附带:当用户点击通知状态栏之后,发送的广播,并会跳转到指定的Activity中:

/*** Created by ZaneLove on 2015/3/16.*/
public class TestActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.test_layout);}
}

示例代码戳Here

友盟消息推送:http://blog.csdn.net/Z18789231876/article/details/42494065
XMPP消息推送:http://blog.csdn.net/menxu_work/article/details/9409555

android开发之消息推送相关推荐

  1. Android中的消息推送

    转载于Android中的消息推送 前段时间做了一个应用,需要用到服务器端向Android或者是Iphone终端主动发送命令.随后客户端做出相应的反应.当时没有找到最佳的方案,一直搁置着.今天看到网上有 ...

  2. android 消息推送方法,一种基于Android系统的消息推送方法技术方案

    [技术实现步骤摘要] 本专利技术涉及一种基于Android系统的消息推送方法,属于计算机 技术介绍 推送功能在手机应用开发中越来越重要,已经成为手机开发的必选项.消息推送,就是在互联网上通过定期传送用 ...

  3. iOS开发之消息推送 —— 远程消息推送入门

    每次看到iOS的远程消息推送,总是感觉很头大,即便后来项目都做完了,还是觉得摸不着远程推送的脉门,网上介绍的资料虽多,但不是写的太简单了,就是写的太详细了,不能一下抓住要点,今天终于能够抽出点时间,来 ...

  4. 浅谈iOS和Android后台实时消息推送的原理和区别

    http://www.52im.net/thread-286-1-1.html 前言 iOS和Android上的实时消息推送差异很大,往小了说是技术实现的差异,往大了说是系统实现理念的不同.实时消息推 ...

  5. 小程序云开发之消息推送功能

    小程序云开发之消息推送功能(图文) 一:新建项目 APPID获取方法:1.在微信公众平台上注册账号,选择小程序(也可以从服务号注册,前提你有一个服务号)注册后登录,登录时微信扫码验证一下 2.填写小程 ...

  6. Android开发之第三方推送JPush极光推送知识点详解 学会集成第三方SDK推送

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 下面是一些知识点介绍,后期将会带领大家进行代码实战: 一.Android实现推送方式解决方案: 1.推 ...

  7. Android接入极光消息推送

    极光消息推送: 极光推送(JPush)是一个端到端的推送服务,使得服务器端消息能够及时地推送到终端用户手机上,让开发者积极地保持与用户的连接 主要功能 保持与服务器的长连接,以便消息能够即时推送到达客 ...

  8. Android MQTT实现消息推送

    飞哥语录:编程说白了就是发送数据,接收数据,处理数据. 1.概述 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有 ...

  9. android系统下消息推送机制

    一.推送方式简介: 当前随着移动互联网的不断加速,消息推送的功能越来越普遍,不仅仅是应用在邮件推送上了,更多的体现在手机的APP上.当我们开发需要和服务器交互的应用程序时,基本上都需要获取服务器端的数 ...

最新文章

  1. 《构建之法》读书笔记
  2. Caffe部署中的几个train-test-solver-prototxt-deploy等说明三
  3. lamp环境搭建经验总结
  4. Gtest在vs 2010上的配置
  5. python吧_python初始与安装 - Python东
  6. 一文带你入门目前大热的图神经网络
  7. 记录——《C Primer Plus (第五版)》第十章编程练习第五题
  8. 基于iOS用CoreImage实现人脸识别
  9. web前端开发视频教程完整54讲下载
  10. 计算机图形学课本pdf,计算机图形学教材.pdf
  11. 灵思致远测色仪色差宝和颜色识别器APP介绍
  12. 实验:IP 与 ICMP 分析
  13. 西数MyBookDuo提供致臻性能、超大容量及综合数据保护
  14. 三维绕任意轴旋转矩阵
  15. 一个创业者的自白:假如重回华为怎么做?
  16. 微信小程序 data命名不能大写
  17. Java基础_集合_List与Set集合(笔记)
  18. 渗透测试工具网址--自用
  19. ffmpeg mplayer x264 代码重点详解 详细分析
  20. 冯诺依曼体系结构计算机

热门文章

  1. 一加6t android,一加6T使用体验:可能是最流畅的安卓旗舰
  2. 创富路径---李嘉诚--王永庆--刘永好
  3. 大白话讲解Promise(一)
  4. 斗米App首次分享:召回沉默用户的技巧和思考(附PDF)
  5. iPad协议-小程序code非常稳定
  6. oracle 表分区含义和使用场合,表分区的方式
  7. 29.typedef
  8. ggplot作图设置双坐标轴-各行其是
  9. 2023 华为 Datacom-HCIE 真题题库 04/12--含解析
  10. 计算机视觉学习资料汇总