本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
转载请注明 http://blog.csdn.net/wrg_20100512/article/details/53940485
  好久没有写博客了,主要是忙着找工作和做毕业设计,没有时间来看一些值得分享的东西。今天写这篇博客是因为我被安排了解下RemoteViews。说实话,这个东西真心没有看过,仅仅看《Android艺术开发探索》的时候知道有这么个“控件”,还记得任主席在群里说过这是他这本书中最自豪的一章,有没有之一我就不清楚啦!话不多说,那就开始Android神奇“控件”之旅吧!

RemoteViews是什么?

先从表层意思理解RemoteViews感觉它是一个view的集合,而且和远程有关系。那事实上它是什么呢?请看官方对它的说明:

从说明可以看出,RemoteViews是用来描述一个视图的,它描述的这个视图将显示在另外一个进程中,这也就符合了RemoteViews中Remote这层含义。同时说明里也说了RemoteViews提供了一些基本的操作方法来修改它描述的那个视图的内容。听起来它还真像是个“控件”,那它真的是吗?

看一下RemoteViews的类继承关系:

从图中发现,RemoteViews与View没有半毛钱的关系,它仅仅就是Object的一个子类,实现了Parcelable接口(这就为RemoteViews能够实现跨进程提供了条件)。所以从严格意义上来说,RemoteViews并不是一个控件,它仅仅是为生成控件和修改控件属性提供一系列的方法。

总结:RemoteViews就是为跨进程生成控件和修改控件属性提供一系列方法的一个类。

说了RemoteViews是什么之后,咱们来看看为什么要用RemoteViews!

为什么要用RemoteViews?

既然RemoteViews是用于跨进程更新UI的,那咱们就来创造这么一个场景:

同一个应用中有两个Activity,这两个Activity分别处在不同的进程中

     
            MainActivity                       TempActivity
其中MainActivity所属的进程为com.example.bjwangruigang.remoteviewstudy,TempActivity所属的进程为com.example.bjwangruigang.remoteviewstudy:remote。现在需要通过TempActivity来改变MainActivity中的视图,也就是实现跨进程更新UI这么一个功能。具体来说就是在MainActivity中添加两个Button。

传统方式实现跨进程更新UI

拿到这个场景需求,结合跨进程和更新UI的知识,有以下几个方案:

  1. TempActivity把要添加的两个Button的布局的ID值通过BroadcastRecriver发送,在MainActivity中注册该广播,同时获取其中的布局ID值,通过LayoutInflater来绘制那两个Button,最后添加到MainActivity的布局中去。

  2. TempActivity通过AIDL这种方式将要添加的两个Button的布局的ID值发送到AIDLService中,通过Handler来发送消息、处理消息。处理过程同样是通过LayoutInflater来绘制那两个Button,最后添加到MainActivity的布局中去。

其实这两种方案大同小异,无非采用的进程间通信方式不同,后续的添加视图是一模一样的。方案一采用广播的形式来进行IPC通信,而方案二则采用AIDL这种相对原生的IPC方式。为了重温AIDL,这里我采用AIDL 的方式来实现上述效果。

首先建立IViewManager.aidl。

interface IViewManager {void setTextViewText(in int id,in String text);//设置TextView的内容void addView(in int layoutId);                 //添加View视图
}

rebuild project让IDE工具自己生成AIDL借口对应的java文件。

建立ViewAIDLService文件。

public class ViewAIDLService extends Service {private static final String TAG = "ViewAIDLService";private Binder viewManager = new IViewManager.Stub(){@Overridepublic void setTextViewText(int id, String text) throws RemoteException {Message message = new Message();message.what = 2;Bundle bundle = new Bundle();bundle.putInt("id",id);bundle.putString("text",text);message.setData(bundle);new MainActivity.MyHandler(ViewAIDLService.this,getMainLooper()).sendMessage(message);}@Overridepublic void addView(int layoutId) throws RemoteException {Message message = new Message();message.what = 3;Bundle bundle = new Bundle();bundle.putInt("layoutId",layoutId);message.setData(bundle);Log.i(TAG,"thread name = "+Thread.currentThread().getName());new MainActivity.MyHandler(ViewAIDLService.this,getMainLooper()).sendMessage(message);}};public ViewAIDLService() {}@Overridepublic IBinder onBind(Intent intent) {return viewManager;}
}

