前言

由于公司环境恶劣,小菜鸟我本来想画UML图来显示类与类之间的关系,可惜这个念头无法达成,也只好用Word文档来完成。待菜鸟我辞职了,再自己画上UML图和Gif动态图,来显示类于类之间的关系。所以如果有看客请谅解谅解本人的情况。

正文

之前复习了实现桌面小部件的时候,知道了其中运用到了RemoteView这个特殊的类。这个类,顾名思义就是远程视图。可以跨越进程的显示View。
这也就诞生了一些特殊应用,比如说,我们完成远程的显示视图。之前复习的AIDL也可以办到这一点,但是相比于直接使用RemoteView来说的确是复杂了那么一点。
不过RemoteView的缺点也是很明显的,为了提高RemoteView的进程间通讯的速度,RemoteView只支持一下几种View以及 ViewGroup:

  1. Layout:
    FrameLayout,LinearLayout,RelativeLayout,GridLayout
  2. View:
    AnalogClock,Button,Chronometer,ImageButton,ImageView,ProgressBar
    ,TextView,ViewFlipper,ListView,GridView,StackView,AdapterViewFilter,
    ViewStub。

RemoteView在系统中主要运用的场景有:通知和桌面部件。之前对通知有了一定的研究了,这一次我们来分析桌面部件中RemoteView的应用。

首先,上一章我们提到了,在传输RemoteView之前,需要现在RemoteView中设置好相关的资源:

RemoteViews remoteView = new RemoteViews(context.getPackageName(),R.layout.appwidget);remoteView.setImageViewBitmap(R.id.imageView1,rotateBitmap(context,srcBitmap,degree));

那么我们就从方法setImageViewBitmap作为研究入口开始探索。

 public void setImageViewBitmap(int viewId, Bitmap bitmap) {setBitmap(viewId, "setImageBitmap", bitmap);}

这里很简单又调用了setBitmap

public void setBitmap(int viewId, String methodName, Bitmap value) {addAction(new BitmapReflectionAction(viewId, methodName, value));}

接着这里又调用了addAction的方法。在这里我先提一提RemoteView的工作原理,这样才好继续理解。
前文提到了,RemoteView同样可以向AIDL实现那样用View去访问远程进程,但是原理却完全不一样。AIDL是通过另一端实现.Stub内部类,在其中调用远程View数据来实现。

然而在RemoteView中,使用的策略是将实现了Parcelable接口的内部类Action传送到远程进程。

private abstract static class Action implements Parcelable

我们将操作的对象操作封装到Action中,接着在远程端依次操作Action,最后再一次跨进程回到RemoteView中让RemoteView去修改View。这么做的高明之处在于,省去了每一种View定义一个Binder接口,提高了程序的性能。

RemoteView中的set工作

了解RemoteView的原理之后,让我们看看addAction是如何实现的。

 private void addAction(Action a) {if (hasLandscapeAndPortraitLayouts()) {throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +" layouts cannot be modified. Instead, fully configure the landscape and" +" portrait layouts individually before constructing the combined layout.");}if (mActions == null) {mActions = new ArrayList<Action>();}mActions.add(a);// update the memory usage statsa.updateMemoryUsageEstimate(mMemoryUsageCounter);}

从上面的源码,可以清晰的知道,addAction是将一系列Action封装到List中,这里就完成了Action的存储。

我们什么时候调用这里面的Action的list呢?我们之前写AppWidget接下来是这么写:

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
appWidgetManager.updateAppWidget(new ComponentName(context, AppProvider.class),remoteView);

这么写其实就是AppWidgetManager调用AppWidgetProvider中的我们自己覆写的onUpdate()方法。

比如说,我们研究的setBitmap方法中,传入了方法名setImageBitmap,接着在addAction中通过反射调用这个方法。
且看在BitmapReflectionAction这个内部类中的构造类。

BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) {this.bitmap = bitmap;this.viewId = viewId;this.methodName = methodName;bitmapId = mBitmapCache.getBitmapId(bitmap);}

