使用Android Studio 开发桌面小部件,闲来无事,自己动手做一个

AppWidget是应用程序窗口小部件(Widget)是微型的应用程序视图

官方文档链接:http://www.android-doc.com/guide/topics/appwidgets/index.html

目录结构(不是桌面小部件开发步骤)

  1. 通过Android Studio 创建Widget
  2. PendingIntent 详解
  3. Activity 显式 隐式 启动
  4. 播放器 小部件demo实现

1,通过Android Studio 创建Widget

此处可参考:https://blog.csdn.net/lgj860123/article/details/79219973

简单讲下步骤,先【创建Android 项目】,在项目【src-main 具体包名】 右键 new -> Widget 选中 然后配置一下具体参数,完成即可。最主要是桌面程序大小的选择,后面会讲到。

创建完后会自动生成以下文件

a,继承 AppWidgetProvider 的类 (AppWidget),创建时自己命名。 看看AppWidgetProvider的源码,或者跳过源码直接看分析解释


public class AppWidgetProvider extends BroadcastReceiver {public AppWidgetProvider() {}// BEGIN_INCLUDE(onReceive)public void onReceive(Context context, Intent intent) {// Protect against rogue update broadcasts (not really a security issue,// just filter bad broacasts out so subclasses are less likely to crash).String action = intent.getAction();if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {Bundle extras = intent.getExtras();if (extras != null) {int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);if (appWidgetIds != null && appWidgetIds.length > 0) {this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);}}} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {Bundle extras = intent.getExtras();if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);this.onDeleted(context, new int[] { appWidgetId });}} else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {Bundle extras = intent.getExtras();if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)&& extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),appWidgetId, widgetExtras);}} else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {this.onEnabled(context);} else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {this.onDisabled(context);} else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {Bundle extras = intent.getExtras();if (extras != null) {int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);if (oldIds != null && oldIds.length > 0) {this.onRestored(context, oldIds, newIds);this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);}}}}// END_INCLUDE(onReceive)public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {}public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,int appWidgetId, Bundle newOptions) {}public void onDeleted(Context context, int[] appWidgetIds) {}public void onEnabled(Context context) {}public void onDisabled(Context context) {}public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {}
}

可以看到,实际上是一个BroadcastReceiver(广播接收器),在onReceive()方法中接收桌面Widget发生变化时,系统发出的一系列广播,然后进行分类处理。最主要是下面五个方法:

  • onUpdate()  当 widget 更新时触发,用户首次添加时也会被调用,但是如果用户定义了widget的configure属性,首次添加时不会触发,而是直接跳转
  • onAppWidgetOptionsChanged()  当widget首次添加,或者大小被改变时触发
  • onEnabled()  当第一个Widget的实例被创建时触发。也就是说,如果用户对同一个Widget增加了两次(两个实例),那么onEnabled()只会在第一次增加Widget时被调用
  • onDisabled()  类似onEnabled()方法,不过是在最后一个Widget被删除时调用
  • onDeleted()  当Widget被删除时调用

b,【res】目录下<xml>文件夹,会生成一个 app_widget_info.xml

此处可参考:https://blog.csdn.net/scry5566/article/details/37692995

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"android:initialKeyguardLayout="@layout/app_widget"android:initialLayout="@layout/app_widget"android:minHeight="40dp"android:minWidth="250dp"android:previewImage="@drawable/example_appwidget_preview"android:resizeMode="horizontal|vertical"android:updatePeriodMillis="86400000"android:widgetCategory="keyguard|home_screen"></appwidget-provider>

