草庐IT

Android11 Notification功能解析

雷涛赛文 2023-03-28 原文

       我们知道,当手机有通知时,下拉通知中心中会显示所有的通知,该功能是在SystemUI中实现的,接着上篇文章Android11 SystemUI解析 ,本文对通知相关的功能逻辑进行分析,基于Android11 CarSystemUI的通知功能逻辑展开分析。
       关于通知功能逻辑,简单来说,无非就是四步,注册、发送、接收、显示,那么接下来针对以上四步进行源码详细分析。

一.注册

       关于CarSystemUI启动及相关逻辑可以参考文章Android11 SystemUI解析 ,本文就不赘述了,直接以NotificationPanelViewController类为入口进行分析:

1.NotificationPanelViewController

       从构造方法来看:

@Inject
public NotificationPanelViewController(xxxxxxxx,
            /* Things needed for notifications */
            IStatusBarService barService,
            CommandQueue commandQueue,
            NotificationDataManager notificationDataManager,
            CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
            CarNotificationListener carNotificationListener,
            NotificationClickHandlerFactory notificationClickHandlerFactory,
            xxxxxxxxxxx
    ) {
}

mNotificationViewController = new NotificationViewController(
            mNotificationView,PreprocessingManager.getInstance(mContext),
            mCarNotificationListener,mCarUxRestrictionManagerWrapper,
            mNotificationDataManager);
mNotificationViewController.enable();

       可以看到,在创建以上实例时,会通过Inject的方式来创造对应参数的实例,该功能是通过dagger2来实现,具体对应的Module为CarNotificationModule类,看一下CarNotificationListener实例创造时的实现,关于NotificationViewController后面再分析:

@Provides
@Singleton
static CarNotificationListener provideCarNotificationListener(Context context,
            CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
            CarHeadsUpNotificationManager carHeadsUpNotificationManager,
            NotificationDataManager notificationDataManager) {
    CarNotificationListener listener = new CarNotificationListener();
    listener.registerAsSystemService(context, carUxRestrictionManagerWrapper,
                carHeadsUpNotificationManager, notificationDataManager);
    return listener;
}

       可以看到,在provideCarNotificationListener()提供CarNotificationListener实例时,还执行了registerAsSystemService()方法,接下来看一下CarNotificationListener类:

2.CarNotificationListener

public class CarNotificationListener extends NotificationListenerService implements
        CarHeadsUpNotificationManager.OnHeadsUpNotificationStateChange {
    .....................
    .....................
    public void registerAsSystemService(Context context,
            CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
            CarHeadsUpNotificationManager carHeadsUpNotificationManager,
            NotificationDataManager notificationDataManager) {
        try {
            mNotificationDataManager = notificationDataManager;
            registerAsSystemService(context,
                    new ComponentName(context.getPackageName(), getClass().getCanonicalName()),
                    ActivityManager.getCurrentUser());
            ...................................
        } catch (RemoteException e) {
            Log.e(TAG, "Unable to register notification listener", e);
        }
    }

    @Override
    public void onListenerConnected() {
        mActiveNotifications = Stream.of(getActiveNotifications()).collect(
                Collectors.toMap(StatusBarNotification::getKey, sbn -> new AlertEntry(sbn)));
        mRankingMap = super.getCurrentRanking();
    }
}

       CarNotificationListener继承了NotificationListenerService类,该类继承了Service,是framework内部的组成部分,通过registerAsSystemService()来看一下该类的实现:

3.NotificationListenerService

public void registerAsSystemService(Context context, ComponentName componentName,
            int currentUser) throws RemoteException {
    if (mWrapper == null) {
        mWrapper = new NotificationListenerWrapper();
    }
    mSystemContext = context;
    INotificationManager noMan = getNotificationInterface();
    mHandler = new MyHandler(context.getMainLooper());
    mCurrentUser = currentUser;
    noMan.registerListener(mWrapper, componentName, currentUser);
}

       该方法内部主要执行了两项操作:
       a.创建了NotificationListenerWrapper对象,该类继承了INotificationListener.Stub,主要用来接收回调,后面在显示环节进行详细分析;