可以知道的,这里将相关的数据全部存入到这个继承了Action类的BitmapReflectionAction。

AppWidgetManager工作

下面是AppWidgetManager的updateAppWidget方法。

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);}}

这里的AppWidgetManager将会在SystemServer中运行起来,这里并不讨论,先放出一点证据:

private static final String APPWIDGET_SERVICE_CLASS ="com.android.server.appwidget.AppWidgetService";
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)) {
mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
}

上面那个SystemServer究竟如何启动这里暂不做讨论,我们继续。

在上面一段函数中mService同样调用了 updateAppWidget,但是在这里指的是IAppWidgetService这个Binder接口。而AppWidgetServiceImpl就是实现这个接口。

class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,OnCrossProfileWidgetProvidersChangeListener

AppWidgetServiceImpl中工作

我们看看这个类中updateAppWidget中调用了updateAppWidgetIds:

private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,RemoteViews views, boolean partially) {final int userId = UserHandle.getCallingUserId();if (appWidgetIds == null || appWidgetIds.length == 0) {return;}// Make sure the package runs under the caller uid.mSecurityPolicy.enforceCallFromPackage(callingPackage);final int bitmapMemoryUsage = (views != null) ? views.estimateMemoryUsage() : 0;if (bitmapMemoryUsage > mMaxWidgetBitmapMemory) {throw new IllegalArgumentException("RemoteViews for widget update exceeds"+ " maximum bitmap memory usage (used: " + bitmapMemoryUsage+ ", max: " + mMaxWidgetBitmapMemory + ")");}synchronized (mLock) {ensureGroupStateLoadedLocked(userId);final int N = appWidgetIds.length;for (int i = 0; i < N; i++) {final int appWidgetId = appWidgetIds[i];// NOTE: The lookup is enforcing security across users by making// sure the caller can only access widgets it hosts or provides.Widget widget = lookupWidgetLocked(appWidgetId,Binder.getCallingUid(), callingPackage);if (widget != null) {updateAppWidgetInstanceLocked(widget, views, partially);}}}}

上一段代码就是整个AppWidget的核心逻辑,上面做的事情有以下几件:
1.final int userId = UserHandle.getCallingUserId();获取uid(用来识别程序的ID)

2.mSecurityPolicy.enforceCallFromPackage(callingPackage);保证package中运行的是我们的程序

  1. ensureGroupStateLoadedLocked(userId);做的事情有点多,简单说就是从uid获取到程序的路径以及相关文件的路径的,获取到Widget的实例,并且将Widget实例添加到ArrayList中。

  2. updateAppWidgetInstanceLocked(widget, views, partially);widget的实例不为空的时候,则更新或者载入View。

我们先看看ensureGroupStateLoadedLocked(userId)做了什么:

private void ensureGroupStateLoadedLocked(int userId) {final int[] profileIds = mSecurityPolicy.getEnabledGroupProfileIds(userId);// Careful lad, we may have already loaded the state for some// group members, so check before loading and read only the// state for the new member(s).int newMemberCount = 0;final int profileIdCount = profileIds.length;for (int i = 0; i < profileIdCount; i++) {final int profileId = profileIds[i];if (mLoadedUserIds.indexOfKey(profileId) >= 0) {profileIds[i] = LOADED_PROFILE_ID;} else {newMemberCount++;}}if (newMemberCount <= 0) {return;}int newMemberIndex = 0;final int[] newProfileIds = new int[newMemberCount];for (int i = 0; i < profileIdCount; i++) {final int profileId = profileIds[i];if (profileId != LOADED_PROFILE_ID) {mLoadedUserIds.put(profileId, profileId);newProfileIds[newMemberIndex] = profileId;newMemberIndex++;}}clearProvidersAndHostsTagsLocked();loadGroupWidgetProvidersLocked(newProfileIds);loadGroupStateLocked(newProfileIds);}

在这里面工作的事情主要有两个:
1.loadGroupWidgetProvidersLocked(newProfileIds):将从xml中通过标签读取Provider到list中
2.loadGroupStateLocked(newProfileIds):通过读取之前定义的appwidget_info中的信息后,实例化widget加入到widget的list中。

检查完组件的状态之后,我们就应该做出更新的相应update动作,updateAppWidgetIds里面调用了updateAppWidgetInstanceLocked:

private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,boolean isPartialUpdate) {if (widget != null && widget.provider != null&& !widget.provider.zombie && !widget.host.zombie) {if (isPartialUpdate && widget.views != null) {// For a partial update, we merge the new RemoteViews with the old.widget.views.mergeRemoteViews(views);} else {// For a full update we replace the RemoteViews completely.widget.views = views;}scheduleNotifyUpdateAppWidgetLocked(widget, views);}}

判断是否是部分widget的刷新,假如是部分刷新以及widget中实例为空,则操作其中RemoteView中的Action的list,接着交给scheduleNotifyUpdateAppWidgetLocked做核心工作。

private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {if (widget == null || widget.provider == null || widget.provider.zombie|| widget.host.callbacks == null || widget.host.zombie) {return;}SomeArgs args = SomeArgs.obtain();args.arg1 = widget.host;args.arg2 = widget.host.callbacks;args.arg3 = updateViews;args.argi1 = widget.appWidgetId;mCallbackHandler.obtainMessage(CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,args).sendToTarget();}

接下来就发送信息CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,将工作交给mCallbackHandler这个Handler对象工作。
让我们看看这个Handler中handleMessage究竟完成了什么:

case MSG_NOTIFY_UPDATE_APP_WIDGET: {SomeArgs args = (SomeArgs) message.obj;Host host = (Host) args.arg1;IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;RemoteViews views = (RemoteViews) args.arg3;final int appWidgetId = args.argi1;args.recycle();handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views);} break;

可以知道我们最后将工作交给handleNotifyUpdateAppWidget()方法。

private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,int appWidgetId, RemoteViews views) {try {callbacks.updateAppWidget(appWidgetId, views);} catch (RemoteException re) {synchronized (mLock) {Slog.e(TAG, "Widget host dead: " + host.id, re);host.callbacks = null;}}}

最后还是调用callbacks.updateAppWidget,而callbacks就是IAppWidgetHost这个Binder接口。

AppWidgetHost中的工作

而IAppWidgetHost的具体实现是AppWidgetHost中的Callback内部类,这个时候我们已经从SystemServer的进程中回到了我们的自己的进程,接着再通过消息机制,调用函数:

void updateAppWidgetView(int appWidgetId, RemoteViews views) {AppWidgetHostView v;synchronized (mViews) {v = mViews.get(appWidgetId);}if (v != null) {v.updateAppWidget(views);}}

可以看见这里就调用AppWidgetHostView里面的update方法。

AppWidgetHostView中的流程

可以说,接下来这个类就是真正执行更新的类。我们先去看看这个类中的update方法。
先声明AppWidgetHostView就是remoteView父容器,它是继承于FrameLayout,也就是说,它拥有FrameLayout中的特性,这样我们其实可以做很多事情了。

public class AppWidgetHostView extends FrameLayout

我们继续看看updateAppWidget中的方法:

public void updateAppWidget(RemoteViews remoteViews) {if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);boolean recycled = false;View content = null;Exception exception = null;// 插入以前的view到bitmap让我们可以办到淡入淡出效果if (CROSSFADE) {if (mFadeStartTime < 0) {if (mView != null) {final int width = mView.getWidth();final int height = mView.getHeight();try {mOld = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);} catch (OutOfMemoryError e) {// we just won't do the fademOld = null;}if (mOld != null) {//mView.drawIntoBitmap(mOld);}}}}if (remoteViews == null) {if (mViewMode == VIEW_MODE_DEFAULT) {// We've already done this -- nothing to do.return;}content = getDefaultView();mLayoutId = -1;mViewMode = VIEW_MODE_DEFAULT;} else {// Prepare a local reference to the remote Context so we're ready to准备一个本地的应用给远程Context// inflate any requested LayoutParams.mRemoteContext = getRemoteContext();int layoutId = remoteViews.getLayoutId();// 大概是如果旧的布局和新的布局相匹配,则重新用回原来的remoteView// 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;}}// Try normal RemoteView inflation尝试着加载远程视图remoteviewif (content == null) {try {content = remoteViews.apply(mContext, this, mOnClickHandler);if (LOGD) Log.d(TAG, "had to inflate new layout");} catch (RuntimeException e) {exception = e;}}mLayoutId = layoutId;mViewMode = VIEW_MODE_CONTENT;}if (content == null) {if (mViewMode == VIEW_MODE_ERROR) {// We've already done this -- nothing to do.return ;}Log.w(TAG, "updateAppWidget couldn't find any view, using error view", exception);content = getErrorView();mViewMode = VIEW_MODE_ERROR;}if (!recycled) {prepareView(content);addView(content);}if (mView != content) {removeView(mView);mView = content;}if (CROSSFADE) {if (mFadeStartTime < 0) {// if there is already an animation in progress, don't do anything --// the new view will pop in on top of the old one during the cross fade,// and that looks okay.mFadeStartTime = SystemClock.uptimeMillis();invalidate();}}}

上面的做的事情主要有两个:
1.如果过去的布局(layout)和新载入的布局(layout)相匹配则旧的重用,调用remoteView.reapply
2.如果过去的布局(layout)和新的不匹配,则调用remoteView.apply
我们这里只讨论第一次加载的情况,因此继续看apply方法。感兴趣的,可以起自行去看看reapply方法的内容。

回到RemoteView工作

在remoteViews.apply(mContext, this, mOnClickHandler)函数中调用了apply:

public View apply(Context context, ViewGroup parent, OnClickHandler handler) {RemoteViews rvToApply = getRemoteViewsToApply(context);View result;// RemoteViews may be built by an application installed in another// user. So build a context that loads resources from that user but// still returns the current users userId so settings like data / time formats// are loaded without requiring cross user persmissions.final Context contextForResources = getContextForResources(context);Context inflationContext = new ContextWrapper(context) {@Overridepublic Resources getResources() {return contextForResources.getResources();}@Overridepublic Resources.Theme getTheme() {return contextForResources.getTheme();}};LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);// Clone inflater so we load resources from correct context and// we don't add a filter to the static version returned by getSystemService.inflater = inflater.cloneInContext(inflationContext);inflater.setFilter(this);result = inflater.inflate(rvToApply.getLayoutId(), parent, false);rvToApply.performApply(result, parent, handler);return result;}

上面的代码可以看出我们是通过LayoutInfater动态加载RemoteView,加载布局文件可以动过rvToApply.getLayoutId()获得的。加载好文件之后,调用rvToApply.performApply(result, parent, handler)去执行具体的更新操作。

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);}}}