该文件描述一个App Widget的元数据,比如布局,更新频率,大小等。后面需要在manifest中注册,下面介绍下属性:

  • initialLayout: 指向 Widget 的布局资源文件
  • initialKeyguardLayout: 指向 Widget 位于lockscreen中的布局资源文件,Android 4.2 引入
  • widgetCategory: 指定了 widget 能显示的地方:能否显示在 home Screen 或 lock screen 或 两者都可以。它的取值包括:"home_screen" 和 "keyguard"。Android 4.2 引入。
  • minWidth 和 minHeight: 它们指定了App Widget布局需要的最小区域。缺省的App Widgets所在窗口的桌面位置基于有确定高度和宽度的单元网格中。如果App Widget的最小长度或宽度和这些网格单元的尺寸不匹配,那么这个App Widget将上舍入(上舍入即取比该值大的最接近的整数——译者注)到最接近的单元尺寸
  • previewImage: 指定预览图,该预览图在用户选择 widget 时出现,如果没有提供,则会显示应用的图标。该字段对应在 AndroidManifest.xml 中 receiver 的 android:previewImage 字段。由 Android 3.0 引入
  • resizeMode:  指定了 widget 的调整尺寸的规则。可取的值有: "horizontal", "vertical", "none"。"horizontal"意味着widget可以水平拉伸,“vertical”意味着widget可以竖值拉伸,“none”意味着widget不能拉伸;默认值是"none"。Android 3.1 引入
  • updatePeriodMillis:  它定义了 widget 的更新频率。实际的更新时机不一定是精确的按照这个时间发生的。建议更新尽量不要太频繁,最好是低于1小时一次。 或者可以在配置 Activity 里面供用户对更新频率进行配置
  • configure 可选属性,定义了 widget 的配置 Activity。如果定义了该项,那么当 widget 创建时,会自动启动该 Activity
  • autoAdvanceViewId: 指定一个子view ID,表明该子 view 会自动更新。在 Android 3.0 中引入

c,App Widget的布局资源文件 app_widget.xml

在【res】目录下 layout 文件夹下会自动生成一个 布局文件(对应上述引用的布局资源文件),也就是widget的界面。简单做了个播放器的小部件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center"android:background="@drawable/arc_bg"android:gravity="center"android:orientation="vertical"android:padding="@dimen/widget_margin"><TextViewandroid:id="@+id/widget_title"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:contentDescription="@string/app_name"android:gravity="center"android:text="电台桌面小部件"android:textColor="#0997f0"android:textSize="22sp" /><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="2"><ImageViewandroid:id="@+id/widget_last"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginEnd="80dp"android:layout_toLeftOf="@+id/widget_pause"android:background="@drawable/btn_player_previous_selector" /><ImageViewandroid:id="@+id/widget_pause"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:background="@drawable/btn_player_pause_selector" /><ImageViewandroid:id="@+id/widget_next"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginStart="80dp"android:layout_toRightOf="@id/widget_pause"android:background="@drawable/btn_player_next_selector" /></RelativeLayout></LinearLayout>

注意,Widget并不支持所有的控件跟布局,而仅仅只是支持Android布局和控件的一个子集

  1. 支持布局:FrameLayout,LinearLayout,RelativeLayout,GridLayout
  2. 支持控件:AnalogClock,Button,Chronometer,ImageButton,ImageView,ProgressBar,TextView,ViewFlipper,ListView,GridView,StackView,AdapterViewFlipper

d,AndroidManifest 中注册

最后,会自动在manifest中生成 注册广播的代码

<receiver android:name=".widget.AppWidget"><intent-filter><action android:name="android.appwidget.action.APPWIDGET_UPDATE" /><action android:name="com.widget.STATUS_CHANGED"/></intent-filter><meta-dataandroid:name="android.appwidget.provider"android:resource="@xml/app_widget_info" /></receiver>

到此通过Android Studio 自动生成的代码基本就这些,一个简单的widget就做好了,在手机桌面长按空白处,添加小部件,就能找到该小部件了。

2,PendingIntent 详解

因为用到,所以记录,不要问我为什么。此处可参考:https://www.jianshu.com/p/4a8fc0b78094

PendingIntent 是 Android 提供的一种用于外部程序调起自身程序的能力,生命周期不与主程序相关。

1,外部程序通过 PendingIntent 只能调用起三种组件

  • Activity
  • Service
  • Broadcast

2,使用场景

  • 使用 AlarmManager 设定闹钟
  • 在系统状态栏显示 Notification
  • 在桌面显示 Widget

3,获取方式

// 获取 Broadcast 关联的 PendingIntent
PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags)// 获取 Activity 关联的 PendingIntent
PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)
PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags, Bundle options)// 获取 Service 关联的 PendingIntent
PendingIntent.getService(Context context, int requestCode, Intent intent, int flags)

4,参数意义

这里主要说明 PendingIntent.getXXX() 第四个参数 flags