protected class NotificationListenerWrapper extends INotificationListener.Stub {
    @Override
    public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
                NotificationRankingUpdate update) {
    }

    @Override
    public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
                NotificationRankingUpdate update, NotificationStats stats, int reason) {
    }
    .....................
}

       b.将以上对象作为参数通过INotificationManager的registerListener进行注册;

protected final INotificationManager getNotificationInterface() {
    if (mNoMan == null) {
        mNoMan = INotificationManager.Stub.asInterface(
                ServiceManager.getService(Context.NOTIFICATION_SERVICE));
    }
    return mNoMan;
}

       通过getNotificationInterface()的是实现可以知道,registerListener()执行到了NotificationManagerService里面去了,接下来一起看一下:

4.NotificationManagerService

@Override
public void registerListener(final INotificationListener listener,
            final ComponentName component, final int userid) {
    enforceSystemOrSystemUI("INotificationManager.registerListener");
    mListeners.registerSystemService(listener, component, userid);
}

       mListeners是NotificationListeners实例,是在init()中进行初始化的,NotificationListeners是其内部类,看一下具体实现:

public class NotificationListeners extends ManagedServices {
    .......................
    .......................
    @Override
    public void onServiceAdded(ManagedServiceInfo info) {
        final INotificationListener listener = (INotificationListener) info.service;
        final NotificationRankingUpdate update;
        synchronized (mNotificationLock) {
            update = makeRankingUpdateLocked(info);
            updateUriPermissionsForActiveNotificationsLocked(info, true);
        }
        try {
            listener.onListenerConnected(update);
        }
    }
}

       NotificationListeners继承了ManagedServices,registerSystemService方法是在ManagedServices里面实现的,看一下:

public void registerSystemService(IInterface service, ComponentName component, int userid) {
    checkNotNull(service);
    ManagedServiceInfo info = registerServiceImpl(
                service, component, userid, Build.VERSION_CODES.CUR_DEVELOPMENT);
    if (info != null) {
        onServiceAdded(info);
    }
}

private ManagedServiceInfo registerServiceImpl(final IInterface service,
            final ComponentName component, final int userid, int targetSdk) {
    ManagedServiceInfo info = newServiceInfo(service, component, userid,
                true /*isSystem*/, null /*connection*/, targetSdk);
    return registerServiceImpl(info);
}

private ManagedServiceInfo registerServiceImpl(ManagedServiceInfo info) {
    synchronized (mMutex) {
        try {
            info.service.asBinder().linkToDeath(info, 0);
            mServices.add(info);
            return info;
        }
    }
    return null;
}

       根据调用关系,registerServiceImpl()方法内先将前面创建的INotificationListener(mWrapper)作为参数创建了ManagedServiceInfo实例info,然后执行linkToDeath进行死亡监测,最后将info加入mServices中进行管理,执行完后再执行onServiceAdded(info),该方法是在NotificationListeners类内部实现的,再回去看一下该方法,最终会调用到CarNotificationListener.java里面的onListenerConnected()方法。
       以上注册过程逻辑比较绕,用一张图来总结一下:


image.png

二.发送

       发送过程比较简单,按照系统提供的方法来发送即可,主要涉及NotificationChannel、Notification、NotificationManager这三个类,简单看一下:
       首先某个应用在发送通知前需要创建该应用对应的NotificationChannel,然后在通知中传入对应channel ID就可以了,创建如下:

1.NotificationChannel

public NotificationChannel(String id, CharSequence name, @Importance int importance) {
    this.mId = getTrimmedString(id);
    this.mName = name != null ? getTrimmedString(name.toString()) : null;
    this.mImportance = importance;
}

       在创建NotificationChannel时需要传入唯一的id、name和importance,创建如下:

NotificationManager notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
NotificationChannel notificationChannel = new NotificationChannel(DEBUG_CHANNEL_ID, DEBUG_CHANNEL_NAME,
                NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(notificationChannel);

2.Notification

       创建完NotificationChannel后,再创建Notification,Notification创建采用的是Builder模式,主要涉及的内容比较多,创建如下:

Notification missedCallNotification = new Notification.Builder(context, DEBUG_CHANNEL_ID)
        .setContentTitle("18330656010")
        .setContentText("未接来电")
        .setCategory(Notification.CATEGORY_MISSED_CALL)
        .setSmallIcon(R.drawable.notification_missed_call)
        .setOngoing(true)
        .setLargeIcon(Icon.createWithResource(context, R.drawable.notification_missed_call))
        .build();

       Notification涉及的内容比较多,可以根据需要进行设定;

3.NotificationManager

       创建完Notification后,通过NotificationManger来进行发送就可以了:

notificationManager.notify(MISSED_CALL_ID, missedCallNotification);

       执行完notify后续的逻辑处理过程,在接收环节进行分析;

三.接收

1.NotificationManager

       发送时会调用到notify()方法:

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

       跟随调用关系,会调用到notifyAsUser()方法:

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

       在notifyAsUser()会调用到NotificationManagerService中的enqueueNotificationWithTag()方法,先看一下fixNotification()方法:

private Notification fixNotification(Notification notification) {
    .....................
    if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
        if (notification.getSmallIcon() == null) {
                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
                        + notification);
        }
    }
    ...............
}

       需要注意一下:当应用targetSdkVersion大于22时,在创建Notification时需要传入smallIcon,否则会抛异常导致发送不成功;接下来看一下enqueueNotificationWithTag()方法:

2.NotificationManagerService

       NotificationManagerService继承了SystemService,在SystemServer中会进行启动,在start()方法内部会执行publishBinderService来进行Binder注册提供服务:

publishBinderService(Context.NOTIFICATION_SERVICE, mService,
 /* allowIsolated= */ false,DUMP_FLAG_PRIORITY_CRITICAL DUMP_FLAG_PRIORITY_NORMAL);

2.1.enqueueNotificationWithTag()

final IBinder mService = new INotificationManager.Stub() {
    ....................................
    @Override
    public 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);
    }
}

2.2.enqueueNotificationInternal()

       可以看到,enqueueNotificationWithTag()会调用到enqueueNotificationInternal(),该方法是真正的逻辑实现:

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) {
    ..........................
    //check
    ..........................

    // Fix the notification as best we can.
    try {
        fixNotification(notification, pkg, tag, id, userId);
    }

    final StatusBarNotification n = new StatusBarNotification(
            pkg, opPkg, id, tag, notificationUid, callingPid, notification,
            user, null, mSystemClock.currentTimeMillis());

    // setup local book-keeping
    String channelId = notification.getChannelId();
    if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
        channelId = (new Notification.TvExtender(notification)).getChannelId();
    }
    String shortcutId = n.getShortcutId();
    final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
            pkg, notificationUid, channelId, shortcutId,
            true /* parent ok */, false /* includeDeleted */);
    if (channel == null) {
        .....................................
        return;
    }

   final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
   ...........................
   ..........................
    // Need escalated privileges to get package importance
    final long token = Binder.clearCallingIdentity();
    boolean isAppForeground;
    try {
        isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
    } finally {
        Binder.restoreCallingIdentity(token);
    }
    mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
}

       该方法中主要做了以下几件事:
       1.进行各种check来确保notification的合法性;
       2.将Notification作为参数创建StatusBarNotification;
       3.获取Notification对应的channel id,根据channel id 来获取应用对应的NotificationChannel,如果为空的话,就直接返回了,因此应用在发送notification前需要先创建对应NotificationChannel;
       4.通过Handler post EnqueueNotificationRunnable来执行后续逻辑;

2.3.EnqueueNotificationRunnable