在TempActivity中绑定服务,并在绑定成功后,针对实现的功能调用不同的远程方法。

  private ServiceConnection viewServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.i(TAG,"onServiceConnected");IViewManager viewsManager = IViewManager.Stub.asInterface(service);try {viewsManager.setTextViewText(R.id.text,"通过AIDL跨进程修改TextView内容");viewsManager.addView(R.layout.layout);} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}};Intent viewServiceIntent = new Intent(this,ViewAIDLService.class);bindService(viewServiceIntent,viewServiceConnection,Context.BIND_AUTO_CREATE);

最终在MainActivity中处理消息,实现功能。(传统的实现方式对应着case 2和case 3)

public static class MyHandler extends Handler {WeakReference<Context> weakReference;public MyHandler(Context context, Looper looper) {super(looper);weakReference = new WeakReference<>(context);}@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Log.i(TAG, "handleMessage");switch (msg.what) {case 1: //RemoteViews的AIDL实现RemoteViews remoteViews = msg.getData().getParcelable("remoteViews");if (remoteViews != null) {Log.i(TAG, "updateUI");View view = remoteViews.apply(weakReference.get(), mLinearLayout);mLinearLayout.addView(view);}break;case 2: //修改MainActivity中TextView的内容Bundle bundle = msg.getData();TextView textView = (TextView) mLinearLayout.findViewById(bundle.getInt("id"));textView.setText(bundle.getString("text"));break;case 3: //在MainActivity中添加View视图LayoutInflater inflater = LayoutInflater.from(weakReference.get());View view = inflater.inflate(msg.getData().getInt("layoutId"),null);mLinearLayout.addView(view);default:break;}}};

大功告成,看一下效果吧!

这里我在绑定服务成功之后 ,相继调用了两次远程服务来实现两种远程UI更新(修改MainActivity中TextView中的内容和为MainActivity中添加两个Button)。那问题来了,如果这时候我们有其他的需求,比如我要为Button中修改内容,这时候我们还需要在IViewManager添加新的接口,在ViewAIDLService实现接口,当然MainActivity中对应的代码同样要修改,牵一发而动全身。除此以外,多次IPC带来的开销问题不容小觑。

终上所述,传统方式实现跨进程更新UI是可行的,但不得不提有以下弊端:

  • View中的方法数比较多,在IPC中需要增加对应的方法比较繁琐。

  • View的每一个方法都会涉及到IPC操作,多次IPC带来的开销问题不容小觑。

  • View中方法的某些参数可能不支持IPC传输。例如:OnClickListener,它仅仅是个接口没有序列化。

接下来我们来看看RemoteViews在实现上述功能有什么优势

RemoteViews实现跨进程更新UI

RemoteViews实现跨进程更新UI同样既可以通过AIDL也可以使用BroadcastReceiver,这里为了和传统方式做下对比,只贴出AIDL方式的代码。

首先建立IremoteViewsManager.aidl。

interface IremoteViewsManager {void addRemoteView(in RemoteViews remoteViews);
}

rebuild project让IDE工具自己生成AIDL借口对应的java文件。

建立RemoteViewsAIDLService文件。

public class RemoteViewsAIDLService extends Service {private static final String TAG = "RemoteViewsAIDLService";private Binder remoteViewsBinder = new IremoteViewsManager.Stub(){@Overridepublic void addRemoteView(RemoteViews remoteViews) throws RemoteException {Message message = new Message();message.what = 1;Bundle bundle = new Bundle();bundle.putParcelable("remoteViews",remoteViews);message.setData(bundle);new MainActivity.MyHandler(RemoteViewsAIDLService.this,getMainLooper()).sendMessage(message);}};public RemoteViewsAIDLService() {}@Overridepublic IBinder onBind(Intent intent) {return remoteViewsBinder;}}

在TempActivity中绑定服务