//如果新请求的 PendingIntent 发现已经存在时,取消已存在的,用新的 PendingIntent 替换
int FLAG_CANCEL_CURRENT//如果新请求的 PendingIntent 发现已经存在时,忽略新请求的,继续使用已存在的。日常开发中很少使用
int FLAG_NO_CREATE//表示 PendingIntent 只能使用一次,如果已使用过,那么 getXXX(...) 将会返回 NULL
//也就是说同类的通知只能使用一次,后续的通知单击后无法打开。
int FLAG_ONE_SHOT//如果新请求的 PendingIntent 发现已经存在时, 如果 Intent 有字段改变了,这更新已存在的 PendingIntent
int FLAG_UPDATE_CURRENT

3,Activity 显式 隐式 启动

  • 显式启动

1,Activity 直接跳转(应用内部直接跳转)

Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.putExtra("key","value");//传递参数
startActivity(intent);

2,通过包名,类名(应用内部+应用外部)

Intent intent = new Intent();
//通过包名,类名进行跳转
intent.setClassName("com.example.mvptest","com.example.mvptest.MainActivity");
intent.putExtra("key","value");//传递参数
startActivity(intent);

3,ComponentName跳转(应用内部+应用外部)

Intent intent = new Intent();
intent.setComponent(newComponentName("com.example.mvptest","com.example.mvptest.MainActivity"));
intent.putExtra("key","value");//传递参数
startActivity(intent);
  • 隐式启动

在AndroidManifest中定义某个Activity的 intent-filter

        <activity android:name=".widget.WidgetActivity"><intent-filter><action android:name="com.example.widget" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></activity>

在Intent中通过 设置 action,添加category来进行匹配

        Intent intent = new Intent();intent.setAction("com.example.widget");intent.addCategory("android.intent.category.DEFAULT");//默认,可以不用写intent.putExtra("key","value");//传递参数startActivity(intent);this.finish();

隐式启动,在启动的时候是不明确的。 如果intent-filter中还设置了 data 属性,则必须 action,category,data 完全匹配才能完成跳转

4,播放器 小部件demo实现

1,先通过Android studio创建一个Widget

2,小部件布局,上面介绍时,已经贴出

3,播放器用广播进行模拟,点击界面按钮,发送一个广播通知播放器播放。 播放器进行相应操作,发送一个广播模拟回调通知,接收到这个通知,更新桌面小部件的界面UI。这里切记需要判断下remoteViews是否为空,更新方式下面onReceive()中有贴出