可以看到的是这个时候,我们获取list中的Action,在这里执行具体的对象对应具体的Action操作。这就完成了,我们不需要将View数据跨越进程的修改操作,而是在本线程进行真正的修改。
此时,存在队列中的ReflectionAction将会调用自身的apply,再通过反射去调用存在其中的方法名。

public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {final View view = root.findViewById(viewId);if (view == null) return;Class<?> param = getParameterType();if (param == null) {throw new ActionException("bad type: " + this.type);}try {getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));} catch (ActionException e) {throw e;} catch (Exception ex) {throw new ActionException(ex);}}

这个时候我们发现将会调用ImageView中的setImageBitmap方法。
大致上,RemoteView的工作流程就完成了。同理在Notification中也是类似的思路,有兴趣的读者可以自己去看看。

RemoteView的使用以及意义

RemoteView可以作为一种简化后的可以跨进程UI更新的方案。下面是一个模拟通知框的远程修改UI的简单Demo,这一次我就借花献佛,借用任玉刚大神的Demo:

我们首先建立两个Activity,一个远程,一个本地,只需要在< activity >标签下添加属性“:remote”即可。
我们先看发送端:
DemoActivity_2.java:

public class DemoActivity_2 extends Activity {private static final String TAG = "DemoActivity_2";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.demo_2);Log.d(TAG, "onCreate");Toast.makeText(this, getIntent().getStringExtra("sid"),Toast.LENGTH_SHORT).show();initView();}private void initView() {}public void onButtonClick(View v) {//加载RemoteView布局文件RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_simulated_notification);//加载资源文件remoteViews.setTextViewText(R.id.msg, "msg from process:" + Process.myPid());remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1);//声明pendingintent是启动activityPendingIntent pendingIntent = PendingIntent.getActivity(this,0, new Intent(this, DemoActivity_1.class), PendingIntent.FLAG_UPDATE_CURRENT);PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, DemoActivity_2.class), PendingIntent.FLAG_UPDATE_CURRENT);//给控件绑定pendingIntentremoteViews.setOnClickPendingIntent(R.id.item_holder, pendingIntent);remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2PendingIntent);Intent intent = new Intent(MyConstants.REMOTE_ACTION);intent.putExtra(MyConstants.EXTRA_REMOTE_VIEWS, remoteViews);//发送广播sendBroadcast(intent);}}

接下来是接收端MainActivity:

public class MainActivity extends Activity {private static final String TAG = "MainActivity";private LinearLayout mRemoteViewsContent;//类似像桌面小部件一样,做一个receiver来接受广播private BroadcastReceiver mRemoteViewsReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {RemoteViews remoteViews = intent.getParcelableExtra(MyConstants.EXTRA_REMOTE_VIEWS);if (remoteViews != null) {//发送来的remoteview不为空时更新updateUI(remoteViews);}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView() {mRemoteViewsContent = (LinearLayout) findViewById(R.id.remote_views_content);IntentFilter filter = new IntentFilter(MyConstants.REMOTE_ACTION);registerReceiver(mRemoteViewsReceiver, filter);}private void updateUI(RemoteViews remoteViews) {
//        View view = remoteViews.apply(this, mRemoteViewsContent);//通过方法getIdentifier来加载相应名字的layout布局int layoutId = getResources().getIdentifier("layout_simulated_notification", "layout", getPackageName());View view = getLayoutInflater().inflate(layoutId, mRemoteViewsContent, false);//调用reapply更新remoteViewremoteViews.reapply(this, view);mRemoteViewsContent.addView(view);}@Overrideprotected void onDestroy() {unregisterReceiver(mRemoteViewsReceiver);super.onDestroy();}public void onButtonClick(View v) {if (v.getId() == R.id.button1) {Intent intent = new Intent(this, TestActivity.class);startActivity(intent);} else if (v.getId() == R.id.button2) {Intent intent = new Intent(this, DemoActivity_2.class);startActivity(intent);}}}

这样就完成一次跨进程的UI更新,是不是觉得比使用AIDL简单多了呢?注意这里要更新的话,必须使用remoteView支持的view和viewgroup

代码下载:Github

RemoteView流程图与机制

RemoteView机制:

RemoteView的View结构:

RemoteView工作流程图:

这样RemoteView的工作流程大致分析完了。当然里面不仅仅只有这么多,里面涉及到的Service不仅仅只有一个AppManagerService还有PackageService,UserService等等,更加详细的,读者感兴趣的可以去自行查看源码,这里只给出了大致脉络,以及主要流程。

感谢任玉刚大神的android开发探索艺术,帮助了我看代码。

关于RemoteView的一点愚见(RemoteView在AppWidget中的工作流程)相关推荐

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

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