  private ServiceConnection remoteViewServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.i(TAG,"onServiceConnected");IremoteViewsManager remoteViewsManager = IremoteViewsManager.Stub.asInterface(service);RemoteViews remoteViews = new RemoteViews(TempActivity.this.getPackageName(),R.layout.layout);Intent intentClick = new Intent(TempActivity.this,MainActivity.class);PendingIntent openMainActivity = PendingIntent.getActivity(TempActivity.this,0,intentClick,0);remoteViews.setOnClickPendingIntent(R.id.firstButton,openMainActivity);Intent secondClick = new Intent(TempActivity.this,LoginActivity.class);PendingIntent openLoginActivity = PendingIntent.getActivity(TempActivity.this,0,secondClick,0);remoteViews.setOnClickPendingIntent(R.id.secondButton,openLoginActivity);try {remoteViewsManager.addRemoteView(remoteViews);} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}};Intent remoteViewServiceIntent = new Intent(this,RemoteViewsAIDLService.class);
bindService(remoteViewServiceIntent,remoteViewServiceConnection,Context.BIND_AUTO_CREATE);

最终在MainActivity中处理消息,实现功能。(代码在上面已经贴出 对应着case 1)
实现效果如下:

细心的同学可能发现,TempActivity在绑定服务中的代码中似乎为两个button做了监听。

 Intent intentClick = new Intent(TempActivity.this,MainActivity.class);PendingIntent openMainActivity = PendingIntent.getActivity(TempActivity.this,0,intentClick,0);remoteViews.setOnClickPendingIntent(R.id.firstButton,openMainActivity);Intent secondClick = new Intent(TempActivity.this,LoginActivity.class);PendingIntent openLoginActivity = PendingIntent.getActivity(TempActivity.this,0,secondClick,0);remoteViews.setOnClickPendingIntent(R.id.secondButton,openLoginActivity);

是的,这里是对button做了监听,妈妈再也不用担心OnClickListener不能在IPC中传递了。当然RemoteViews的强大之处还不止体现在这,如果想修改button中的内容,这时候你也不需要修改IremoteViewsManager.aidl、RemoteViewsAIDLService文件啦!你只需在传递RemoteViews之前添加一行代码:

         remoteViews.setCharSequence(R.id.firstButton,"setText","想改就改");

这里就不贴效果图啦,anyway这都不重要。

最重要的是:整个过程只有一次IPC,只有一次哦,一次哦。

整体来说,RemoteViews就是为跨进程更新UI而生的,内部封装了多种方法用来跨进程更新UI。但这也不代表RemoteViews是宇宙强无敌,因为它也有软肋,它目前支持的布局和View有限

layout:
FrameLayout LinearLayout RelativeLayout GridLayout
View:
AnalogClock button Chronometer ImageButton ImageView ProgressBar TextView ViewFlipper ListView GridView StackView AdapterViewFlipper ViewStub

不支持自定义View 所以传统的方式依旧是有用武之地的。

深入理解RemoteViews

按着是什么、为什么的规矩,接下来就是怎么用啦。其实上面在介绍为什么用RemoteViews的时候已经介绍了如何使用,但是并不是开发中常用的方式,仅仅是为了说明它相对于传统的跨进程更新UI的优势在哪。RemoteViews最常用的两个场景是Notification和AppWidget小部件,因为这两者的界面都运行在其他进程进程,确切来说它们所属systemServer进程,所以RemoteViews是它两的不二之选。

那这部分就结合着AppWidget使用RemoteViews,深入学习RemoteViews是怎么保证它强大的跨进程更新UI的优势的。

这里需要注意两个问题:
1、RemoteViews为什么可以通过一次IPC实现对多个View的操作。
2、其他进程怎么获取布局文件。

首先准备AppWidget的所有文件:MyAppWidgetProvider、要显示的xml以及向AndroidManifest.xml中注册MyAppWidgetProvider 等等。

AndroidManifest.xml

 <receiver android:name=".MyAppWidgetProvider"><meta-data
                android:name="android.appwidget.provider"android:resource="@xml/appwidget_provider_info" /><intent-filter><!--自定义的action用于响应点击AppWidget--><action android:name="com.wrg.study" /><!--必须要显示声明的action!因为所有的widget的广播都是通过它来发送的;要接收widget的添加、删除等广播,就必须包含它--><action android:name="android.appwidget.action.APPWIDGET_UPDATE" /></intent-filter></receiver>

MyAppWidgetProvider