package com.example.mvptest.widget;import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.RemoteViews;import com.example.mvptest.R;/*** Implementation of App Widget functionality.*/
public class AppWidget extends AppWidgetProvider {private static final String TAG = "AppWidget";public static final String START_ACT_ACTION = "com.example.widget";public static final String RECEIVER_ACTION_STATUS = "com.widget.STATUS_CHANGED";public static final int OPEN_ACT_CODE = 111;private static RemoteViews remoteViews;/*** 接收到任意广播时触发** @param context* @param intent*/@Overridepublic void onReceive(Context context, Intent intent) {super.onReceive(context, intent);String action = intent.getAction();Log.e(TAG, "onReceive: " + action);if (RECEIVER_ACTION_STATUS.equals(action)) {if (null == remoteViews) {remoteViews = new RemoteViews(context.getPackageName(), R.layout.app_widget);}if (WidgetReciver.status == 0) {remoteViews.setTextViewText(R.id.widget_title, "播放中");remoteViews.setImageViewResource(R.id.widget_pause, R.drawable.btn_player_play_normal);} else {remoteViews.setTextViewText(R.id.widget_title, "电台桌面插件");remoteViews.setImageViewResource(R.id.widget_pause, R.drawable.btn_player_pause_normal);}ComponentName componentName = new ComponentName(context, AppWidget.class);AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteViews);}}/*** 当 widget 更新时触发* 用户首次添加时也会被调用* 如果定义了widget的configure属性,首次添加时不会被调用** @param context* @param appWidgetManager* @param appWidgetIds*/@Overridepublic void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {for (int appWidgetId : appWidgetIds) {updateAppWidget(context, appWidgetManager, appWidgetId);}}/*** 当 widget 首次添加或者大小被改变时触发** @param context* @param appWidgetManager* @param appWidgetId* @param newOptions*/@Overridepublic void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);Log.e(TAG, "onAppWidgetOptionsChanged: ");}/*** 当第1个 widget 的实例被创建时触发。也就是说,如果用户对同一个 widget 增加了两次(两个实例),* 那么onEnabled()只会在第一次增加widget时触发** @param context*/@Overridepublic void onEnabled(Context context) {}/*** 当最后1个 widget 的实例被删除时触发** @param context*/@Overridepublic void onDisabled(Context context) {}/*** 当 widget 被删除时** @param context* @param appWidgetIds*/@Overridepublic void onDeleted(Context context, int[] appWidgetIds) {super.onDeleted(context, appWidgetIds);}/*******************************************************************************************************************//*** 更新** @param context* @param appWidgetManager* @param appWidgetId*/static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {Log.e(TAG, "updateAppWidget: ");remoteViews = new RemoteViews(context.getPackageName(), R.layout.app_widget);remoteViews.setTextViewText(R.id.widget_title, "电台桌面插件");openAct(context);radioCtrl(context);appWidgetManager.updateAppWidget(appWidgetId, remoteViews);}/*** 点击标题 打开activity* 通过PendingIntent 添加一个跳转activity** @param context*/private static void openAct(Context context) {Intent intent = new Intent();intent.setAction(START_ACT_ACTION);PendingIntent pi = PendingIntent.getActivity(context, OPEN_ACT_CODE, intent, PendingIntent.FLAG_CANCEL_CURRENT);remoteViews.setOnClickPendingIntent(R.id.widget_title, pi);}/*** 控制 播放(模拟)* 通过PendingIntent 添加一个Broadcast** @param context*/private static void radioCtrl(Context context) {Intent intent = new Intent(WidgetReciver.ACTION_WIDGET_CTRL);intent.putExtra(WidgetReciver.INTENT_EXTRAS, WidgetReciver.ACTION_WIDGET_PAUSE);//暂停 播放remoteViews.setOnClickPendingIntent(R.id.widget_pause, PendingIntent.getBroadcast(context, 11, intent, PendingIntent.FLAG_CANCEL_CURRENT));intent.putExtra(WidgetReciver.INTENT_EXTRAS, WidgetReciver.ACTION_WIDGET_NEXT);//下一首remoteViews.setOnClickPendingIntent(R.id.widget_next, PendingIntent.getBroadcast(context, 12, intent, PendingIntent.FLAG_CANCEL_CURRENT));intent.putExtra(WidgetReciver.INTENT_EXTRAS, WidgetReciver.ACTION_WIDGET_LAST);//上一首remoteViews.setOnClickPendingIntent(R.id.widget_last, PendingIntent.getBroadcast(context, 13, intent, PendingIntent.FLAG_CANCEL_CURRENT));}}

添加模拟播放器的广播接收类,不要忘记在AndroidManifest中注册

package com.example.mvptest.widget;import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;public class WidgetReciver extends BroadcastReceiver {private static final String TAG = "WidgetReciver";public static final String INTENT_EXTRAS = "Widget_Reciver";public static final String ACTION_WIDGET_CTRL = "com.example.widget.CTRL";public static final int ACTION_WIDGET_PAUSE = 0;public static final int ACTION_WIDGET_LAST = 1;public static final int ACTION_WIDGET_NEXT = 2;public static int status = 1;//0_播放 1_暂停@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();Log.e(TAG, "onReceive: " + action);if (action.equals(ACTION_WIDGET_CTRL)) {int e = intent.getIntExtra(INTENT_EXTRAS, 0);Log.e(TAG, "onReceive: " + e);switch (e) {case ACTION_WIDGET_LAST:Log.e(TAG, "onReceive: 播放上一首");break;case ACTION_WIDGET_NEXT:Log.e(TAG, "onReceive: 播放下一首");break;case ACTION_WIDGET_PAUSE:Log.e(TAG, "onReceive: 暂停/开始 播放");status = status == 0 ? 1 : 0;context.sendBroadcast(new Intent(AppWidget.RECEIVER_ACTION_STATUS));break;}}}
}

注册广播

        <receiverandroid:name=".widget.WidgetReciver"android:enabled="true"android:exported="true"><intent-filter><action android:name="com.example.widget.CTRL" /></intent-filter></receiver>

简单demo到此结束,后续更新复杂数据,list数据的刷新跟显式。

Android AppWidget(桌面小部件)相关推荐

  1. AppWidget(桌面小部件)

    AppWidget(桌面小部件) AppWidget概览 1. AppWidget限制 1.1 手势 由于AppWidget位于主屏幕上,因此它们必须与在主屏幕上建立的导航共存.与全屏应用相比,这样会 ...

  2. Android widget桌面小部件

    android小部件主要是用到RemoteViews这个类,和继承AppWidgerProvider. 在res/xml下新建一个appwidget.xml这个是定义小控件的配置信息. <app ...