protected class EnqueueNotificationRunnable implements Runnable {
    .......................
    @Override
    public void run() {
        ........................
        mEnqueuedNotifications.add(r);
        final StatusBarNotification n = r.getSbn();
        NotificationRecord old = mNotificationsByKey.get(n.getKey());
        ..........................
        postPostNotificationRunnableMaybeDelayedLocked(
                        r, new PostNotificationRunnable(r.getKey()));
    }
}

       在EnqueueNotificationRunnable内部会将r(NotificationRecord)加入mEnqueuedNotifications进行管理,然后判断该NotificationRecord是否已经存在过,最后执行PostNotificationRunnable;

2.4.PostNotificationRunnable

protected class PostNotificationRunnable implements Runnable {

    @Override
    public 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;
                    }
                }
                .......................
                NotificationRecord old = mNotificationsByKey.get(key);
                final StatusBarNotification n = r.getSbn();
                final Notification notification = n.getNotification();
                ......................
                int index = indexOfNotificationLocked(n.getKey());
                if (index < 0) {
                    mNotificationList.add(r);
                    mUsageStats.registerPostedByApp(r);
                    r.setInterruptive(isVisuallyInterruptive(null, r));
                } else {
                    old = mNotificationList.get(index);  // Potentially *changes* old
                    mNotificationList.set(index, r);
                    mUsageStats.registerUpdatedByApp(r, old);
                    // Make sure we don't lose the foreground service state.
                    notification.flags |=
                                old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
                    r.isUpdate = true;
                    final boolean isInterruptive = isVisuallyInterruptive(old, r);
                    r.setTextChanged(isInterruptive);
                    r.setInterruptive(isInterruptive);
               }

               mNotificationsByKey.put(n.getKey(), r);
               ...........................
               if (notification.getSmallIcon() != null) {
                   StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
                   mListeners.notifyPostedLocked(r, old);
                   .........................
                } finally {
                    int N = mEnqueuedNotifications.size();
                    NotificationRecord enqueued = null;
                    for (int i = 0; i < N; i++) {
                        enqueued = mEnqueuedNotifications.get(i);
                        if (Objects.equals(key, enqueued.getKey())) {
                            mEnqueuedNotifications.remove(i);
                            break;
                        }
                    }

                    // If the enqueued notification record had a cancel attached after it, execute
                    // it right now
                    if (enqueued != null && mDelayedCancelations.get(enqueued) != null) {
                        for (CancelNotificationRunnable r : mDelayedCancelations.get(enqueued)) {
                            r.doNotificationCancelLocked();
                        }
                        mDelayedCancelations.remove(enqueued);
                    }
                }
            }
        }
}

       PostNotificationRunnable的run()中主要处理逻辑如下:
       1.从mEnqueuedNotifications中找到跟key对应的NotificationRecord;
       2.通过indexOfNotificationLocked()看mNotificationList里面是否已经包含该NotificationRecord,如果不存在,说明是新的record,需要加入mNotificationList进行管理;否则的话,将mNotificationList中index对应的NotificationRecord进行更新;
       3.将要处理的NotificationRecord放入mNotificationsByKey进行管理;
       4.执行mListeners.notifyPostedLocked(r, old)来进行通知;
       5.在finally里面将要处理的NotificationRecord从mEnqueuedNotifications里面移除;
       6.如果在加入后有取消操作,需要立刻执行取消操作,并将NotificationRecord从mDelayedCancelations中移除;

四.显示

       前面讲到,在PostNotificationRunnable中会执行mListeners.notifyPostedLocked(r, old)进行通知,mListeners是NotificationListeners实例:

1.NotificationListeners

public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) {
    notifyPostedLocked(r, old, true);
}

       跟随调用关系:

private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
                boolean notifyAllListeners) {
    try {
        // Lazily initialized snapshots of the notification.
        StatusBarNotification sbn = r.getSbn();
        StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
        TrimCache trimCache = new TrimCache(sbn);

        for (final ManagedServiceInfo info : getServices()) {
              ..............................
              final StatusBarNotification sbnToPost = trimCache.ForListener(info);
              mHandler.post(() -> notifyPosted(info, sbnToPost, update));
        }
    }
}

       通过getServices()来找到已经注册过的ManagedServiceInfo列表,最后执行notifyPosted();

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

       在notifyPosted()内部通过info.service来找到对应的INotificationListener实例,对应NotificationListenerService内部的NotificationListenerWrapper,然后将StatusBarNotification封装成StatusBarNotificationHolder,最后执行NotificationListenerWrapper的onNotificationPosted()方法:

2.NotificationListenerWrapper

protected class NotificationListenerWrapper extends INotificationListener.Stub {
    @Override
    public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
                NotificationRankingUpdate update) {
        StatusBarNotification sbn;
        try {
            sbn = sbnHolder.get();
        }
        .................................
        // protect subclass from concurrent modifications of (@link mNotificationKeys}.
        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();
            }
        }
..............
}

       最终会通过Handler来发送消息来进行处理;

3.NotificationListenerService

    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(sbn, rankingMap)是在CarNotificationListener内部实现的;

4.CarNotificationListener

@Override
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
    .....................
    AlertEntry alertEntry = new AlertEntry(sbn);
    onNotificationRankingUpdate(rankingMap);
    notifyNotificationPosted(alertEntry);
}

       在CarNotificationListener内部会将StatusBarNotification封装成AlertEntry,然后执行notifyNotificationPosted():

private void notifyNotificationPosted(AlertEntry alertEntry) {
    ..........................
    postNewNotification(alertEntry);
}

       一步一步调用:

private void postNewNotification(AlertEntry alertEntry) {
    mActiveNotifications.put(alertEntry.getKey(), alertEntry);
    sendNotificationEventToHandler(alertEntry, NOTIFY_NOTIFICATION_POSTED);
}

       先将alertEntry存入mActiveNotifications进行管理;然后执行sendNotificationEventToHandler发送NOTIFY_NOTIFICATION_POSTED消息;

private void sendNotificationEventToHandler(AlertEntry alertEntry, int eventType) {
    Message msg = Message.obtain(mHandler);
    msg.what = eventType;
    msg.obj = alertEntry;
    mHandler.sendMessage(msg);
}

       该Handler是通过setHandler来赋值的,具体是在什么地方呢?
       这个需要回到最前面NotificationPanelViewController里面了,前面说到NotificationViewController是在显示环节进行分析,轮到NotificationViewController登场了;

5.NotificationViewController

       NotificationViewController是在NotificationPanelViewController实例化,并执行enable()方法,先看一下构造方法:

public NotificationViewController(CarNotificationView carNotificationView,
            PreprocessingManager preprocessingManager,
            CarNotificationListener carNotificationListener,
            CarUxRestrictionManagerWrapper uxResitrictionListener,
            NotificationDataManager notificationDataManager) {
    mCarNotificationView = carNotificationView;
    mPreprocessingManager = preprocessingManager;
    mCarNotificationListener = carNotificationListener;
    mUxResitrictionListener = uxResitrictionListener;
    mNotificationDataManager = notificationDataManager;
}

       在构造方法内部,会传入CarNotificationView、CarNotificationListener、PreprocessingManager等实例,都是跟显示有关的核心类;
       1.CarNotificationView:负责处理通知显示;
       2.PreprocessingManager:负责管理通知,通过CarNotificationListener来获取通知;
       3.CarNotificationListener:跟NotificationManagerService间接交互的类;

public void enable() {
    mCarNotificationListener.setHandler(mNotificationUpdateHandler);
    ..................
}

private class NotificationUpdateHandler extends Handler {
    @Override
    public void handleMessage(Message message) {
        if (mIsVisible) {
            updateNotifications(
                    mShowLessImportantNotifications,message.what,(AlertEntry) message.obj);
        } else {
            resetNotifications(mShowLessImportantNotifications);
       }
    }
}

       可以看到Handler是在NotificationViewController里面实现的,当有消息到来时,如果CarNotificationView显示时执行updateNotifications()来直接显示通知;不显示时执行resetNotifications()来对通知进行管理;

