android notification
前言
在做应用时,时常需要在通知栏显示一条通知,那么具体流程是怎样的呢,怀着这样的探究目的,来进行一步步源码分析。
源码梳理
package com.android.server;
...
public final class SystemServer implements Dumpable {...private void startOtherServices(@NonNull TimingsTraceAndSlog t) {...if (mFactoryTestMode == FactoryTest.FACTORY_TEST_LOW_LEVEL) {dpms = null;} else {...t.traceBegin("StartNotificationManager");mSystemServiceManager.startService(NotificationManagerService.class);SystemNotificationChannels.removeDeprecated(context);SystemNotificationChannels.createAll(context);notification = INotificationManager.Stub.asInterface(ServiceManager.getService(Context.NOTIFICATION_SERVICE));t.traceEnd();...}...}...
}
package com.android.server.notification;
...
public class NotificationManagerService extends SystemService {...@Overridepublic void onStart() {...publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);publishLocalService(NotificationManagerInternal.class, mInternalService);}...@VisibleForTestingfinal IBinder mService = new INotificationManager.Stub() {...}...
}
package com.android.server;
...
@SystemApi(client = Client.SYSTEM_SERVER)
public abstract class SystemService {...protected final void publishBinderService(String name, IBinder service,boolean allowIsolated, int dumpPriority) {ServiceManager.addService(name, service, allowIsolated, dumpPriority);}...
}
应用开发都比较熟悉,显示一条通知是调用NotificationManager
的notify
接口
package android.app;
...
@SystemService(Context.NOTIFICATION_SERVICE)
public class NotificationManager {.../*** @hide*/@UnsupportedAppUsagestatic public INotificationManager getService() {if (sService != null) {return sService;}IBinder b = ServiceManager.getService("notification");sService = INotificationManager.Stub.asInterface(b);return sService;}...public void notify(int id, Notification notification) {notify(null, id, notification);}public void notify(String tag, int id, Notification notification) {notifyAsUser(tag, id, notification, mContext.getUser());}/*** @hide*/@UnsupportedAppUsagepublic void notifyAsUser(String tag, int id, Notification notification, UserHandle user) {INotificationManager service = getService();String pkg = mContext.getPackageName();try {if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,fixNotification(notification), user.getIdentifier());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}...
}
这里比较明朗,直接跳转至NotificationManagerService
的mService#enqueueNotificationWithTag
package com.android.server.notification;
...
public class NotificationManagerService extends SystemService {...@VisibleForTestingfinal IBinder mService = new INotificationManager.Stub() {...@Overridepublic void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,Notification notification, int userId) throws RemoteException {enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),Binder.getCallingPid(), tag, id, notification, userId);}...}...void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,final int callingPid, final String tag, final int id, final Notification notification,int incomingUserId) {enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,incomingUserId, false);}void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,final int callingPid, final String tag, final int id, final Notification notification,int incomingUserId, boolean postSilently) {...final StatusBarNotification n = new StatusBarNotification(pkg, opPkg, id, tag, notificationUid, callingPid, notification,user, null, System.currentTimeMillis());...final NotificationRecord r = new NotificationRecord(getContext(), n, channel);r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));r.setPostSilently(postSilently);r.setFlagBubbleRemoved(false);r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));...mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));}...
}
最终,通过Handler
调度EnqueueNotificationRunnable
package com.android.server.notification;
...
public class NotificationManagerService extends SystemService {...protected class EnqueueNotificationRunnable implements Runnable {private final NotificationRecord r;private final int userId;private final boolean isAppForeground;EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground) {this.userId = userId;this.r = r;this.isAppForeground = foreground;}@Overridepublic void run() {synchronized (mNotificationLock) {...// tell the assistant service about the notificationif (mAssistants.isEnabled()) {mAssistants.onNotificationEnqueuedLocked(r);mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),DELAY_FOR_ASSISTANT_TIME);} else {mHandler.post(new PostNotificationRunnable(r.getKey()));}}}}...
}
接下来是PostNotificationRunnable
package com.android.server.notification;
...
public class NotificationManagerService extends SystemService {...private NotificationListeners mListeners;...protected class PostNotificationRunnable implements Runnable {private final String key;PostNotificationRunnable(String key) {this.key = key;}@Overridepublic void run() {synchronized (mNotificationLock) {try {NotificationRecord r = null;int N = mEnqueuedNotifications.size();for (int i = 0; i < N; i++) {final NotificationRecord enqueued = mEnqueuedNotifications.get(i);if (Objects.equals(key, enqueued.getKey())) {r = enqueued;break;}}...final StatusBarNotification n = r.getSbn();final Notification notification = n.getNotification();...if (notification.getSmallIcon() != null) {StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;mListeners.notifyPostedLocked(r, old);...} else {...}...} finally {...}}}}...
}
执行到mListeners.notifyPostedLocked(r, old);
,继续跳转
package com.android.server.notification;
...
public class NotificationManagerService extends SystemService {...public class NotificationListeners extends ManagedServices {...@GuardedBy("mNotificationLock")public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) {notifyPostedLocked(r, old, true);}@GuardedBy("mNotificationLock")private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,boolean notifyAllListeners) {try {...for (final ManagedServiceInfo info : getServices()) {...final NotificationRankingUpdate update = makeRankingUpdateLocked(info);...final StatusBarNotification sbnToPost = trimCache.ForListener(info);mHandler.post(() -> notifyPosted(info, sbnToPost, update));}} catch (Exception e) {Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);}}...private void notifyPosted(final ManagedServiceInfo info,final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {final INotificationListener listener = (INotificationListener) info.service;StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);try {listener.onNotificationPosted(sbnHolder, rankingUpdate);} catch (RemoteException ex) {Slog.e(TAG, "unable to notify listener (posted): " + info, ex);}}...}...
}
走到这里,接下来就是通过aidl调用onNotificationPosted
接口
package android.service.notification;
...
public abstract class NotificationListenerService extends Service {...private Handler mHandler;...@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);mHandler = new MyHandler(getMainLooper());}...protected class NotificationListenerWrapper extends INotificationListener.Stub {@Overridepublic void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,NotificationRankingUpdate update) {StatusBarNotification sbn;...synchronized (mLock) {applyUpdateLocked(update);if (sbn != null) {SomeArgs args = SomeArgs.obtain();args.arg1 = sbn;args.arg2 = mRankingMap;mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,args).sendToTarget();} else {...}}}...}...private final class MyHandler extends Handler {public static final int MSG_ON_NOTIFICATION_POSTED = 1;...@Overridepublic void handleMessage(Message msg) {if (!isConnected) {return;}switch (msg.what) {case MSG_ON_NOTIFICATION_POSTED: {SomeArgs args = (SomeArgs) msg.obj;StatusBarNotification sbn = (StatusBarNotification) args.arg1;RankingMap rankingMap = (RankingMap) args.arg2;args.recycle();onNotificationPosted(sbn, rankingMap);}break;...}}}
}
到这里,接下来就是调用onNotificationPosted
接口,那么就要转至SystemUI的NotificationListener
中去
- 有机会单独记录下为啥是
NotificationListener
package com.android.systemui.statusbar;
...
public class NotificationListener extends NotificationListenerWithPlugins {...@Overridepublic void onNotificationPosted(final StatusBarNotification sbn,final RankingMap rankingMap) {if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {mMainHandler.post(() -> {processForRemoteInput(sbn.getNotification(), mContext);for (NotificationHandler handler : mNotificationHandlers) {handler.onNotificationPosted(sbn, rankingMap);}});}}...
}
再转至这里的NotificationHandler
中去
package com.android.systemui.statusbar.notification;
...
public class NotificationEntryManager implementsCommonNotifCollection,Dumpable,VisualStabilityManager.Callback {...private final NotificationHandler mNotifListener = new NotificationHandler() {@Overridepublic void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {final boolean isUpdateToInflatedNotif = mActiveNotifications.containsKey(sbn.getKey());if (isUpdateToInflatedNotif) {updateNotification(sbn, rankingMap);} else {addNotification(sbn, rankingMap);}}...}...
}
显然,接下来进入addNotification
环节
package com.android.systemui.statusbar.notification;
...
public class NotificationEntryManager implementsCommonNotifCollection,Dumpable,VisualStabilityManager.Callback {...private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;...private void addNotificationInternal(StatusBarNotification notification,RankingMap rankingMap) throws InflationException {...NotificationEntry entry = mPendingNotifications.get(key);...// Construct the expanded view.if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {mNotificationRowBinderLazy.get().inflateViews(entry, mInflationCallback);}...}public void addNotification(StatusBarNotification notification, RankingMap ranking) {try {addNotificationInternal(notification, ranking);} catch (InflationException e) {handleInflationException(notification, e);}}...
}
可以看到,这里开始调用NotificationRowBinder
的inflateViews
创建通知栏控件,而NotificationRowBinder
接口的实现类是NotificationRowBinderImpl
package com.android.systemui.statusbar.notification.collection.inflation;
...
public class NotificationRowBinderImpl implements NotificationRowBinder {...private final Provider<RowInflaterTask> mRowInflaterTaskProvider;...@Overridepublic void inflateViews(NotificationEntry entry,NotificationRowContentBinder.InflationCallback callback)throws InflationException {...if (entry.rowExists()) {...} else {mIconManager.createIcons(entry);mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,row -> {// Setup the controller for the view.ExpandableNotificationRowComponent component =mExpandableNotificationRowComponentBuilder.expandableNotificationRow(row).notificationEntry(entry).onExpandClickListener(mPresenter).listContainer(mListContainer).build();ExpandableNotificationRowController rowController =component.getExpandableNotificationRowController();rowController.init(entry);entry.setRowController(rowController);bindRow(entry, row);updateRow(entry, row);inflateContentViews(entry, row, callback);});}}...
}
这里很好理解,调用RowInflaterTask
的inflate
接口,不出意外,最终会转至回调中inflateContentViews
package com.android.systemui.statusbar.notification.row;
...
public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInflateFinishedListener {...private RowInflationFinishedListener mListener;...public void inflate(Context context, ViewGroup parent, NotificationEntry entry,RowInflationFinishedListener listener) {...mListener = listener;AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);...inflater.inflate(R.layout.status_bar_notification_row, parent, this);}...@Overridepublic void onInflateFinished(View view, int resid, ViewGroup parent) {if (!mCancelled) {try {mEntry.onInflationTaskFinished();mListener.onInflationFinished((ExpandableNotificationRow) view);} catch (Throwable t) {...}}}...
}
果然,这里不出意外,返回NotificationRowBinderImpl
的inflateContentViews
package com.android.systemui.statusbar.notification.collection.inflation;
...
public class NotificationRowBinderImpl implements NotificationRowBinder {...private final RowContentBindStage mRowContentBindStage;...private void inflateContentViews(NotificationEntry entry,ExpandableNotificationRow row,@Nullable NotificationRowContentBinder.InflationCallback inflationCallback) {...mRowContentBindStage.requestRebind(entry, en -> {...if (inflationCallback != null) {inflationCallback.onAsyncInflationFinished(en);}});}...
}
RowContentBindStage
继承自BindRequester
且未实现requestRebind
package com.android.systemui.statusbar.notification.row;
...
public abstract class BindRequester {private @Nullable BindRequestListener mBindRequestListener;public final CancellationSignal requestRebind(@NonNull NotificationEntry entry,@Nullable BindCallback callback) {CancellationSignal signal = new CancellationSignal();if (mBindRequestListener != null) {mBindRequestListener.onBindRequest(entry, signal, callback);}return signal;}final void setBindRequestListener(BindRequestListener listener) {mBindRequestListener = listener;}
}
这里的mBindRequestListener
是在NotifBindPipeline
中进行设置
package com.android.systemui.statusbar.notification.row;
...
public final class NotifBindPipeline {...private BindStage mStage;...public void setStage(BindStage stage) {mLogger.logStageSet(stage.getClass().getName());mStage = stage;mStage.setBindRequestListener(this::onBindRequested);}...private void onBindRequested(@NonNull NotificationEntry entry,@NonNull CancellationSignal signal,@Nullable BindCallback callback) {...requestPipelineRun(entry);}...
}
上面的mStage
就是RowContentBindStage
实例
- 后面专门记录下
RowContentBindStage
、BindRequester
、BindStage
以及NotifBindPipeline
之间的联系
接下来看requestPipelineRun
的实现逻辑
package com.android.systemui.statusbar.notification.row;
...
public final class NotifBindPipeline {...NotifBindPipeline(CommonNotifCollection collection,NotifBindPipelineLogger logger,@Main Looper mainLooper) {...mMainHandler = new NotifBindPipelineHandler(mainLooper);}...private void requestPipelineRun(NotificationEntry entry) {...if (!mMainHandler.hasMessages(START_PIPELINE_MSG, entry)) {Message msg = Message.obtain(mMainHandler, START_PIPELINE_MSG, entry);mMainHandler.sendMessage(msg);}}...private static final int START_PIPELINE_MSG = 1;private class NotifBindPipelineHandler extends Handler {NotifBindPipelineHandler(Looper looper) {super(looper);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case START_PIPELINE_MSG:NotificationEntry entry = (NotificationEntry) msg.obj;startPipeline(entry);break;default:throw new IllegalArgumentException("Unknown message type: " + msg.what);}}}
}
调度调用到startPipeline
package com.android.systemui.statusbar.notification.row;
...
public final class NotifBindPipeline {...private void startPipeline(NotificationEntry entry) {...mStage.executeStage(entry, row, (en) -> onPipelineComplete(en));}...
}
上面了解到,这里的mStage
是RowContentBindStage
实例
package com.android.systemui.statusbar.notification.row;
...
public class RowContentBindStage extends BindStage<RowContentBindParams> {private final NotificationRowContentBinder mBinder;...@Overrideprotected void executeStage(@NonNull NotificationEntry entry,@NonNull ExpandableNotificationRow row,@NonNull StageCallback callback) {...mBinder.bindContent(entry, row, contentToBind, bindParams, forceInflate, inflationCallback);}...
}
这里的NotificationRowContentBinder
接口的实现类为NotificationContentInflater
package com.android.systemui.statusbar.notification.row;
...
public class NotificationContentInflater implements NotificationRowContentBinder {...@Overridepublic void bindContent(NotificationEntry entry,ExpandableNotificationRow row,@InflationFlag int contentToBind,BindParams bindParams,boolean forceInflate,@Nullable InflationCallback callback) {if (row.isRemoved()) {// We don't want to reinflate anything for removed notifications. Otherwise views might// be readded to the stack, leading to leaks. This may happen with low-priority groups// where the removal of already removed children can lead to a reinflation.return;}StatusBarNotification sbn = entry.getSbn();// To check if the notification has inline image and preload inline image if necessary.row.getImageResolver().preloadImages(sbn.getNotification());if (forceInflate) {mRemoteViewCache.clearCache(entry);}// Cancel any pending frees on any view we're trying to bind since we should be bound after.cancelContentViewFrees(row, contentToBind);AsyncInflationTask task = new AsyncInflationTask(mBgExecutor,mInflateSynchronously,contentToBind,mRemoteViewCache,entry,mConversationProcessor,row,bindParams.isLowPriority,bindParams.usesIncreasedHeight,bindParams.usesIncreasedHeadsUpHeight,callback,mRemoteInputManager.getRemoteViewsOnClickHandler(),mIsMediaInQS,mSmartReplyStateInflater);if (mInflateSynchronously) {task.onPostExecute(task.doInBackground());} else {task.executeOnExecutor(mBgExecutor);}}...
}
接下来是执行异步任务
package com.android.systemui.statusbar.notification.row;
...
public class NotificationContentInflater implements NotificationRowContentBinder {...private static CancellationSignal apply(Executor bgExecutor,boolean inflateSynchronously,InflationProgress result,@InflationFlag int reInflateFlags,NotifRemoteViewCache remoteViewCache,NotificationEntry entry,ExpandableNotificationRow row,RemoteViews.InteractionHandler remoteViewClickHandler,@Nullable InflationCallback callback) {...int flag = FLAG_CONTENT_VIEW_CONTRACTED;if ((reInflateFlags & flag) != 0) {...applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,privateLayout, privateLayout.getContractedChild(),privateLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),runningInflations, applyCallback);}flag = FLAG_CONTENT_VIEW_EXPANDED;if ((reInflateFlags & flag) != 0) {if (result.newExpandedView != null) {...applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,remoteViewCache, entry, row, isNewView, remoteViewClickHandler,callback, privateLayout, privateLayout.getExpandedChild(),privateLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,applyCallback);}}flag = FLAG_CONTENT_VIEW_HEADS_UP;if ((reInflateFlags & flag) != 0) {if (result.newHeadsUpView != null) {...applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,remoteViewCache, entry, row, isNewView, remoteViewClickHandler,callback, privateLayout, privateLayout.getHeadsUpChild(),privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP), runningInflations,applyCallback);}}flag = FLAG_CONTENT_VIEW_PUBLIC;if ((reInflateFlags & flag) != 0) {...applyRemoteView(bgExecutor, inflateSynchronously, result, reInflateFlags, flag,remoteViewCache, entry, row, isNewView, remoteViewClickHandler, callback,publicLayout, publicLayout.getContractedChild(),publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),runningInflations, applyCallback);}...}...public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>implements InflationCallback, InflationTask {...@Overrideprotected void onPostExecute(InflationProgress result) {if (mError == null) {mCancellationSignal = apply(mBgExecutor,mInflateSynchronously,result,mReInflateFlags,mRemoteViewCache,mEntry,mRow,mRemoteViewClickHandler,this);} else {handleError(mError);}}...}...
}
可以看出,异步任务都指向了applyRemoteView
接口
package com.android.systemui.statusbar.notification.row;
...
public class NotificationContentInflater implements NotificationRowContentBinder {...@VisibleForTestingstatic void applyRemoteView(Executor bgExecutor,boolean inflateSynchronously,final InflationProgress result,final @InflationFlag int reInflateFlags,@InflationFlag int inflationId,final NotifRemoteViewCache remoteViewCache,final NotificationEntry entry,final ExpandableNotificationRow row,boolean isNewView,RemoteViews.InteractionHandler remoteViewClickHandler,@Nullable final InflationCallback callback,NotificationContentView parentLayout,View existingView,NotificationViewWrapper existingWrapper,final HashMap<Integer, CancellationSignal> runningInflations,ApplyCallback applyCallback) {RemoteViews newContentView = applyCallback.getRemoteView();if (inflateSynchronously) {try {if (isNewView) {View v = newContentView.apply(result.packageContext,parentLayout,remoteViewClickHandler);v.setIsRootNamespace(true);applyCallback.setResultView(v);} else {newContentView.reapply(result.packageContext,existingView,remoteViewClickHandler);existingWrapper.onReinflated();}} catch (Exception e) {handleInflationError(runningInflations, e, row.getEntry(), callback);// Add a running inflation to make sure we don't trigger callbacks.// Safe to do because only happens in tests.runningInflations.put(inflationId, new CancellationSignal());}return;}RemoteViews.OnViewAppliedListener listener = new RemoteViews.OnViewAppliedListener() {@Overridepublic void onViewInflated(View v) {if (v instanceof ImageMessageConsumer) {((ImageMessageConsumer) v).setImageResolver(row.getImageResolver());}}@Overridepublic void onViewApplied(View v) {if (isNewView) {v.setIsRootNamespace(true);applyCallback.setResultView(v);} else if (existingWrapper != null) {existingWrapper.onReinflated();}runningInflations.remove(inflationId);finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations,callback, entry, row);}@Overridepublic void onError(Exception e) {// Uh oh the async inflation failed. Due to some bugs (see b/38190555), this could// actually also be a system issue, so let's try on the UI thread again to be safe.try {View newView = existingView;if (isNewView) {newView = newContentView.apply(result.packageContext,parentLayout,remoteViewClickHandler);} else {newContentView.reapply(result.packageContext,existingView,remoteViewClickHandler);}Log.wtf(TAG, "Async Inflation failed but normal inflation finished normally.",e);onViewApplied(newView);} catch (Exception anotherException) {runningInflations.remove(inflationId);handleInflationError(runningInflations, e, row.getEntry(),callback);}}};CancellationSignal cancellationSignal;if (isNewView) {cancellationSignal = newContentView.applyAsync(result.packageContext,parentLayout,bgExecutor,listener,remoteViewClickHandler);} else {cancellationSignal = newContentView.reapplyAsync(result.packageContext,existingView,bgExecutor,listener,remoteViewClickHandler);}runningInflations.put(inflationId, cancellationSignal);}...
}
这里是要转调RemoteViews
的applyAsync
接口,并且传入了OnViewAppliedListener
回调监听;我们知道RemoteViews
是可以在不同线程或进程中创建和更新UI的组件,而且,在调用通知接口时,传入的自定义的布局参数也是RemoteViews
package android.app;
...
public class Notification implements Parcelable {...public static class Builder {...@Deprecatedpublic Builder setContent(RemoteViews views) {return setCustomContentView(views);}@NonNullpublic Builder setCustomContentView(RemoteViews contentView) {mN.contentView = contentView;return this;}...}...
}
那么就再来看下RemoteViews
的applyAsync
接口
package android.widget;
...
public class RemoteViews implements Parcelable, Filter {...public CancellationSignal applyAsync(Context context, ViewGroup parent,Executor executor, OnViewAppliedListener listener, InteractionHandler handler) {return applyAsync(context, parent, executor, listener, handler, null /* size */);}public CancellationSignal applyAsync(Context context, ViewGroup parent,Executor executor, OnViewAppliedListener listener, InteractionHandler handler,SizeF size) {return applyAsync(context, parent, executor, listener, handler, size,null /* themeColors */);}public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor,OnViewAppliedListener listener, InteractionHandler handler, SizeF size,ColorResources colorResources) {return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener,handler, colorResources, null /* result */,true /* topLevel */).startTaskOnExecutor(executor);}...private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree>implements CancellationSignal.OnCancelListener {...final OnViewAppliedListener mListener;...@Overrideprotected void onPostExecute(ViewTree viewTree) {...if (mListener != null) {if (mError != null) {mListener.onError(mError);} else {mListener.onViewApplied(viewTree.mRoot);}} else if (mError != null) {...}}...}...
}
好了,这里开始走OnViewAppliedListener
回调的onViewApplied
接口
package com.android.systemui.statusbar.notification.row;
...
public class NotificationContentInflater implements NotificationRowContentBinder {...@VisibleForTestingstatic void applyRemoteView(Executor bgExecutor,boolean inflateSynchronously,final InflationProgress result,final @InflationFlag int reInflateFlags,@InflationFlag int inflationId,final NotifRemoteViewCache remoteViewCache,final NotificationEntry entry,final ExpandableNotificationRow row,boolean isNewView,RemoteViews.InteractionHandler remoteViewClickHandler,@Nullable final InflationCallback callback,NotificationContentView parentLayout,View existingView,NotificationViewWrapper existingWrapper,final HashMap<Integer, CancellationSignal> runningInflations,ApplyCallback applyCallback) {RemoteViews newContentView = applyCallback.getRemoteView();...RemoteViews.OnViewAppliedListener listener = new RemoteViews.OnViewAppliedListener() {...@Overridepublic void onViewApplied(View v) {if (isNewView) {v.setIsRootNamespace(true);applyCallback.setResultView(v);} else if (existingWrapper != null) {existingWrapper.onReinflated();}runningInflations.remove(inflationId);finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations,callback, entry, row);}...};...}...private static boolean finishIfDone(InflationProgress result,@InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache,HashMap<Integer, CancellationSignal> runningInflations,@Nullable InflationCallback endListener, NotificationEntry entry,ExpandableNotificationRow row) {...if (runningInflations.isEmpty()) {...if (endListener != null) {endListener.onAsyncInflationFinished(entry);}return true;}return false;}...
}
在finishIfDone
中,最后回调InflationCallback
的onAsyncInflationFinished
,而这个回调就是AsyncInflationTask
package com.android.systemui.statusbar.notification.row;
...
public class NotificationContentInflater implements NotificationRowContentBinder {...public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>implements InflationCallback, InflationTask {...private final InflationCallback mCallback;...@Overridepublic void onAsyncInflationFinished(NotificationEntry entry) {mEntry.onInflationTaskFinished();mRow.onNotificationUpdated();if (mCallback != null) {mCallback.onAsyncInflationFinished(mEntry);}// Notify the resolver that the inflation task has finished,// try to purge unnecessary cached entries.mRow.getImageResolver().purgeCache();}...}...
}
回溯一下之前的回调
package com.android.systemui.statusbar.notification.row;
...
public class RowContentBindStage extends BindStage<RowContentBindParams> {private final NotificationRowContentBinder mBinder;...@Overrideprotected void executeStage(@NonNull NotificationEntry entry,@NonNull ExpandableNotificationRow row,@NonNull StageCallback callback) {...InflationCallback inflationCallback = new InflationCallback() {@Overridepublic void handleInflationException(NotificationEntry entry, Exception e) {mNotifInflationErrorManager.setInflationError(entry, e);}@Overridepublic void onAsyncInflationFinished(NotificationEntry entry) {mNotifInflationErrorManager.clearInflationError(entry);getStageParams(entry).clearDirtyContentViews();callback.onStageFinished(entry);}};mBinder.cancelBind(entry, row);mBinder.bindContent(entry, row, contentToBind, bindParams, forceInflate, inflationCallback);}...
}
继续回溯callback.onStageFinished
package com.android.systemui.statusbar.notification.row;
...
public final class NotifBindPipeline {...private final List<BindCallback> mScratchCallbacksList = new ArrayList<>();...private void startPipeline(NotificationEntry entry) {...mStage.executeStage(entry, row, (en) -> onPipelineComplete(en));}private void onPipelineComplete(NotificationEntry entry) {final BindEntry bindEntry = getBindEntry(entry);final Set<BindCallback> callbacks = bindEntry.callbacks;mLogger.logFinishedPipeline(entry.getKey(), callbacks.size());bindEntry.invalidated = false;// Move all callbacks to separate list as callbacks may themselves add/remove callbacks.// TODO: Throw an exception for this re-entrant behavior once we deprecate// NotificationGroupAlertTransferHelpermScratchCallbacksList.addAll(callbacks);callbacks.clear();for (int i = 0; i < mScratchCallbacksList.size(); i++) {mScratchCallbacksList.get(i).onBindFinished(entry);}mScratchCallbacksList.clear();}...
}
接下来回溯BindCallback
的onBindFinished
package com.android.systemui.statusbar.notification.row;
...
public final class NotifBindPipeline {...private void onBindRequested(@NonNull NotificationEntry entry,@NonNull CancellationSignal signal,@Nullable BindCallback callback) {final BindEntry bindEntry = getBindEntry(entry);if (bindEntry == null) {// Invalidating views for a notification that is not active.return;}bindEntry.invalidated = true;// Put in new callback.if (callback != null) {final Set<BindCallback> callbacks = bindEntry.callbacks;callbacks.add(callback);signal.setOnCancelListener(() -> callbacks.remove(callback));}requestPipelineRun(entry);}...
}
package com.android.systemui.statusbar.notification.collection.inflation;
...
public class NotificationRowBinderImpl implements NotificationRowBinder {...private final RowContentBindStage mRowContentBindStage;...private void inflateContentViews(NotificationEntry entry,ExpandableNotificationRow row,@Nullable NotificationRowContentBinder.InflationCallback inflationCallback) {...mRowContentBindStage.requestRebind(entry, en -> {...if (inflationCallback != null) {inflationCallback.onAsyncInflationFinished(en);}});}...
}
也就是要继续回溯inflationCallback.onAsyncInflationFinished(en)
package com.android.systemui.statusbar.notification;
...
public class NotificationEntryManager implementsCommonNotifCollection,Dumpable,VisualStabilityManager.Callback {...private NotificationPresenter mPresenter;...private final InflationCallback mInflationCallback = new InflationCallback() {...@Overridepublic void onAsyncInflationFinished(NotificationEntry entry) {...if (!entry.isRowRemoved()) {...if (isNew) {for (NotificationEntryListener listener : mNotificationEntryListeners) {listener.onEntryInflated(entry);}addActiveNotification(entry);updateNotifications("onAsyncInflationFinished");for (NotificationEntryListener listener : mNotificationEntryListeners) {listener.onNotificationAdded(entry);}} else {for (NotificationEntryListener listener : mNotificationEntryListeners) {listener.onEntryReinflated(entry);}}}}};...private void addNotificationInternal(StatusBarNotification notification,RankingMap rankingMap) throws InflationException {...// Construct the expanded view.if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {mNotificationRowBinderLazy.get().inflateViews(entry, mInflationCallback);}...}...public void updateNotifications(String reason) {reapplyFilterAndSort(reason);if (mPresenter != null && !mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {mPresenter.updateNotificationViews(reason);}}...
}
这里的NotificationPresenter
接口实现类为StatusBarNotificationPresenter
package com.android.systemui.statusbar.phone;
...
public class StatusBarNotificationPresenter implements NotificationPresenter,ConfigurationController.ConfigurationListener,NotificationRowBinderImpl.BindRowCallback,CommandQueue.Callbacks {...private final NotificationViewHierarchyManager mViewHierarchyManager;...private final NotificationPanelViewController mNotificationPanel;...@Overridepublic void updateNotificationViews(final String reason) {...mViewHierarchyManager.updateNotificationViews();mNotificationPanel.updateNotificationViews(reason);}...
}
这里显然是开始调用更新通知栏接口了
package com.android.systemui.statusbar;
...
public class NotificationViewHierarchyManager implements DynamicPrivacyController.Listener {...private NotificationListContainer mListContainer;...public void updateNotificationViews() {...ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());...for (int i = 0; i < toShow.size(); i++) {View v = toShow.get(i);if (v.getParent() == null) {mVisualStabilityManager.notifyViewAddition(v);mListContainer.addContainerView(v);} else if (!mListContainer.containsView(v)) {// the view is added somewhere else. Let's make sure// the ordering works properly below, by excluding thesetoShow.remove(v);i--;}}...}...
}
不难理解,这里开始添加通知栏控件了
package com.android.systemui.statusbar.notification.stack;
...
public class NotificationStackScrollLayoutController {...private NotificationStackScrollLayout mView;...private class NotificationListContainerImpl implements NotificationListContainer {...@Overridepublic void addContainerView(View v) {mView.addContainerView(v);}...}...
}
package com.android.systemui.statusbar.notification.stack;
...
public class NotificationStackScrollLayout extends ViewGroup implements Dumpable {...public void addContainerView(View v) {Assert.isMainThread();addView(v);if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {mController.updateShowEmptyShadeView();updateFooter();}updateSpeedBumpIndex();}...
}
自此,通知栏添加一条通知的流程算是简单梳理完成
相关文章:
android notification
前言 在做应用时,时常需要在通知栏显示一条通知,那么具体流程是怎样的呢,怀着这样的探究目的,来进行一步步源码分析。 源码梳理 package com.android.server; ... public final class SystemServer implements Dumpable {...pr…...
C# 多态性
文章目录 前言一、多态性的定义二、C# 中的多态性实现方式1. 方法重写(Overriding)2. 方法重载(Overloading)3. 接口实现(Interface implementation) 三、多态性的优点1. 提高代码的可维护性2. 增强代码的可…...
类与对象以及ES6的继承
认识class定义类 类的声明用的比较多 类与构造函数的异同 类的构造函数 类的实例方法 类的访问器方法 在类里面写拦截方法 类的静态方法 通过类名直接访问 es6类的继承-extends super关键字 子类可以重写父类方法包括父类的静态方法也可以继承父类的静态方法 babel可以将新的代…...
每日一站技術架構解析之-cc手機桌布網
# 網站技術架構解析: ## 一、整體架構概述https://tw.ccwallpaper.com是一個提供手機壁紙、桌布免費下載的網站,其技術架構設計旨在實現高效的圖片資源管理與用戶訪問體驗優化。 ### (一)前端展示 1. **HTML/CSS/JavaScript基礎構…...
【橘子容器】如何构建一个docker镜像
你肯定打过docker镜像是吧,作为一个开发这很正常,那么你用的什么打包方式呢,这里我们来梳理几种常用的docker镜像构建方式。 ps:这里不是太讲原理,更多的是一种科普和操作。因为讲原理的东西网上已经够多了。 一、Dock…...
【漏洞复现】CVE-2024-34102 Magento Open Source XXE漏洞
目录 漏洞介绍 影响版本 环境搭建 查看版本 漏洞复现 手动复现 漏洞 poc Magento Open Source 是一个免费开源的电子商务平台,适合中小企业或开发团队通过自定义代码和插件创建在线商店。它由社区开发和支持,功能强大但需要更多的技术投入。Adobe…...
数据结构 ——二叉树转广义表
数据结构 ——二叉树转广义表 1、树转广义表 如下一棵树,转换为广义表 root(c(a()(b()()))(e(d()())(f()(j(h()())())))) (根(左子树)(右子树)) 代码实现 #include<stdio.h> #include<stdlib.h>//保存…...
Redis篇-6--原理篇5--单线程模型
1、概述 Redis 采用单线程模型来处理客户端请求,这意味着在任意时刻只有一个命令被执行。这种设计简化了 Redis 的实现,并确保了高并发环境下的数据一致性。尽管 Redis 是单线程的,但它通过高效的内存管理和网络 I/O 操作,仍然能…...
LSTM详解
1. LSTM设计 LSTM(长短期记忆网络)详解 长短期记忆网络(LSTM, Long Short-Term Memory) 是一种特殊的循环神经网络(RNN),特别适合处理和预测序列数据中的长时间依赖关系。LSTM 通过引入“门机制”(如输入门、遗忘门、输出门)来解决标准 RNN 在长时间序列任务中梯度消…...
Docker 安装 Seata2.0.0 (快速配置)
说明:已安装Docker、MySql等,案例使用Mysql数据库模式、Nacos配置信息 1、准备工作 1.1 拉取镜像 [rootTseng ~]# docker pull seataio/seata-server:2.0.0 2.0.0: Pulling from seataio/seata-server 001c52e26ad5: Already exists d9d4b9b6e964: P…...
文件断点续传(视频播放,大文件下载)
客户端每次请求取大文件部分数据。 浏览器播放mp4视频时,会首先传Range消息头,检测到206状态码,和Content-Range,Accept-Ranges 会自动请求余下数据。后端需要在文件任意偏移量取数据。 参考: springboot项目实现断…...
神经网络基础-初识神经网络
人工神经网络( Artificial Neural Network, 简写为ANN)也简称为神经网络(NN),是一种模仿生物神经网络结构和功能的计算模型。人脑可以看做是一个生物神经网络,由众多的神经元连接而成。各个神经…...
爬虫获取的数据能否用于商业分析?
根据搜索结果,爬虫获取的数据能否用于商业分析,主要取决于以下几个因素: 数据的合法性与合规性: 爬虫技术本身并不违法,关键在于使用的方式和目的。爬虫技术的使用必须遵守相关法律法规,如《反不正当竞争法…...
【Java】3、并发编程 JUC(模块三:设计模式)
目录 Immutability模式Copy-on-Write模式线程本地存储模式Guarded Suspension模式(保护性暂停)Balking模式Thread-Per-Message模式Worker Thread模式两阶段终止模式生产者-消费者模式 Immutability模式 Copy-on-Write模式 线程本地存储模式 Guarded S…...
ASP.NET|日常开发中连接Sqlite数据库详解
ASP.NET|日常开发中连接Sqlite数据库详解 前言一、安装和引用相关库1.1 安装 SQLite 驱动1.2 引用命名空间 二、配置连接字符串2.1 连接字符串的基本格式 三、建立数据库连接3.1 创建连接对象并打开连接 四、执行数据库操作4.1 创建表(以简单的用户表为例…...
渗透测试学习笔记(四)web漏洞
一.web相关漏洞 漏洞分类漏洞类型Web 源码类漏洞SQL 注入,文件上传,XSS,代码执行,变量覆盖,逻辑漏洞,反序列化Web 中间件漏洞未授权访问,变量覆盖数据库漏洞弱口令,权限提升系统层漏…...
Facebook如何避免因IP变动而封号?实用指南
随着Facebook在个人社交与商业推广中的广泛应用,越来越多的用户面临因“IP变动”而被封号的问题。尤其是跨境电商、广告运营者和多账号管理用户,这种情况可能严重影响正常使用和业务发展。那么,如何避免因IP变动导致的封号问题?本…...
【Vulkan入门】10-CreatePipeline
目录 先叨叨Git信息关键代码TestPipeline::Initialize() 编译运行 先叨叨 到上篇为止已经创建了FrameBuffer和RenderPass。建立Pipeline的先决条件已经具备。本篇就来创建Pipeline。 Git信息 repository: https://gitee.com/J8_series/easy-car-uitag: 10-CreatePipelineurl…...
视频安防监控平台:Liveweb视频监控管理云平台方案
LiveWeb是深圳市好游科技有限公司开发的一套综合视频汇聚管理平台,可提供多协议(RTSP/RTMP/GB28181/海康Ehome/大华,海康SDK等)的视频设备接入,支持GB/T28181上下级联,RTSP\RTMP转GB/T28181,云台…...
企业级日志分析系统ELK之ELK概述
ELK 概述 ELK 介绍 什么是 ELK 早期IT架构中的系统和应用的日志分散在不同的主机和文件,如果应用出现问题,开发和运维人员想排 查原因,就要先找到相应的主机上的日志文件再进行查找和分析,所以非常不方便,而且还涉及…...
scala隐式转换
概念: 在Scala编程语言中,隐式转换是一种强大的功能,它允许程序在需要时自动转换数据类型或增强对象功能。这种转换通常是通过定义一个标记为implicit的函数来实现的,这个函数能够将一种类型转换为另一种类型。隐式转换的使用可以…...
基于无线传感器网络的无线土壤湿度采集系统(附详细使用教程+完整代码+原理图+完整课设报告)
🎊项目专栏:【Zigbee课程设计系列文章】(附详细使用教程完整代码原理图完整课设报告) 前言 👑由于无线传感器网络(也即是Zigbee)作为🌐物联网工程的一门必修专业课,具有…...
367_C++_计算mouse移动过程中,视频框的右侧、底部边距,以及根据实时的右侧、底部边距计算—视频框的左上角位置
代码分析 1. restorePos 方法 restorePos 的作用是恢复 NavigationFrame 的位置,将其移动到父窗口或者指定矩形内的特定位置。 void NavigationFrame::restorePos() {// 获取目标矩形:优先使用 `m_pRect`,否则默认使用视频区域或父窗口区域RSRect videoRect(m_pVide...
Ubuntu下将Julia嵌入Jupyter内核
一.安装 Julia 如果 Julia 尚未安装: 打开终端,下载最新的 Julia 安装包: wget https://julialang-s3.julialang.org/bin/linux/x64/1.9/julia-1.9.3-linux-x86_64.tar.gz 解压并移动到 /opt: tar -xvzf julia-1.9.3-linux-x86_…...
babeltrace与CTF相关学习笔记-1
babeltrace与CTF相关学习笔记-1 写在前面代码下载代码代码编译相关的依赖bootstrapconfigure过程编译和安装注 编译完成后,初步的审视找到与ctf相关的工程tests./test-ctf-writer.sh先运行./test-ctf-writer.shctf-writer脚本 vscode跟踪ctf-writer.c后记࿱…...
国内Chrome浏览器下载安装教程,谷歌浏览器最新下载教程
今天主要讲解的是国内Chrome浏览器下载安装教程,谷歌浏览器最新下载教程,包括确认浏览器版本、ChromeDriver 驱动的下载,同理,这个教程同样适用于windows版本的,linux 版本的, mac 版本的。 众所周知&…...
使用秘钥登录服务器
在我们测试或生产环境中,为了服务器安全性,有时可能需要以 SSH 密钥的方式登录服务器,接下来,将演示如何通过 SSH 私钥的方式来远程服务器。 一、远程服务器生成密钥对 1、首先在目标远程服务器下生成 SSH 密钥对 ssh-keygen然…...
vscode中PyQt5模块代码提示问题
在VSCode中使用PyQt5时遇到代码提示缺失的问题,尝试了更新jedi、使用Pylance插件以及修改python.autoComplete.extraPaths配置均未见效。 ## 配置qgis的vscode开发环境 在vscode编辑器中qgis的引入会报错,请按一下步骤解决: 1. 在vscode中&a…...
SpringBoot 整合 RabbitMQ 实现流量消峰
RabbitMQ 即一个消息队列,主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲,消息分发的作用。 消息中间件在互联网公司的使用中越来越多,刚才还看到新闻阿里将 RocketMQ 捐献给了 Apache,当然了今天的主角还…...
Jenkins与SonarQube持续集成搭建及坑位详解
Jenkins和SonarQube都是软件开发过程中常用的工具,它们在代码管理、构建、测试和质量管理方面发挥着重要作用。以下是关于Jenkins与SonarQube的作用及整合步骤环境搭建的详细解释: 一、Jenkins与SonarQube的作用 Jenkins: Jenkins是一个开源的持续集成和交付工具,它可以帮…...
从YOLOv5到训练实战:易用性和扩展性的加强
文章目录 前言一、模型介绍二、YOLOv5网络结构1.Input(输入端):智能预处理与优化策略2.Backbone(骨干网络):高效特征提取3.NECK(颈部):特征增强与多尺度融合4.Prediction…...
聊聊Oracle自适应查询优化
成也AQO败也AQO 因为工作的原因,我们接触到的客户大部分是金融和运营商行业,这些客户有个最大的特点是追求稳定,对于使用数据库新特性持保守的态度,不会轻易尝试某些可能会导致生产系统不稳定的新特性。上线前通常都会将一些新特…...
MySQL其四,各种函数,以及模拟了炸裂函数创建用户等操作
目录 一、MySQL中的函数 1、IFNULL 2、IF 3、case (难点) 4、exists(难) --存在的意思 二、常见的函数 1、字符串函数 2、数学函数 3、日期函数 (使用频率不是很高) 4、其他函数 5、关于字符集的问题 6、mysql炸裂函数…...
浅谈 php 采用curl 函数库获取网页 cookie 和 带着cookie去访问 网页的方法!!!!
由于近段时间帮朋友开发一个能够查询正方教务系统的微信公众平台号。有所收获。这里总结下个人经验。 开讲前,先吐槽一下新浪云服务器,一个程序里的 同一个函数 在PC测试可以正常运行,在它那里就会挂的现象。 老样子,我将在代…...
ssm-springmvc-学习笔记
简介 简单的来说,就是一个在表述层负责和前端数据进行交互的框架 帮我们简化了许多从前端获取数据的步骤 springmvc基本流程 用户在原本的没有框架的时候请求会直接调用到controller这个类,但是其步骤非常繁琐 所以我们就使用springmvc进行简化 当用…...
nVisual 登录页页面配置说明
一、概述 nVisual登录页面可根据具体客户需要通过public\config\access.js文件进行自定义配置。页面可以大致分为4个部分,头部、底部、可移动区域以及页面中间的信息填写区域。其中头部和底部又包含头部左侧、头部中间、头部右侧、底部左侧、底部中间、底部右侧六个…...
Qt6开发自签名证书的https代理服务器
目标:制作一个具备类似Fiddler、Burpsuit、Wireshark的https协议代理抓包功能,但是集成到自己的app内,这样无需修改系统代理设置,使用QWebengineview通过自建的代理服务器,即可实现https包的实时监测、注入等自定义功能…...
crapy 爬虫框架的使用
1.scrapy框架安装 安装前先安装python3和pycharm 社区版 执行命令安装scrapy, pip install scrapy 2.创建项目 执行命令: scrapy startproject test_spider 如图: 3.使用pycharm大开项目并设置pipenv虚拟机环境 虚拟环境是为了依赖隔…...
Edge SCDN 边缘安全加速有什么用?
Edge SCDN是最新推出的边缘安全加速服务,它是一种融合了安全防护和内容分发加速功能的网络服务技术,通过在网络边缘部署服务器节点,来优化内容的传输和用户的访问体验,同时保障网络安全。 抵御 DDoS 攻击: Edge SCDN …...
使用aarch64-unknown-linux-musl编译生成静态ARM64可执行文件
使用aarch64-unknown-linux-musl编译生成静态ARM64可执行文件 使用aarch64-unknown-linux-musl编译生成静态ARM64可执行文件1. 安装aarch64-unknown-linux-musl目标2. 安装交叉编译工具链安装musl-cross-make 3. 配置Rust编译器使用交叉编译工具链4. 编译你的Rust项目5. 运行或…...
u-boot移植、配置、编译学习笔记【刚开始就中止了】
教程视频地址 https://www.bilibili.com/video/BV1L24y187cK 【这个视频中途停更了…原因是实际中需要去改u-boot的情况比较少】 使用的u-boot的源码 视频中使用的是 u-boot-2017.03 学习到这里,暂停u-boot的移植、配置、编译学习,原因是经过与老师…...
torchaudio.load 段错误
使用 torchaudio.load 时出现崩溃,如图 解决: 安装 ffmpeg conda install ffmpeg -c conda-forge 尝试但没解决问题的方法包括 重装 cuda,重装 pytorch,安装 PySoundFile、SoundFile、sox。...
自定义函数库
求两点距离 double dis(double x1, double y1, double x2, double y2){return sqrt(pow(x2-x1, 2)pow(y2-y1, 2)); }判断闰年 bool isLeapYear(int year){return year%40 && year%100!0 || year%4000; }判断素数 bool isPrime(int num){if(num<2) return false;f…...
Tomcat的下载和使用,配置控制台输出中文日志
目录 1. 简介2. 下载3. 使用3.1 文件夹展示3.1.1 控制台输出乱码 3.2 访问localhost:80803.3 访问静态资源 4. 总结 1. 简介 Tomcat,全称为Apache Tomcat,是一个开源的Web应用服务器和Servlet容器,由Apache软件基金会的Jakarta项目开发。它实…...
STM32应用开发——BH1750光照传感器详解
STM32应用开发——BH1750光照传感器详解 目录 STM32应用开发——BH1750光照传感器详解前言1 硬件介绍1.1 BH1750简介1.2 硬件接线 2 软件编程2.1 软件原理2.1.1 IIC设备地址2.1.2 IIC读写2.1.3 BH1750指令集2.1.4 BH1750工作流程2.1.5 BH1750测量模式 2.2 测试代码2.3 运行测试…...
java jar包加密 jar-protect
介绍 java 本身是开放性极强的语言,代码也容易被反编译,没有语言层面的一些常规保护机制,jar包很容易被反编译和破解。 受classfinal(已停止维护)设计启发,针对springboot日常项目开发,重新编写安全可靠的jar包加壳加密技术,用于保护软件版权。 使用说…...
NMEA/观测文件/导航电文
NMEA-0183 NMEA-0183是美国国家海洋电子协会为海用电子设备制定的标准格式。它包含了定位时间,纬度,经度,高度,定位所用的卫星数,DOP,差分状态和校正时段等很多信息。 参考:GPS NMEA数据包解析…...
HTTPS的工作原理深入解析
在当今互联网时代,网络安全已经成为了一个备受关注的话题。随着越来越多的个人隐私和商业数据被传输在网络中,如何确保这些数据在传输过程中的安全性成为了每个网络开发者和用户关注的核心问题之一。而HTTPS(HyperText Transfer Protocol Sec…...
pandas.core.frame.DataFrame怎么进行对象内容的读写
在 Python 中,pandas.core.frame.DataFrame 是 Pandas 数据库的核心数据结构,可以方便地读取和操作表格数据。以下是几种常见的读取内容的方法: 读取特定列 通过列名获取数据。 # 假设 df 是一个 DataFrame data df["列名"] # …...
OFCA-OpenHarmony人才认证题库答案
单选题 1.[单选题] 位于后台的应用,启动组件需校验的权限是: A: ohos.permission.DISTRIBUTED_DATASYNC B: ohos.permission.START_ABILITIES_FROM_BACKGROUND C: ohos.permission.ABILITY_BACKGROUND_COMMUNICATION D: ohos.permission.START_INVISIBLE_ABIL…...