  2. 关于Android动画的一点愚见

    这一段时间在看Activity的工作流程,奈何内容太多,涉及到的东西有点多,暂时放下这一篇,慢慢来.先总结一下自己学过的Android自定义动画与属性动画. Android的View动画分为两类变换动 ...

  3. matlab二维图形中确定一点的坐标系,在CAD中已知一个平面图中两个点的坐标如何建立坐标系以便求出图形中任一点的坐标?...

    在CAD中已知一个平面图中两个点的坐标如何建立坐标系以便求出图形中任一点的坐标?以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一 ...

  4. 九个月搞定三轮融资,做 AI 导购的「智能一点」如何在巨头中突围?

    五个月前,雷锋网在「聊天机器人」专题中,报道了专注于母婴行业的售前智能客服创业公司--智能一点,并采访了其创始人胡云华.其时,智能一点刚刚获得洪泰基金和洪泰智造工场联合投资.追梦者基金跟投的 600 ...

  5. 程序员想找工作怎么办?如果记住这一点,不怕找不到好工作!

    风险提示:找工作就像做投资,只不过你投入的是专业技能和时间精力. 同样,工作这项投资也可以按照风险收益高低来划分:高风险高收益,例如股票期货等:低风险低收益,例如债券存款等.另外,还可以按照持有时长来 ...