  3. Android AppWidget(桌面小部件-音乐播放动画)

    桌面小部件基础篇:Android AppWidget (桌面小部件) 音乐播放 (动画实现)     一个音乐播放的柱状图(不会上传动图,自行脑补) 思路方案: 1,自定义View,widget 仅支 ...

  4. Android桌面小部件AppWidget:音乐播放器桌面控制部件Widget(3)

     Android桌面小部件AppWidget:音乐播放器桌面控制部件Widget(3) Android桌面小部件AppWidget比较常用的场景就是音乐播放器,音乐播放器虽然通常在后台播放,但需要 ...

  5. Android Widget——实现桌面小部件

    前言 在Android手机中,我们经常会看到各种桌面小部件,天气.音乐播放器.时间表盘等,这些都是桌面小部件的实例.本篇主要介绍桌面小部件开发的一些最基础的知识. Widget介绍 Widget并没有 ...

  6. android remoteview 缓存,关于RemoteView的一点愚见(实现桌面小部件)

    RemoteView中的应用除了有通知栏的自定义之外,还有桌面小部件也是通过RemoteView以及AppWidgetProvider来完成的. 实际上AppWidgetProvider是一个广播,即 ...

  7. 手机bootstrap搜索框_这些桌面小部件,Android 手机可不能错过

    在开源.可玩性较高的安卓系统上,小部件呈现了与 iOS 不一样的魅力.它高度的灵活自由,使得安卓手机用户,更能对自己的手机桌面进行个性化的定制. 可在细数当下众多的安卓小部件里,哪些能在保持实用性的同 ...

  8. android 仿苹果 小组件,安卓仿ios14桌面小部件

    安卓仿ios14桌面小部件,是一个可以让安卓手机的界面看起来像苹果界面的软件,功能非常强大,它可以提供多种不同主题的壁纸桌面,随心选择,设置后的效果还是非常不错的,很有高级感,操作简单,上手也很快. ...

  9. kwgt 歌词_这8款Android桌面小部件,能让你的Android好用很多倍

    安卓手机一直以来以其可玩性高而受到欢迎,即使是简单的桌面小部件,也有人能玩出花来.强大如 Zooper Widget 和 KWGT 等复杂且定制性极强的美化类桌面插件应用,抑或是朴实无华但又必不可少的 ...

最新文章

  1. CSS入门-五个简单,但有用的CSS属性
  2. 170亿参数加持,微软发布史上最大Transformer模型
  3. 我所理解的MVCMVPMVVM
  4. CodeForces - 1481E Sorting Books(贪心+dp)
  5. /IWBEP/CX_MGW_TECH_EXCEPTION - expand issue for old service on Docu history node
  6. 5训练需要更改参数吗_糖尿病病人需要多喝水吗?多喝水的5大好处,了解一下...
  7. C++ 程序运行后的内存模型
  8. 设置或者获取CheckboxList控件的选中值
  9. python贪吃蛇手机版代码_200行python代码实现贪吃蛇游戏
  10. 自动化Python+selenium 用这一篇学习就够了
  11. Reeder 5.0.3 将RSS阅读体验发挥到极致
  12. 黑客游戏之Monyer系列
  13. xp计算机限制打开u盘,处置xp系统电脑限制使用u盘的解决方法
  14. mysql三表联查sql语句_mybatis中SQL语句的三表联查
  15. Android移动开发的几种方式
  16. spring boot 2.1.7启动过程源码解析
  17. 各种安装包的下载地址
  18. 常见的HTT相应状态码
  19. 移动端苏宁首页制作和rem
  20. 高德标签label样式

热门文章

  1. eprime提示计算机内存不足,线程数不足旗舰平台拒绝旗舰表现_技嘉 G1.Assassin 2_主板评测-中关村在线...
  2. 【老生谈算法】matlab实现全相位校准算法——全相位校准算法
  3. (转)Oracle JDBC Memory Management中的客户端缓存
  4. Jieba分词工具介绍及相关API使用方法
  5. 概率图模型(3)朴素贝叶斯分类
  6. 概率论与数理统计 | 知识点总结(下)
  7. ros3.30版本以上的动态域名解析3322与changeip成功
  8. 如何删除pagefile.sys
  9. 网络游戏《丛林战争》开发与学习之(四):游戏客户器端的功能开发(上)
  10. Linux虚拟文件系统、文件描述符、管道