private void updateNotifications(
            boolean showLessImportantNotifications, int what, AlertEntry alertEntry) {

    if (mPreprocessingManager.shouldFilter(alertEntry,
                mCarNotificationListener.getCurrentRanking())) {
        // if the new notification should be filtered out, return early
        return;
    }

    mCarNotificationView.setNotifications(
            mPreprocessingManager.updateNotifications(
                    showLessImportantNotifications,
                    alertEntry,
                    what,
                    mCarNotificationListener.getCurrentRanking()));
}

       以上就是Notification的整个工作过程,最后用一张流程图来总结一下:


image.png

有关Android11 Notification功能解析的更多相关文章

  1. Ruby 解析字符串 - 2

    我有一个字符串input="maybe(thisis|thatwas)some((nice|ugly)(day|night)|(strange(weather|time)))"Ruby中解析该字符串的最佳方法是什么?我的意思是脚本应该能够像这样构建句子:maybethisissomeuglynightmaybethatwassomenicenightmaybethiswassomestrangetime等等,你明白了......我应该一个字符一个字符地读取字符串并构建一个带有堆栈的状态机来存储括号值以供以后计算,还是有更好的方法?也许为此目的准备了一个开箱即用的库?

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 用逗号、双引号和编码解析 csv - 2

    我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

  4. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  5. ruby-on-rails - 我更新了 ruby​​ gems,现在到处都收到解析树错误和弃用警告! - 2

    简而言之错误:NOTE:Gem::SourceIndex#add_specisdeprecated,useSpecification.add_spec.Itwillberemovedonorafter2011-11-01.Gem::SourceIndex#add_speccalledfrom/opt/local/lib/ruby/site_ruby/1.8/rubygems/source_index.rb:91./opt/local/lib/ruby/gems/1.8/gems/rails-2.3.8/lib/rails/gem_dependency.rb:275:in`==':und

  6. ruby - 安装libv8(3.11.8.13)出错,Bundler无法继续 - 2

    运行bundleinstall后出现此错误:Gem::Package::FormatError:nometadatafoundin/Users/jeanosorio/.rvm/gems/ruby-1.9.3-p286/cache/libv8-3.11.8.13-x86_64-darwin-12.gemAnerroroccurredwhileinstallinglibv8(3.11.8.13),andBundlercannotcontinue.Makesurethat`geminstalllibv8-v'3.11.8.13'`succeedsbeforebundling.我试试gemin

  7. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  8. ruby - 用 YAML.load 解析 json 安全吗? - 2

    我正在使用ruby2.1.0我有一个json文件。例如:test.json{"item":[{"apple":1},{"banana":2}]}用YAML.load加载这个文件安全吗?YAML.load(File.read('test.json'))我正在尝试加载一个json或yaml格式的文件。 最佳答案 YAML可以加载JSONYAML.load('{"something":"test","other":4}')=>{"something"=>"test","other"=>4}JSON将无法加载YAML。JSON.load("

  9. ruby - 如何使用 Nokogiri 解析纯 HTML 表格? - 2

    我想用Nokogiri解析HTML页面。页面的一部分有一个表,它没有使用任何特定的ID。是否可以提取如下内容:Today,3,455,34Today,1,1300,3664Today,10,100000,3444,Yesterday,3454,5656,3Yesterday,3545,1000,10Yesterday,3411,36223,15来自这个HTML:TodayYesterdayQntySizeLengthLengthSizeQnty345534345456563113003664354510001010100000344434113622315

  10. ruby-on-rails - 将 Amazon Simple Notification service SNS 与 ruby​​ 结合使用 - 2

    很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visitthehelpcenter.关闭9年前。我需要从基于ruby​​的应用程序使用AmazonSimpleNotificationService,但不知道从哪里开始。您对从哪里开始有什么建议吗?

随机推荐