  6. 记住这一点,不怕找不到好工作!

    风险提示:找工作就像做投资,只不过你投入的是专业技能和时间精力.同样,工作这项投资也可以按照风险收益高低来划分:高风险高收益,例如股票期货等:低风险低收益,例如债券存款等.另外,还可以按照持有时长来划 ...

  7. java弹出窗放textarea_java中窗体上有一个Button和一个TextArea当鼠标经过按钮上的某一点时在文本框中输出该点的坐标事件...

    展开全部 你好!只需要在按钮上32313133353236313431303231363533e59b9ee7ad9431333264636163添加鼠标监听器监听鼠标经过事件就可以了 核心代码: p ...

  8. java poll()是什么方法,JAVA学习中的一点小发现:LinkedList中.poll()方法在输出时使用应注意的小点...

    在学习JAVA类集一章中关于LinkedList子类中.poll()方法易错点的分析心得 例如如下代码: package Test; import java.util.LinkedList; publ ...

  9. 专科python应届生工资多少-请问学过一点python,应届生怎么找工作?

    感谢邀请.知乎有人提了这么个问题,Python这么火,为啥找工作这么难呢? 这两年因为第三波人工智能热潮让 Python火了一把,让中小学生.非程序员(产品经理.运营)都加入到了学习Python的行列 ...

最新文章