public class MyAppWidgetProvider extends AppWidgetProvider {private static final String TAG = "MyAppWidgetProvider";private static final String CLICK_ACTION = "com.wrg.study";public MyAppWidgetProvider(){super();}@Overridepublic void onReceive(final Context context, Intent intent) {super.onReceive(context,intent);Log.i(TAG,"onReceive : action = "+intent.getAction());if(intent.getAction().equals(CLICK_ACTION)){Toast.makeText(context,"click it",Toast.LENGTH_SHORT).show();new Thread(){@Overridepublic void run() {super.run();Bitmap srcBitmap = BitmapFactory.decodeResource(context.getResources(),R.drawable.time);AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);for(int i=0;i<37;i++){float degree = (i*10)%360;RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.widget);remoteViews.setImageViewBitmap(R.id.imageView,rotateBitmap(srcBitmap,degree));appWidgetManager.updateAppWidget(new ComponentName(context,MyAppWidgetProvider.class),remoteViews);SystemClock.sleep(30);}}}.start();}}private Bitmap rotateBitmap(Bitmap srcBitmap, float degree) {Matrix matrix = new Matrix();matrix.reset();matrix.setRotate(degree);Bitmap tempBitmap = Bitmap.createBitmap(srcBitmap,0,0,srcBitmap.getWidth(),srcBitmap.getHeight(),matrix,true);return tempBitmap;}@Overridepublic void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {super.onUpdate(context, appWidgetManager, appWidgetIds);Log.i(TAG,"onUpdate");final int counter = appWidgetIds.length;Log.i(TAG,"counter = " + counter);for(int i=0;i<counter;i++){int appWidgetId = appWidgetIds[i];onWidgetUpdate(context,appWidgetManager,appWidgetId);}}private void onWidgetUpdate(Context context,AppWidgetManager appWidgetManager,int appWidgetId){Log.i(TAG,"appWidgetId = "+appWidgetId);RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.widget);Intent intentClick = new Intent();intentClick.setAction(CLICK_ACTION);PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0,intentClick,0);remoteViews.setOnClickPendingIntent(R.id.imageView,pendingIntent);appWidgetManager.updateAppWidget(appWidgetId,remoteViews);}
}

接下来结合代码来分析RemoteViews是怎么发挥它的优势的:

1、当用户将AppWidget拖到桌面上时,MyAppWidgetProvider继承AppWidgetProvider原有的onReceive方法,回调其onUpdate方法

2、在onWidgetUpdate方法中建立RemoteViews,之后调用appWidgetManager的updateAppWidget发起IPC。

这里实例化了RemoteViews,先看RemoteViews的构造函数

这里我们关注RemoteViews的mLayoutId成员变量。

之后RemoteViews调用了setOnClickPendingIntent方法。

remoteViews.setOnClickPendingIntent(R.id.imageView,pendingIntent);

setOnClickPendingIntent方法在内部利用viewId, pendingIntent生成SetOnClickPendingIntent对象,并将此对象作为参数传入addAction中,这里不难看出SetOnClickPendingIntent和Action存在继承或者实现的关系。先看addAction的具体逻辑,发现addAction中将传入的参数添加至RemoteViews的成员变量mActions中。

看一下Action类