  1. ​从800个GPU训练几十天到单个GPU几小时,看神经架构搜索如何进化
  2. Win XP等多个版本操作系统技术支持将到期
  3. 如何高效的编写Verlog HDL——菜鸟版
  4. POJ1088:滑雪(简单dp)
  5. Linux下安装JDK和验证
  6. C/C++之 C++ String(字符串)
  7. 二叉树题目----2 检查两颗树是否相同 和 对称二叉树的判定
  8. mongodb 监听不到端口_干货|MongoDB简单操作和通过python进行操作
  9. 正确理解Python函数是第一类对象
  10. django-模型类字段类型
  11. linux 统计端口链接,linux netstat 统计链接数查看外部(转)
  12. android studio导入jar包和so库,Android实战技巧之十二:Android Studio导入第三方类库、jar包和so库(示例代码)...
  13. 【Flask】ORM关系以及一对多
  14. c++ 4种新型的类型static_cast、dynamic_cast、reinterpret_cast、const_cast转换运算符
  15. HDLBITS 练习 卡诺图化简部分
  16. matlab 脉冲压缩算法,雷达脉冲压缩matlab
  17. (计算圓柱体的体积)编写程序,读入圆柱体的半径和高,并使用下列公式计算圆柱的体积
  18. linux 7查看网络流量,CentOS7 监控网络流量
  19. jordan标准 相似_矩阵的Jordan标准形及其相似变换矩阵.pdf
  20. 计算机内存条能装几个,4G内存条和2G内存条能不能装到一个电脑上?

热门文章

  1. JVM运行时数据区之Java堆
  2. AnyChart 8.1.0 New Crack-2021-05-19
  3. awd文件服务器,AWD线下环境手册.md
  4. 第7章 负面评论识别
  5. 读书笔记-许式伟架构课读后感-1
  6. 2022年最火的网络歌曲在抖音上怎么把一首歌发完整
  7. Harbor安装教程
  8. jmeter连接数据库
  9. 多项式算法3:多项式除法
  10. VS2019安装使用VS2015工具