Action类为一个抽象类,同时实现了Parcelable接口,支持IPC。唯一的一个抽象方法apply。
再看涉及到的Action的子类SetOnClickPendingIntent

    private class SetOnClickPendingIntent extends Action {public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {this.viewId = id;this.pendingIntent = pendingIntent;}@Overridepublic void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {final View target = root.findViewById(viewId);//1...........OnClickListener listener = null;if (pendingIntent != null) {listener = new OnClickListener() {public void onClick(View v) {final Rect rect = getSourceBounds(v);final Intent intent = new Intent();intent.setSourceBounds(rect);handler.onClickHandler(v, pendingIntent, intent);}};}target.setOnClickListener(listener);}}

RemoteViews的setOnClickPendingIntent方法可以这么理解:将添加监听的一个View动作,封装成一个Action类,保存在RemoteViews的mActions中。其实查看RemoteViews的每一个set方法,不难发现都是把对View操作的动作封装成Action类,最终保存在RemoteViews的mActions中。这个过程可以理解为:

到目前为止发现RemoteViews更多承担的是信息的一个载体,这些信息包括:要显示View的资源ID值、mActions等等。

接下来来看看appWidgetManager.updateAppWidget内部发生了什么

    public void updateAppWidget(int appWidgetId, RemoteViews views) {if (mService == null) {return;}updateAppWidget(new int[] { appWidgetId }, views);}public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {if (mService == null) {return;}try {mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);}catch (RemoteException e) {throw new RuntimeException("system server dead?", e);}}

看到了RemoteException猜测这里就开始了远程服务的调用,而这个远程服务对象mService的类型是 IAppWidgetService。之后由AppWidgetService发送消息,AppWidgetHost监听来自AppWidgetService的事件(这其中的细节涉及太多知识点,毕竟要扒的是RemoteViews。这是详细分析AppWidget生成流程的一系列文章),AppWidgetHost收到AppWidgetService发送的消息,创建AppWidgetHostView,然后通过AppWidgetService查询appWidgetId对应的RemoteViews,最后把RemoteViews传递给AppWidgetHostView去updateAppWidget。

 public void updateAppWidget(RemoteViews remoteViews) {.......if (remoteViews == null) {.......} else {mRemoteContext = getRemoteContext();int layoutId = remoteViews.getLayoutId();//如果remoteViews中的layoutId值和mLayoutId相等说明已经加载过了,只需要更新界面//不需要重新加载if (content == null && layoutId == mLayoutId) {try {remoteViews.reapply(mContext, mView, mOnClickHandler);content = mView;recycled = true;if (LOGD) Log.d(TAG, "was able to recycled existing layout");} catch (RuntimeException e) {exception = e;}}// 需要重新加载remoteViews的布局if (content == null) {try {content = remoteViews.apply(mContext, this, mOnClickHandler);} catch (RuntimeException e) {exception = e;}}}}

updateAppWidget的实现逻辑很好理解(当然这里只是保留了主要的逻辑代码),如果没有加载过remoteViews的布局则调用remoteViews.apply方法,若加载过了则调用remoteViews.reapply方法。
其实这个时候所有的操作已经处于systemServer进程中了,所要理解的也就是remoteViews的apply和reapply方法了。由于apply比reapply方法中多了一道加载布局文件的程序,这里选择分析apply的实现过程。

 public View apply(Context context, ViewGroup parent, OnClickHandler handler) {RemoteViews rvToApply = getRemoteViewsToApply(context);View result;final Context contextForResources = getContextForResources(context);LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);inflater = inflater.cloneInContext(inflationContext);inflater.setFilter(this);result = inflater.inflate(rvToApply.getLayoutId(), parent, false);rvToApply.performApply(result, parent, handler);return result;}

apply的实现过程如下:

  1. 通过RemoteViews的getLayoutId方法获取要显示的资源ID值
  2. 利用LayoutInflater加载要加载的xml文件,生成View。
  3. 调用RemoteViews的performApply方法。
 private void performApply(View v, ViewGroup parent, OnClickHandler handler) {if (mActions != null) {handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;final int count = mActions.size();for (int i = 0; i < count; i++) {Action a = mActions.get(i);a.apply(v, parent, handler);}}}

performApply的流程相对简单,就是将前面存入mActions中的Action遍历取出来,并调用action的apply方法。接下来再看具体的Action的apply的方法,就拿上面的SetOnClickPendingIntent类来分析这个过程吧!

 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {final View target = root.findViewById(viewId);//1...........OnClickListener listener = null;if (pendingIntent != null) {listener = new OnClickListener() {public void onClick(View v) {final Rect rect = getSourceBounds(v);final Intent intent = new Intent();intent.setSourceBounds(rect);handler.onClickHandler(v, pendingIntent, intent);}};}target.setOnClickListener(listener);}

实现的过程如下:
1.通过View的id值获取对应的view(target)。
2.SetOnClickPendingIntent类中的成员变量pendingIntent生成相应的OnClickListener。
3.为target设置监听。
当然这里就分析了一个相对简单的Action,其他的Action逻辑也是相同的,有的会使用反射技术来修改View的某些属性。

到这里关于RemoteViews的学习也就结束了,最后盗用别人的图来进一步解释下RemoteView内部机制,至于上面两个问题我想也不需要解释太多了。

Android神奇“控件”-----RemoteViews相关推荐

  1. Android开源控件ViewPager Indicator的使用方法

     1月16日厦门 OSC 源创会火热报名中,奖品多多哦   摘要 Android开源控件ViewPager Indicator的使用介绍 ViewPagerIndicator 目录[-] 1. V ...

  2. 日历控件的android代码,Android日历控件PickTime代码实例

    Android日历控件PickTime代码实例 发布时间:2020-10-03 16:05:51 来源:脚本之家 阅读:86 作者:手撕高达的村长 最近做项目,需要设置用户的生日,所以做这样一个功能. ...

  3. android 获取控件高度_安卓开发入门教程UI控件_ImageView

    什么是ImageView ImageView是用于显示图片的UI控件. 基础样例 1.展示本地图片 效果图 代码 <ImageViewandroid:layout_width="wra ...

  4. xamarin.android 控件,Android 库控件 - Xamarin | Microsoft Docs

    Xamarin Android 库控件Xamarin.Android Gallery control 03/15/2018 本文内容 Gallery是一种布局小组件,用于显示水平滚动列表中的项,并将当 ...

  5. android 获取控件在屏幕中的坐标

    今天,简单讲讲android如何获取控件在屏幕中的坐标. 这个其实也很简单,但是昨天做一个功能时,需要功能控件的坐标做一些逻辑操作时,居然不知道怎么做.所以在网上查找了资料后,解决了这个问题.这里记录 ...

  6. Android图表控件MPAndroidChart——曲线图LineChart的使用(财富收益图)

    目录 前言 本文涉及文章 其他相关文章 1.数据准备 1.1 数据来源 2.曲线展示 2.1 MPAndroidChart获取 2.2 数据对象获取 2.3 数据展示 3.曲线完善 3.1 图表背景. ...

  7. Android AutoCompleteTextView控件实现类似百度搜索提示,限制输入数字长度

    Android AutoCompleteTextView 控件实现类似被搜索提示,效果如下 1.首先贴出布局代码 activity_main.xml: <?xml version="1 ...

  8. android控件使用大全,Android常见控件使用详解

    本文实例为大家分享了六种Android常见控件的使用方法,供大家参考,具体内容如下 1.TextView 主要用于界面上显示一段文本信息 2.Button 用于和用户交互的一个按钮控件 //为Butt ...

  9. android组件用法说明,Android第三方控件PhotoView使用方法详解

    Android第三方控件PhotoView使用方法详解 发布时间:2020-10-21 15:06:09 来源:脚本之家 阅读:74 作者:zhaihaohao1 PhotoView的简介: 这是一个 ...

最新文章

  1. 阿里面试败北:5种微服务注册中心如何选型?这几个维度告诉你!
  2. 一不小心把win10的秘钥卸载了解决方法
  3. NYOJ 970 Yougth's Game II
  4. Elasticsearch的功能、使用场景以及特点
  5. 前端学习(2730):重读vue电商网站40之使用vue-table-with-tree-grid
  6. 钉钉推出“钉工牌”,门禁、差旅、员工福利一码通用
  7. table表格表头不懂,内容y轴滚动
  8. C++《STL和泛型编程》容器不带/带有成员函数总结
  9. [Java] 蓝桥杯 BEGIN-4 入门训练 Fibonacci数列
  10. DotNetBar 中Ribbon汉化
  11. Windows FindFirstFile利用
  12. CMP?MSP?1+1才能大于2
  13. C/C++[PAT B level 1036]图形输出
  14. android禁用状态栏5.1,Android5.1禁止状态栏下拉(SystemUI StatusBar)
  15. mysql 替换所有字母,mysql如何替换掉字母
  16. 品铂平板电脑刷linux,教你如何把品铂PIPO W8平板电脑升级到win10系统
  17. 手机放哪里辐射危害最低?
  18. 从工地转行软件测试,拿下13k+年终奖是种什么体验?
  19. 听 小城大事 大城大事 总结出来的故事(上篇)
  20. sqlserver 的查询最近7天的数据 group by 与with的结合 以及系统表的妙用

热门文章

  1. 网络协议分析(3):FTP
  2. 【课程总结】2022中科大-组合数学-期末考试试题
  3. [转载]屏蔽双显卡笔记本的独显
  4. Web前端:古诗排版网页案例设计
  5. mac苹果系统安装虚拟机方法教程 虚拟机操作之一
  6. Stream流的常用方法以及代码练习
  7. 阿里QuickBi快速上手
  8. iis启动和停止的方法介绍
  9. scanf(“%*[\n]%[^\n]“,s)是什么意思?
  10. JavaScript 堆内存溢出