Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

庆科物联网平台架构分析

$
0
0

庆科物联网分析和开发原创系列由华南师范大学物联网创新中心Hunter_Zhu执笔!

之前在介绍国内物联网平台的发展时曾经说到,物联网有两个发展路径,一是云端服务商选择和设备厂商合作推出完整解决方案,如阿里物联、微信硬件等等;二是模组设备厂商选择和云厂商合作并后续推出自己的云平台。庆科是国内第二种路径的典型代表,其与阿里物联深度合作,在智能家电市场占有重要地位,并在近期推出了自己的云平台,发布从云到设备到APP的完整解决方案。

上个月18号庆科举办了新品发布会,发布的新模块包括EMW3031、EMW3239、EMW3166、VBS6100四款新模块,发布会上还发布了MICO 3.0操作系统、MiCoder工具链和MiCoder IDE集成开发环境和Fogcloud2.0云服务产品。

本篇文章将带各位企鹅仔从以下三大部分去了解庆科物联网平台架构: FogCloud云端服务、MiCO设备、APP控制端。


一、Fogcloud云端服务

Fogcloud web端主要为开发者提供了产品管理、OTA升级、APP应用管理、设备管理和用户管理等功能界面。

设备通过HTTPS协议和MQTT协议/TLS协议与Fogcloud进行交互:设备激活、查询超级用户以及上传传感器数据、接收控制消息等,数据安全可靠。设备端Fogcloud接口主要包括:


设备Fogcloud接口更详细说明可以参考:https://v2.fogcloud.io/dev/sdfsdf/device1/

APP开发包括原生开发和APICloud混合模式开发,多数开发者选择APICloud来完成APP开发,基于APICloud平台的APP开发其实就是进行H5+CSS3+JS的移动web开发,庆科为这种方式的开发者提供了mico2资源包,开发者可以通过JS调用提供的API接口来完成用户管理如登陆注册和设备管理如设备配网、设备发现、设备绑定和分享以及远程控制等,关于mico2包更详细说明可以参考:https://github.com/mxchipSDK/Fog2.0/tree/master/APICloud

二、MiCO设备

设备端基于MICO操作系统完成Fogcloud接入以及顶层应用开发,MICO设备固件结构:


庆科为开发者提供了MiCO API来完成开发,API主要包括五部分:

1)外设API:UART, GPIO,SPI, I2C, FLASH, ADC, RTC等等;

2)RTOS:线程、信号量、互斥锁、消息队列、定时器等相关API;

3)网络通信:Wlan无线通信、TCP/IP、TLS/SSL等等;

4)系统服务:系统核心数据管理、系统电源管理、系统通知中心、系统配置服务器、串口命令行、Easylink/Airkiss系统网络设置、mDNS设备发现、系统固件更新等等;

5)安全算法:CRC, MD5, SHA等等;

除此之外,庆科还为开发者提供了MQTT客户端组件,MICO SDK v3.0.0和相关组件可以在这里进行获取:http://www.mico.io/wiki/download

三、APP控制端

Fogcloud提供的SDK支持APICloud混合模式开发、android和ios原生开发使用,APICloud是一个可以使用web页面编程实现APP开发的平台,因此作为一个web前端开发者也能轻松地进行APP的开发。

APP端如前所述,主要完成用户管理和设备管理功能,其可以和Fogcloud以及设备端进行通信交互。APP和设备的交互包括:Easylink配网、mDNS协议发现设备、绑定以及其它一些设备本地配置,APP接收传感器数据和控制设备、设备在离线状态获取等等都是通过调用SDK和Fogcloud进行交互完成的,这些SDK都封装在mico2资源包中。

 

以上从云端、设备、APP端三大部分为大家进行庆科物联网平台介绍,目前,接入Fogcloud 2.0的设备端以及APP端demo尚未发布,本研发团队受邀进行公测,待发布后结合demo进行分析,以更好地分享给大家。


嵌入式企鹅圈原创团队由阿里、魅族、nvidia、龙芯、炬力、拓尔思等资深工程师组成。百分百原创,每周两篇,分享嵌入式、Linux、物联网、GPU、Android、自动驾驶等技术。欢迎扫码关注微信公众号:嵌入式企鹅圈,实时推送原创文章!

 

 


作者:yueqian_scut 发表于2016/9/21 20:22:58 原文链接
阅读:107 评论:0 查看评论

Android 6.0+ 相机图册调用崩溃解决方案

$
0
0

最近客户更新系统发现,以前的项目在调用相机的时候,闪退掉了,很奇怪,后来查阅后发现,Android 6.0以后需要程序授权相机权限,默认会给出提示,让用户授权,个人感觉这一特性很好,大概如下:

导入Android V4, V7包!

Android Studio 导入很简单,右键项目后找到dependency就ok了。

继承AppCompatActivity

public class MainActivity extends AppCompatActivity

引入需要的类库

import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;

检查相机权限,并请求权限

// BEGIN_INCLUDE(camera_permission_request)
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.CAMERA)) {
            // Provide an additional rationale to the user if the permission was not granted
            // and the user would benefit from additional context for the use of the permission.
            // For example if the user has previously denied the permission.
            Log.i(TAG,
                    "Displaying camera permission rationale to provide additional context.");
            Snackbar.make(mLayout, R.string.permission_camera_rationale,
                    Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.ok, new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            ActivityCompat.requestPermissions(MainActivity.this,
                                    new String[]{Manifest.permission.CAMERA},
                                    REQUEST_CAMERA);
                        }
                    })
                    .show();
        } else {

            // Camera permission has not been granted yet. Request it directly.
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
                    REQUEST_CAMERA);
        }
        // END_INCLUDE(camera_permission_request)

授权后接收回调方法:

 /**
     * Callback received when a permissions request has been completed.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {

        if (requestCode == REQUEST_CAMERA) {
            // BEGIN_INCLUDE(permission_result)
            // Received permission result for camera permission.
            Log.i(TAG, "Received response for Camera permission request.");

            // Check if the only required permission has been granted
            } 
    }

混合应用开发解决方案

对于混合应用开发,有两种解决方案:

  1. 更改现有插件,添加权限访问代码(可能有有些麻烦)

  2. 调用权限插件:

安装

cordova plugin add cordova-plugin-android-permissions@0.10.0

包含的权限

// Example 
permissions.ACCESS_COARSE_LOCATION
permissions.CAMERA
permissions.GET_ACCOUNTS
permissions.READ_CONTACTS
permissions.READ_CALENDAR
...

示例代码

var permissions = cordova.plugins.permissions;
permissions.hasPermission(permissions.CAMERA, checkPermissionCallback, null);

function checkPermissionCallback(status) {
  if(!status.hasPermission) {
    var errorCallback = function() {
      console.warn('Camera permission is not turned on');
    }

    permissions.requestPermission(
      permissions.CAMERA,
      function(status) {
        if(!status.hasPermission) errorCallback();
      },
      errorCallback);
  }
}

没有更新的赶紧更新吧!

作者:jiangbo_phd 发表于2016/9/21 21:16:43 原文链接
阅读:199 评论:0 查看评论

Android7.0 MessageQueue

$
0
0

Android中的消息处理机制大量依赖于Handler。每个Handler都有对应的Looper,用于不断地从对应的MessageQueue中取出消息处理。
一直以来,觉得MessageQueue应该是Java层的抽象,然而事实上MessageQueue的主要部分在Native层中。
自己对MessageQueue在Native层的工作不太熟悉,借此机会分析一下。

一、MessageQueue的创建
当需要使用Looper时,我们会调用Looper的prepare函数:

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //sThreadLocal为线程本地存储区;每个线程仅有一个Looper
    sThreadLocal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
    //创建出MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

1 NativeMessageQueue
我们看看MessageQueue的构造函数:

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    //mPtr的类型为long?
    mPtr = nativeInit();
}

MessageQueue的构造函数中就调用了native函数,我们看看android_os_MessageQueue.cpp中的实现:

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    //MessageQueue的Native层实体
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    ............
    //这里应该类似与将指针转化成long类型,放在Java层保存;估计Java层使用时,会在native层将long变成指针,就可以操作队列了
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

我们跟进NativeMessageQueue的构造函数:

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    //创建一个Native层的Looper,也是线程唯一的
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

从代码来看,Native层和Java层均有Looper对象,应该都是操作MessageQueue的。MessageQueue在Java层和Native层有各自的存储结构,分别存储Java层和Native层的消息。

2 Native层的looper

我们看看Native层looper的构造函数:

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    //此处创建了个fd
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    .......
    rebuildEpollLocked();
}

在native层中,MessageQueue中的Looper初始化时,还调用了rebuildEpollLocked函数,我们跟进一下:

void Looper::rebuildEpollLocked() {
    // Close old epoll instance if we have one.
    if (mEpollFd >= 0) {
        close(mEpollFd);
    }

    // Allocate the new epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    ............
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    //在mEpollFd上监听mWakeEventFd上是否有数据到来
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    ...........
    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);
        //监听request对应fd上数据的到来
        int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
        ............
    }
}

从native层的looper来看,我们知道Native层依赖于epoll来驱动事件处理。此处我们先保留一下大致的映像,后文详细分析。

二、使用MessageQueue
1 写入消息
Android中既可以在Java层向MessageQueue写入消息,也可以在Native层向MessageQueue写入消息。我们分别看一下对应的操作流程。

1.1 Java层写入消息
Java层向MessageQueue写入消息,依赖于enqueueMessage函数:

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            .....
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            //在头部插入数据,如果之前MessageQueue是阻塞的,那么现在需要唤醒
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                //不是第一个异步消息时,needWake置为false
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

上述代码比较简单,主要就是将新加入的Message按执行时间插入到原有的队列中,然后根据情况调用nativeAwake函数。

我们跟进一下nativeAwake:

void NativeMessageQueue::wake() {
    mLooper->wake();
}

void Looper::wake() {
    uint64_t inc = 1;
    //就是向mWakeEventFd写入数据
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    .............
}

在native层的looper初始化时,我们提到过native层的looper将利用epoll来驱动事件,其中构造出的epoll句柄就监听了mWakeEventFd。
实际上从MessageQueue中取出数据时,若没有数据到来,就会利用epoll进行等待;因此当Java层写入消息时,将会将唤醒处于等待状态的MessageQueue。
在后文介绍从MessageQueue中提取消息时,将再次分析这个问题。

1.2 Native层写入消息
Native层写入消息,依赖于Native层looper的sendMessage函数:

void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) {
    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
    sendMessageAtTime(now, handler, message);
}

void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,
        const Message& message) {
    size_t i = 0;
    {
        AutoMutex _l(mLock);

        //同样需要按时间插入
        size_t messageCount = mMessageEnvelopes.size();
        while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {
            i += 1;
        }

        //将message包装成一个MessageEnvelope对象
        MessageEnvelope messageEnvelope(uptime, handler, message);
        mMessageEnvelopes.insertAt(messageEnvelope, i, 1);

        // Optimization: If the Looper is currently sending a message, then we can skip
        // the call to wake() because the next thing the Looper will do after processing
        // messages is to decide when the next wakeup time should be.  In fact, it does
        // not even matter whether this code is running on the Looper thread.
        if (mSendingMessage) {
            return;
        }
    }
    // Wake the poll loop only when we enqueue a new message at the head.
    if (i == 0) {
        //若插入在队列头部,同样利用wake函数触发epoll唤醒
        wake();
    }
}

以上就是向MessageQueue中加入消息的主要流程,接下来我们看看从MessageQueue中取出消息的流程。

2、提取消息
当Java层的Looper对象调用loop函数时,就开始使用MessageQueue提取消息了:

public static void loop() {
    final Looper me = myLooper();
    .......
    for (;;) {
        Message msg = queue.next(); // might block
        .......
        try {
            //调用Message的处理函数进行处理
            msg.target.dispatchMessage(msg);
        }........
    }
}

此处我们看看MessageQueue的next函数:

Message next() {
    //mPtr保存了NativeMessageQueue的指针
    final long ptr = mPtr;
    .......
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;

    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            //会调用Native函数,最终调用IPCThread的talkWithDriver,将数据写入Binder驱动或者读取一次数据
            //不知道在此处进行这个操作的理由?
            Binder.flushPendingCommands();
        }

        //处理native层的数据,此处会利用epoll进行blocked
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //下面其实就是找出下一个异步处理类型的消息;异步处理类型的消息,才含有对应的执行函数
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }

            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    //完成next记录的存储
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            //MessageQueue中引入了IdleHandler接口,即当MessageQueue没有数据处理时,调用IdleHandler进行一些工作

            //pendingIdleHandlerCount表示待处理的IdleHandler,初始为-1
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                //mIdleHandlers的size默认为0,调用接口addIdleHandler才能增加
                pendingIdleHandlerCount = mIdleHandlers.size();
            }

            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            //将待处理的IdleHandler加入到PendingIdleHandlers中
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            //调用ArrayList.toArray(T[])节省每次分配的开销;毕竟对于Message.Next这样调用频率较高的函数,能省一点就是一点
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                //执行实现类的queueIdle函数,返回值决定是否继续保留
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

整个提取消息的过程,大致上如上图所示。
可以看到在Java层,Looper除了要取出MessageQueue的消息外,还会在队列空闲期执行IdleHandler定义的函数。

2.1 nativePollOnce
现在唯一的疑点是nativePollOnce是如何处理Native层数据的,我们看看对应的native函数:

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    //果然Java层调用native层MessageQueue时,将long类型的ptr变为指针
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    //最后还是进入到Native层looper的pollOnce函数
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {
        .........
    }
}

看看native层looper的pollOnce函数:

//timeoutMillis为超时等待时间。值为-1时,表示无限等待直到有事件到来;值为0时,表示无需等待
//outFd此时为null,含义是:存储产生事件的文件句柄
//outEvents此时为null,含义是:存储outFd上发生了哪些事件,包括可读、可写、错误和中断
//outData此时为null,含义是:存储上下文数据,其实调用时传入的参数
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        //处理response,目前我们先不关注response的内含
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;

                if (outFd != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
            }
        }

        //根据pollInner的结果,进行操作
        if (result != 0) {
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }

        //主力还是靠pollInner
        result = pollInner(timeoutMillis);
    }
}

跟进一下pollInner函数:

int Looper::pollInner(int timeoutMillis) {
    // Adjust the timeout based on when the next message is due.
    //timeoutMillis是Java层事件等待事件
    //native层维持了native message的等待时间
    //此处其实就是选择最小的等待时间
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
         nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
         int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
         if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            timeoutMillis = messageTimeoutMillis;
        }
    }

    int result = POLL_WAKE;
    //pollInner初始就清空response
    mResponses.clear();
    mResponseIndex = 0;

    // We are about to idle.
    mPolling = true;

    //利用epoll等待mEpollFd监控的句柄上事件到达
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // No longer idling.
    mPolling = false;

    // Acquire lock.
    mLock.lock();

    //重新调用rebuildEpollLocked时,将使得epoll句柄能够监听新加入request对应的fd
    if (mEpollRebuildRequired) {
        mEpollRebuildRequired = false;
        rebuildEpollLocked();
        goto Done;
    }

    // Check for poll error.
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ......
        result = POLL_ERROR;
        goto Done;
    }

    // Check for poll timeout.
    if (eventCount == 0) {
        result = POLL_TIMEOUT;
        goto Done;
    }

    for (int i = 0; i < eventCount; i++) {
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                //前面已经分析过,当java层或native层有数据写入队列时,将写mWakeEventFd,以触发epoll唤醒
                //awoken将读取并清空mWakeEventFd上的数据
                awoken();
            } else {
                .........
            }
        } else {
            //epoll同样监听的request对应的fd
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
                //存储这个fd对应的response
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {
                ..........
            }
        }
    }

Done:
    // Invoke pending message callbacks.
    mNextMessageUptime = LLONG_MAX;
    //处理Native层的Message
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            // Remove the envelope from the list.
            // We keep a strong reference to the handler until the call to handleMessage
            // finishes.  Then we drop it so that the handler can be deleted *before*
            // we reacquire our lock.
            {
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();

                //处理Native Message
                handler->handleMessage(message);
            }
            mLock.lock();
            mSendingMessage = false;
            result = POLL_CALLBACK;
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

    // Release lock.
    mLock.unlock();

    //处理带回调函数的response
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;

            //调用response的callback
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd, response.request.seq);
            }

            response.request.callback.clear();
            result = POLL_CALLBACK;
        }
    }
    return result;
}

说实话native层的代码写的很乱,该函数的功能比较多。
如上图所示,在nativePollOnce中利用epoll监听是否有数据到来,然后处理native message、native response。

最后,我们看看如何在native层中加入request。

3 添加监控请求
native层增加request依赖于looper的接口addFd:

//fd表示需要监听的句柄
//ident的含义还没有搞明白
//events表示需要监听的事件,例如EVENT_INPUT、EVENT_OUTPUT、EVENT_ERROR和EVENT_HANGUP中的一个或多个
//callback为事件发生后的回调函数
//data为回调函数对应的参数
int Looper::addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data) {
    return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : NULL, data);
}

结合上文native层轮询队列的操作,我们大致可以知道:addFd的目的,就是让native层的looper监控新加入的fd上是否有指定事件发生。
如果发生了指定的事件,就利用回调函数及参数构造对应的response。
native层的looper处理response时,就可以执行对应的回调函数了。

看看实际的代码:

int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
    ........
    {
        AutoMutex _l(mLock);

        //利用参数构造一个request
        Request request;
        request.fd = fd;
        request.ident = ident;
        request.events = events;
        request.seq = mNextRequestSeq++;
        request.callback = callback;
        request.data = data;
        if (mNextRequestSeq == -1) mNextRequestSeq = 0; // reserve sequence number -1

        struct epoll_event eventItem;
        request.initEventItem(&eventItem);

        //判断之前是否已经利用该fd构造过Request
        ssize_t requestIndex = mRequests.indexOfKey(fd);
        if (requestIndex < 0) {
            //mEpollFd新增一个需监听fd
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            .......
            mRequests.add(fd, request);
        } else {
            //mEpollFd修改旧的fd对应的监听事件
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            if (epollResult < 0) {
                if (errno == ENOENT) {
                    // Tolerate ENOENT because it means that an older file descriptor was
                    // closed before its callback was unregistered and meanwhile a new
                    // file descriptor with the same number has been created and is now
                    // being registered for the first time. 
                    epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
                    .......
                }
                //发生错误重新加入时,安排EpollRebuildLocked,将让epollFd重新添加一次待监听的fd
                scheduleEpollRebuildLocked();
            }
            mRequests.replaceValueAt(requestIndex, request);
        }
    }
}

对加入监控请求的处理,在上文介绍pollInner函数时已做分析,此处不再赘述。

三、总结

1、流程总结

MessageQueue的整个流程包括了Java部分和Native部分,从图中可以看出Native层的比重还是很大的。我们结合上图回忆一下整个MessageQueue对应的处理流程:
1、Java层创建Looper对象时,将会创建Java层的MessageQueue;Java层的MessageQueue初始化时,将利用Native函数创建出Native层的MessageQueue。

2、Native层的MessageQueue初始化后,将创建对应的Native Looper对象。Native对象初始化时,将创建对应epollFd和WakeEventFd。其中,epollFd将作为epoll的监听句柄,初始时epollFd仅监听WakeEventFd。

3、图中红色线条为Looper从MessageQueue中取消息时,处理逻辑的流向。
3.1、当Java层的Looper开始循环时,首先需要通过JNI函数调用Native Looper进行pollOnce的操作。

3.2、Native Looper开始运行后,需要等待epollFd被唤醒。当epollFd等待超时或监听的句柄有事件到来,Native Looper就可以开始处理事件了。

3.3、在Native层,Native Looper将先处理Native MessageQueue中的消息,再调用Response对应的回调函数。

3.4、本次循环中,Native层事件处理完毕后,才开始处理Java层中MessageQueue的消息。若MessageQueue中没有消息需要处理,并且MessageQueue中存在IdleHandler时,将调用IdleHandler定义的处理函数。

图中蓝色部分为对应的函数调用:
在Java层:
利用MessageQueue的addIdleHandler,可以为MessageQueue增加IdleHandler;
利用MessageQueue的enqueueMessage,可以向MessageQueue增加消息;必要时将利用Native函数向Native层的WakeEventFd写入消息,以唤醒epollFd。

在Native层:
利用looper:sendMessage,可以为Native MessageQueue增加消息;同样,要时将向Native层的WakeEventFd写入消息,以唤醒epollFd;
利用looper:addFd,可以向Native Looper注册监听请求,监听请求包含需监听的fd、监听的事件及对应的回调函数等,监听请求对应的fd将被成为epollFd监听的对象。当被监听的fd发生对应的事件后,将会唤醒epollFd,此时将生成对应response加入的response List中,等待处理。一旦response被处理,就会调用对应的回调函数。

2、注意事项
MessageQueue在Java层和Native层有各自的存储结构,可以分别增加消息。从处理逻辑来看,会优先处理native层的Message,然后处理Native层生成的response,最后才是处理Java层的Message。

作者:Gaugamela 发表于2016/9/21 22:37:42 原文链接
阅读:155 评论:0 查看评论

Unity Shaders and Effects Cookbook (D-2) Cull Back背面剔除 -- 模型半边不可见

$
0
0

这两天从其它游戏里面拔一些资源来给美术做参考,碰到很多模型都是只有一半可见,另一半要转动视角才可见。



用的是最简单的Diffuse Shader:

Shader "Custom/MyDiffuse" 
{
	Properties 
	{
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200

		CGPROGRAM
		
		#pragma surface surf Standard

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input 
		{
			float2 uv_MainTex;
		};


		fixed4 _Color;

		void surf (Input IN, inout SurfaceOutputStandard o) 
		{
			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

那这个问题,其实是背面剔除的导致的。

我们知道物体都有两面,一个立方体盒子,有外面和里面之分,对于我们游戏来说,玩家一般只能看到外面,里面其实是不需要的。

所以默认Unity会开启背面剔除,把背对摄像机的面都剔除掉。

转自http://blog.csdn.net/huutu http://www.thisisgame.com.cn

三角面的正面朝向有两种定义方式:

1、顶点的绘制顺序

2、面法线的朝向


我不知道Unity是哪种方式。

但是我可以关闭背面剔除。


首先。

Unity 中的背面剔除有几种模式:

1、Cull Back 剔除背面,这是默认的方式

2、Cull Front 剔除正面

3、Cull Off 关闭剔除


默认是 Cull Back


使用 Cull Front 的话,修改Shader如下

Shader "Custom/MyDiffuse" 
{
	Properties 
	{
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		Cull Front  
		
		CGPROGRAM
		
		#pragma surface surf Standard

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input 
		{
			float2 uv_MainTex;
		};


		fixed4 _Color;

		void surf (Input IN, inout SurfaceOutputStandard o) 
		{
			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

效果转自http://blog.csdn.net/huutu http://www.thisisgame.com.cn


剔除了正面,留下了背面,背面是不受光照的,所以会更暗。


关闭背面剔除,修改Shader 如下

Shader "Custom/MyDiffuse" 
{
	Properties 
	{
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		Cull Off  
		
		CGPROGRAM
		
		#pragma surface surf Standard

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input 
		{
			float2 uv_MainTex;
		};


		fixed4 _Color;

		void surf (Input IN, inout SurfaceOutputStandard o) 
		{
			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

效果转自http://blog.csdn.net/huutu http://www.thisisgame.com.cn



作者:cp790621656 发表于2016/9/21 22:41:44 原文链接
阅读:147 评论:0 查看评论

Android的性能优化(上)

$
0
0

1.Android UI的渲染机制

当我们感觉到的流畅画面,需要的画面帧数要达到40帧到60帧每秒。而一帧的时间大约是16.67ms,换句话说,在1000ms的时间内,16.67ms大约就是现实60帧画面的单位时间。在Android系统中,系统是通过VSYNC信号触发对UI的渲染的,如果系统每次渲染的事件都保持在16.67ms以内,那么我们看到的UI界面将是非常的流畅的,这也就需要我们将所有程序的逻辑都保证在16ms之内,如果不能在16ms内完成绘制,那么就将造成丢帧的现象。即当前该重绘的帧被未处理完成的逻辑阻塞,例如一次绘制任务耗时20ms,那么在16ms系统发出的VSYNC信号就无法绘制,该帧就会被丢弃,等待下次信号擦次开始绘制,这就是画面卡顿的原因。

Android系统提供了检测UI渲染时间的工具,在“开发者选项中”有“GPU呈现模式分析”,选择“在屏幕上显示为条形图”,如下所示(本测试机为魅族,其他手机可能略有不同):
这里写图片描述

每一个条形图都包含有三部分,蓝色部分表示测量绘制Display List的时间,红色代表的是OpenGL渲染Display List所需要的时间,黄色代表的是CPU等待GPU处理的时间,中间的绿色横线代表的是VSYNC时间16ms,需要尽量将所有条形图都控制在这条绿线之下。

2.overDraw

overDraw表示的就是过度绘制,是指在一帧的时间内(16.67ms)像素被绘制了多次,理论上一个像素每次只绘制一次是最优的,但是由于重叠的布局导致一些像素会被多次绘制,而每次绘制都会对应到CPU的一组绘图命令和GPU的一些操作,造成CPU和GPU资源的浪费。在系统默认的绘制Activity的背景,如果再给布局绘制了重叠的背景,那么默认Activity的背景就是无效的过度绘制。

在我们的“开发者选项”中有这样一个检测工具“调用GPU过度绘制”,激活该功能之后可以通过界面上的颜色来判断overDraw的次数。
这里写图片描述
这个工具可以帮助我们检测当前区域的绘制次数,从而优化界面绘图层次,尽量增大蓝色的区域,减少红色的区域。

3.优化布局层次

在Android中,系统对View进行测量,布局和绘制时,都是通过对View数的遍历来进行操作的。如果一个View树的高度太高,就会严重影响到测量,布局和绘制的速度,因此,优化布局的第一个方法就会是降低View树的高度,Google也在API文档中建议View树的高度不宜超过10层。在现在的XML文件的根布局中,我们默认RelativeLayout来替换使用LineraLayout作为默认的根布局,其原因就是通过扁平的RelativeLayout来降低LineraLayout嵌套所产生的布局树的高度,从而提高UI的渲染速度。

1.使用 include 标签重用Layout

在一个应用程序的界面上,为了保持风格的统一,很多界面都会存在共通的UI,比如所说Topbar,Bottombar,Actionbar等等,如果在每一个界面上都进行赋值这一段共通的布局代码,不仅不利于后期代码的维护,还会增加程序的冗余。这时候就可以使用include标签来定义一这一个共通的UI。

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="0dp"
              android:layout_height="0dp"
              android:textSize="28sp"
              android:text="這是共通的UI界面"
              android:gravity="center">
</TextView>

在该共通的布局中,我将layout_width和layout_height设置为0dp,这样就迫使调用者在使用时必须对控件的狂傲进行赋值,否者是无法看见该控件的。

下一步就是如何使用该共通的UI布局了。只需要在使用该共通的UI布局文件中使用include标签的layout属性对这个共通的UI的ID的引用即可。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">


    <include layout="@layout/commen_ui"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textSize="20sp"
        android:text="你好你好你好"/>
</LinearLayout>

这时候如果我们要对布局中的属性进行赋值,就要重新覆盖某一项属性,进行赋值即可。效果如下:
这里写图片描述

2. 使用ViewStub实现view的延迟加载

使用ViewStub标签来实现对一个view的引用并且实现延迟加载。ViewStub是一个非常轻量级的组件,它不仅不可视,而且大小为0,下面来演示如何使用ViewStub来进行实现延迟加载的目的。

首先创建一个布局,这个布局在初始化加载时是不需要显示的,只有在某些情况下才需要进行显示的,例如查看用户信息的时候,只有点击了某一个按钮时,用户详细信息才显示出来。
当运行程序后,我们发现ViewStub中的布局确实没有显示出来,那么要如何才能重新加载显示的布局呢?
首先要通过findViewById()方法找到组件ViewStub。

mStub = (ViewStub) findViewById(R.id.vs_viewstub);
接下来就有两种方式显示这个view:
  1. VISIBLE
    通过调用ViewStub的setVisiblity()方法来显示这个view.代码如下:
mStub.setVisibility(View.VISIBLE);
  1. INFLATE
    通过调用ViewStub的inflate()方法来显示这个view.代码如下:
View inflate = mStub.inflate();

这两种方式都是可以将ViewStub重新进行展开,显示引用的布局,而唯一的区别在于就是inflate()可以返回引用的布局,从而可以通过View.findViewById()方法来找到对应的控件。

View inflate = mStub.inflate();
        TextView textview = (TextView) inflate.findViewById(R.id.textview);
        textview.setText("我是点击后加载的");

注意:不管只用那种方式,一旦ViewStub被设置可见或者是inflate之后,ViewStub就不存在了,不能被反复的inflate,取而代之的就是被inflate的Layout,并将这个Layout的ID重新设置为ViewStub中通过android:inflateId属性所指定的ID,这也就是为什么两次点击之后会报错的原因:如下所示:

Caused by: java.lang.IllegalStateException: ViewStub must have a non-null ViewGroup viewParent

简要说明View.GONE和ViewStub标签的区别是什么?共同点都是初始时都不会显示,但是ViewStub标签只会在显示时才会去渲染整个布局,而View.GONE,在初始化布局树的时候就已经添加在布局树文件中了,相比之下ViewStub的效率会更高。
如下图所示:
这里写图片描述

4.hierarchyviewer.bat

hierarchyviewer.bat是无法在真机上进行使用的,只能在模拟器上使用或者是原生的模拟器上使用,也就是没有加密的设备上。当然真机上也是可以用的,可以到github上下载一个开源项目View Server,下面在模拟器上使用hierarchyviewer.bat。
hierarchyviewer.bat位于sdk\tools目录下,直接双击即可启动,如下:
这里写图片描述

注意:我们在使用hierarchyviewer的时候,一定要是模拟器是打开的,这样才能在hierarchyviewer中看到我们要显示的布局文件。
这里写图片描述

下面我们写一个非常冗余的布局进行显示文件,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="你好啊啊 啊啊啊啊 啊"
                android:textColor="#efff0019"
                android:textSize="20sp"/>
        </LinearLayout>
    </LinearLayout>

</LinearLayout>

这个布局文件是三层LinearLayout嵌套之后里面装了一个button,很显然这些LinearLayout都是冗余的,利用hierarchyviewer可以打开这个布局文件,如下图所示:

这里写图片描述
通常情况下,我们只关注ID为content的Framlayout的分支,这也是setContentView()设置的内容,可以很明显的看出在layout布局文件中(红色布局),这三层LinearLayout没有任何的分支,说明是冗余嵌套,可以直接去掉的。

当点击其中一个view的时候,可以显示view的绘制情况的,不过第一次点击的时候各种显示的事件都是n/a,需要点击菜单中的Profile Node按钮重新进行计算,才能回去到绘制信息。在系统的右下方会给出不同颜色的小圆点,用来表示绘制的效率,绿黄红分别代表的是好中差的绘制效率。
这里写图片描述

作者:qq_24304811 发表于2016/9/22 0:03:47 原文链接
阅读:124 评论:0 查看评论

工作第十周:干货太多脑子不够用怎么办

$
0
0

上周中秋,闹得最凶的就是“阿里程序员脚本抢月饼被开除事件”。

作为程序猿我是同情他们的,觉得阿里小题大做;

但换到公司角度,这种事还是防微杜渐比较好。

这里写图片描述

上一周:

1.首先是拔牙,680 一颗智齿,关键我还忘了带回家,心疼。

2.中秋休息,朋友来上海玩,正好赶上下雨,于是几个人除了胡吃海喝就是网吧开黑,最后一天天气好了点,才去了趟外滩。

看着江水,憧憬着未来。

这里写图片描述

3.不要过于注重程序的 “设计模式”

某位著名的架构师说:

代码如恶魔,在你完成编码后,应回头并且优化它。从长远来看,这里或那里一些的改进,会让后来的支持人员更加轻松。

在学习了一些设计模式之后,我看见代码就想优化。

有一天遇到一个问题,一个自定义 View ,业务逻辑也写在里面。新的需求里也用到了这个 View ,但数据和业务逻辑不一样,直接复制粘贴修改逻辑不太好。

我想到了适配器模式,业务跟视图分离,然后抽成一个可复用的 View,具体业务在 Activity 里实现。

结果在修改老代码的时候,发现这个 Activity 已经好几百行了,如果用我写的自定义 View ,Activity 里还得增加一两百行,变得更臃肿了。在复用 View 和 Activity 之间我犹豫了好久,最后终于决定不修改老代码了。

非著名程序猿小张说:

不要过于注重程序的 “设计模式”。有时候,写的简单直观点,要比引入某种模式更有助于项目演进。在多数情况下,程序代码应是简单易懂,甚至小白也能看懂。

尽量做到强拓展多复用。但是如果某个类复用的代价是需要在本来就很臃肿的 Activity 中添加更多代码,还是考虑清楚再做吧。

6.0 M 全名:marshmallow 棉花糖

7.0 N 全名:Nougat 牛轧糖

技术上的收获

1.返回数据解析错误
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path

期望返回一个对象但是却返回了一个数组,解决办法:

修改期望返回数据为 LIst<该对象>,这样才能解析到数据。

2.使用 rebase 进行代码提交、合并:

  1. git commit -m “提交信息”
  2. git rebase branch-name
  3. git pull –rebase
  4. git push

3.git pull 和 git fetch 区别
http://blog.csdn.net/hudashi/article/details/7664457

  • git pull 等于 git fetch + git merge,拉下来直接合并
  • git fetch 更安全,可以拉下来后看情况再合并
git pull origin master

等价于

git fetch origin master:tmp
git diff tmp 
git merge tmp

先拉去 master 分支代码到一个新分支 temp
然后对比当前分支跟 temp 区别
最后决定合并

4.fast-forward , –no–ff 和 squash 区别
https://segmentfault.com/q/1010000002477106

a.fast-forward 快速前进,即合并时如果没有问题直接把 HEAD 指针指向最新,把旧提交分支指向新提交内容的末端,移动指针而不多进行一次提交,是为快速提交。是默认的 git merge 方式。
- 优点:提交历史看起来是直线,不乱
- 缺点:没有提交历史,如果删除分支,会丢失分支信息

b.–no–ff 即不使用 fast-forward,虽然难看点,有了分歧,但是保留了提交历史。

这里写图片描述

c.squash 把多个提交历史合并成一个。

5 ListView RecyclerView 复用的注意事项:

  • 使用 tag 存储 item 状态,根据状态是否与当前 item 一致来设置属性
  • 确定变化的和不变的,不变的状态值在 getView 外边创建一个集合保存
  • 终极大招:将动态变化的状态值跟数据绑定,避免 tag 由于 view 复用后无用的 bug

6.堆,栈,常量池,静态域
http://blog.csdn.net/miraclestar/article/details/6039743#comments

  • 栈:存放基本类型数据和对象的引用。
    数据的生命周期可以确定,没有引用指向数据时,就会消失;
  • 堆:存放所有 new 出来的对象。
    堆中的对象由垃圾回收期负责回收,生命周期不确定;
  • 常量池:存放字符串常量和基本数据类型常量( public static final);
  • 静态域:存放静态变量 (static)。

7.字符串 加深理解:

String s1 = "china";  
String s2 = "china";  
String s3 = "china";  
String ss1 = new String("china");  
String ss2 = new String("china");  
String ss3 = new String("china"); 
  • 直接用双引号包围的常量 “china”,存储在常量池中
  • 引用 s1, s2, s3, ss1… 存储在栈中
  • new String(“china”) 会创建一个在堆中、指向常量池的对象
  • 对于 equals 相等的字符串,在常量池中永远只有一份,在堆中有多份
  • 对于通过new产生一个字符串(假设为”china”)时,会先去常量池中查找是否已经有了”china”对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此”china”对象的拷贝对象。

    这里写图片描述

8.final 修饰的变量一定不会改变吗?

答:

  • final 修饰的变量如果是基本类型,那这个变量在初始化后就不可改变。
  • 如果修饰的是引用,初始化之后这个对象不能再指向其他对象(hashcode 不变)。但如果原配对象是可变类型(比如 StringBuilder 可以改变),那这个 引用虽然被 final 修饰,指向的对象改变后,这个引用的值还是会变的。

9.as 快捷操作
http://www.jianshu.com/p/bc8f6bfe12c6

  • as 多行编辑: 按住alt键+鼠标左键一直向下拉,就可以选中多行,然后编辑就可以同时对多行进行编辑,编辑完相同部分再分别输入不同部分就ok了。
    要修改还是 alt + 左键选取。
  • 重构的时候,要抽取出一些语句(Java 代码到一个方法里、xml 布局到一个 include 文件),windows 可以选中要提取部分,按 alt+shift+m,填写方法名称即可自动创建。
  • 提取sytle,layout等并没有默认的快捷键。打开keymap。找到Extract,根据自己的喜好了来自定义快捷键.

10.跟小伙伴在不同 git 分支同时进行开发,想对比两个分支差异怎么办?
看这里 http://blog.csdn.net/u011240877/article/details/52586664

总结

每天打开稀土、gank、微信公众号,到处都是值得学习的内容,要学习的东西太多了,一不小心从一个链接跳到另一个链接又到另一个。

浏览器打开一堆网页,都是心理安慰,没几个认真看完,到下班时挨个依依不舍的关闭,我对不起你们啊!浏览器书签都快挤到爆炸,干货太多脑子不够用啊啊啊!!

一个网友推荐了勺子,一个挺好的知识管理工具,保存书签也挺方便。

这里写图片描述

万事俱备,只差看了 !

这里写图片描述

作者:u011240877 发表于2016/9/22 0:26:33 原文链接
阅读:133 评论:0 查看评论

Android开发基础规范(二)

$
0
0

转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52614696

前言:Android中一些开发规范,避免给自己和别人少留坑。

二、代码相关

2.2.15 Field Ordering 属性排序
在类文件顶部声明的任何属性都应该按下列的排序规则进行排序:

  • 1.Enums (枚举类型)
  • 2.Constants (常量)
  • 3.Dagger Injected fields (Dagger注入的属性)
  • 4.Butterknife View Bindings (Butterknife绑定的view)
  • 5.private global variables (private成员变量)
  • 6.public global variables (public成员变量)
    例如:
//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
  public static enum {
      ENUM_ONE, ENUM_TWO
  }

  public static final String KEY_NAME = "KEY_NAME";
  public static final int COUNT_USER = 0;

  @Inject SomeAdapter mSomeAdapter;

  @BindView(R.id.text_name) TextView mNameText;
  @BindView(R.id.image_photo) ImageView mPhotoImage;

  private int mUserCount;
  private String mErrorMessage;

  public int mSomeCount;
  public String mSomeString;

使用上述的排序规则有助于保持字段声明的分组,从而增加字段的可读性

2.2.16 Class member ordering 类成员排序

为了提高代码的可读性,组织类成员在一个符合逻辑的方式中是非常的重要,请按下列的排序方式去实现:

  • 1.Constants
  • 2.Fields
  • 3.Constructors
  • 4.Override methods and callbacks (public or private)
  • 5.Public methods
  • 6.Private methods
  • 7.Inner classes or interfaces

例如:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
public class MainActivity extends Activity {
      private int mStepCount;
      public static newInstance() { }
      @Override
      public void onCreate() { }
      public void setColor(Color color) { }
      private int getId() { }
      static class AnInnerClass { }
      interface SomeInterface { }
}

在Android框架中任何生命周期的方法应该在其相应的生命周期中排序,例如:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
public class MainActivity extends Activity {

      // Field and constructors

      @Override
      public void onCreate() { }

      @Override
      public void onStart() { }

      @Override
      public void onResume() { }

      @Override
      public void onPause() { }

      @Override
      public void onStop() { }

      @Override
      public void onRestart() { }

      @Override
      public void onDestroy() { }

     // public methods, private methods, inner classes and interfaces
  }

2.2.17 Method parameter ordering 方法的参数排序
当定义方法时,参数应该按照下列的规则排序:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
public Post loadPost(Context context, int postId);
public void loadPost(Context context, int postId, Callback callback);

Context上下文参数应放在第一位,并且Callback回调参数放置在最后

2.2.18 String constants, naming, and values 字符串常量、命名和值
当使用字符串常量时,其应该修饰为静态final并且遵循下列规则:

2.2.19 Enums 枚举
枚举的使用仅仅在实际需要用到时。如果另外一种方法可行,此时应该选择更好的方式去实现它,例如:
相对于下面这样:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
public enum SomeEnum {
    ONE, TWO, THREE
}

更推荐这样做:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
  private static final int VALUE_ONE = 1;
  private static final int VALUE_TWO = 2;
  private static final int VALUE_THREE = 3;

2.2.20 Arguments in fragments and activities

在fragment和activity中的参数
当我们使用Intent或者Bundle传递数据时,值的键必须使用下面定义的约定:

  • Activity
    • 传递数据到一个activity必须使用一个KEY的引用,像下面这样定义:
      private static final String KEY_NAME = “com.package.name.activity.KEY_NAME”;
  • Fragment
    • 传递数据到一个fragment必须使用一个EXTRA的引用,像下面这样定义:
      private static final String EXTRA_NAME = “EXTRA_NAME”;

当创建fragment或者activity的新实例涉及到传递数据时,我们应该提供一个静态的方法来获取新的实例,传递的数据作为参数。例如:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
//Activity中
public static Intent getStartIntent(Context context, Post post) {
      Intent intent = new Intent(context, CurrentActivity.class);
      intent.putParcelableExtra(EXTRA_POST, post);
      return intent;
}

//Fragment中
public static PostFragment newInstance(Post post) {
      PostFragment fragment = new PostFragment();
      Bundle args = new Bundle();
      args.putParcelable(ARGUMENT_POST, post);
      fragment.setArguments(args)
      return fragment;
}

2.2.21 Line Length Limit 行长度限制
代码的行数字符长度最好不要超过100个字符,这样代码的可读性会更高。有时为了实现上述要求,我们需要做的:

  • 1.提取数据到一个局部变量
  • 2.提取代码逻辑到外部的方法
  • 3.将一段较长的代码换行显示
  • 注意:对于代码的注释和导入声明,超过100个字符的限制是可以的。

2.2.21.1 Line-wrapping techniques 换行技巧
当涉及到换行时,有一些情况我们应该保持与格式化代码的一致性。

  • 运算符换行
    当我们需要在一个运算公式换行时,需要在运算符前面换行:
//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
  int count = countOne + countTwo - countThree + countFour * countFive - countSix
          + countOnANewLineBecauseItsTooLong;

如果需要,你可以直接在“=”后换行:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
  int count =
          countOne + countTwo - countThree + countFour * countFive + countSix;
  • 方法链
    当涉及到方法链时(这个比较流行,建议写方法链,RxJava几乎都是),每个方法的调用都应该另起一行:
    不要下面这样:
//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
  Picasso.with(context).load("someUrl").into(imageView);
取而代之应这样:
  Picasso.with(context)
          .load("someUrl")
          .into(imageView);
  • 长参数
    对于一个含有长参数的方法,我们在适当的情况下应该换行。例如当声明一个方法时我们应该在最后一个参数的逗号处换行:
//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
  private void someMethod(Context context, 
                          String someLongStringName, 
                          String text,long 
                          thisIsALong, String anotherString) {               
  }     

当调用这个方法时,我们应该在每个参数的逗号后面换行:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
  someMethod(context,
          "thisIsSomeLongTextItsQuiteLongIsntIt",
          "someText",
          01223892365463456,
          "thisIsSomeLongTextItsQuiteLongIsntIt");

2.2.22 Method spacing(方法间间距)
在同一个类中,方法与方法之间只需要留有一行的空白,如下:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
public String getUserName() {
    // Code
}

public void setUserName(String name) {
    // Code
}

public boolean isUserSignedIn() {
    // Code
}

2.2.23 Comments(注释)

  • 2.2.23.1 Inline comments(行内注释)
    必要的时候,写注释,其他情况下最好不要写注释,从方法名或者成员变量上就能看出做什么。

  • 2.2.23.2 JavaDoc Style Comments(java文档的注释风格)
    方法的名字应该起的和该方法的功能相对应,有时可以提供JavaDoc风格的注释。方法名起的好会帮助读者更好的理解方法的功能,同时也会让使用者明白传入方法中参数的作用。

/**
 * Authenticates the user against the API given a User id.
 * If successful, this returns a success result
 *
 * @param userId The user id of the user that is to be authenticated.
 */
 public class XXX {

 }
  • 2.2.23.3 Class comments(类注释)
    在创建类注释时,它们应该是有意义的,有描述性的,必要的时候使用超链接。如下:
/**
  * RecyclerView adapter to display a list of {@link Post}.
  * Currently used with {@link PostRecycler} to show the list of Post items.
  */
public class RecyclerView {

}

不要写初创作者信息,因为以后会有很多人在这个类上改来改去,写上作者信息是没有任何意义的。

/**
* Created By yuiop 22/09/2016
*/
public class XXX {

}

2.2.24 Sectioning code(分段代码)
2.2.24.1 Java code(java代码)
如果对代码做了“分段”,应该使用下面的方法完成,如下:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
public void method() { }

public void someOtherMethod() { }

/********* MVP Method Implementations  ********/

public void anotherMethod() { }

/********* Helper Methods  ********/

public void someMethod() { }

不能像下面这样:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
public void method() { }

public void someOtherMethod() { }

// Mvp Method Implementations

public void anotherMethod() { }

这样会更容易定位类中方法。

2.2.24.2 Strings file(字符串文件)
字符串资源文件string.xml中分段注释如下:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
// User Profile Activity
<string name="button_save">Save</string>
<string name="button_cancel">Cancel</string>

// Settings Activity
<string name="message_instructions">...</string>

这样写不仅可以让string文件看起来整洁,还能在需要更改它们时更容易找到。

2.2.24.3 RxJava chaining(RxJava链接)
当进行异步操作时,每一步操作都应该在遇到“.”号之前另起一行,如下:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
return dataManager.getPost()
            .concatMap(new Func1<Post, Observable<? extends Post>>() {
                @Override
                 public Observable<? extends Post> call(Post post) {
                     return mRetrofitService.getPost(post.id);
                 }
            })
            .retry(new Func2<Integer, Throwable, Boolean>() {
                 @Override
                 public Boolean call(Integer numRetries, Throwable throwable) {
                     return throwable instanceof RetrofitError;
                 }
            });

这样会使读者更容易理解接下来的异步操作。
2.2.25 Butterknife(Butterknife)
2.2.25.1 Event listeners(事件监听者)
如有可能,尽量使用ButterKnife绑定监听。举个栗子,可以用ButterKnife替换传统的点击事件:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
mSubmitButton.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        // Some code here...
    }
  };

换成如下:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
@OnClick(R.id.button_submit)
public void onSubmitButtonClick() { }

2.3 XML Style Rules(XML文件中样式规则)
2.3.1 Use self=-closing tags(使用单标记)
在xml布局中,如果一个viwe没有任何子view,那么就应该使用单标记。
用这个:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
<ImageView
    android:id="@+id/image_user"
    android:layout_width="90dp"
    android:layout_height="90dp" />

不用这个:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
<ImageView
    android:id="@+id/image_user"
    android:layout_width="90dp"
    android:layout_height="90dp">
</ImageView>

2.3.2 Resource naming(资源命名)
所有的资源命名规则都应该是小写和下划线的组合,如下:
2.3.2.1 ID naming(id命名)
所有的id命名规则都应该用元素作为前缀。

Element Prefix
ImageView image_
Fragment fragment_
RelativeLayout layout_
Button button_
TextView text_
View view_

例如:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
<TextView
    android:id="@+id/text_username"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

注意:如果一个布局中一种类型的view只有一种,比方toolbar,那么可以直接起名叫toolbar

2.3.2.2 Strings(字符串)
所有的字符串名字应该以该应用的当前功能页面作为前缀,如下:

Screen String ResourceName
Registration Fragment “Register now” registration_register_now
Sign Up Activity “Cancel” sign_up_cancel
Rate App Dialog “No thanks” rate_app_no_thanks

如果没法像上面一样命名,咱们可以用下面的方法:

Prefix Description
error_ Used for error messages
title_ Used for dialog titles
action_ Used for option menu actions
msg_ Used for generic message such as in a dialog
label_ Used for activity labels

需要注意以下两点:

  • 1、同一个的字符串资源不能在多个文件中共享使用。如果其中的一个页面字符串发生改变也会造成另一个页面的改变从而产生问题。每个页面使用单独的字符串资源会给将来省去很多麻烦。
  • 2、字符串资源必须放在字符串资源文件中,不能写在布局或者类中。

2.3.2.3 Styles and themes
当定义style和theme时,每个单词应该大写开头。如下:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
AppTheme.DarkBackground.NoActionBar
AppTheme.LightBackground.TransparentStatusBar
ProfileButtonStyle
TitleTextStyle

2.3.3 Attributes ordering(属性排序)
定义属性不能只为了看起来整洁,同时能够在布局中快速找到属性位置。以下是基本规则:

  • 1、viwe的id
  • 2、style
  • 3、布局的宽高
  • 4、其他的布局属性,按照字母顺序排序
  • 5、其他的属性,按照字母顺序排序

如下:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
<Button
    android:id="@id/button_accept"
    style="@style/ButtonStyle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_alignParentStart="true"
    android:padding="16dp"
    android:text="@string/button_skip_sign_in"
    android:textColor="@color/bluish_gray" />

注意:在Android studio中快速格式化快捷键是:cmd + shift + L
这样做,当布局文件发生变化时,可以通过xml属性快速定位。

本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52614696

2.4 Tests style rules(测试风格规则)
2.4.1 Unit tests(单元测试)
所有测试类起名字都应该和他们被测试的类相对应,并且以Test作为后缀,如下:

Class Test Class
DataManager DataManagerTest
UserProfilePresenter UserProfilePresenterTest
PreferencesHelper PreferencesHelperTest

所有的测试方法应该用@Test进行注释,测试方法应该用下面的模板:

@Test
public void methodNamePreconditionExpectedResult() { }

举例,如果我们想测试一个使用不正确邮箱登录的功能,测试方法应该使用如下的:

@Test
public void signUpWithInvalidEmailFails() { }

测试应该将重点放在测试方法赋予的功能名称上面,如果在你的测试方法中还有别的情况需要考虑,这些额外的需要测试的情况应该分到它专门的测试方法中。
如果一个类中包含许多不同的方法,测试应该在多个测试类中进行拆分-这样有助于测试更易于维护和定位。例如,一个数据库工具类有时候会分解成如下几个测试类:

DatabaseHelperUserTest
DatabaseHelperPostsTest
DatabaseHelperDraftsTest

2.4.2 Espresso tests(功能测试框架Espresso)
每个Espresso测试类一般都对应一个Activity,所以命名时应该和对应的Activity相一致,其次是测试,如下:

Class Test Class
MainActivity MainActivityTest
ProfileActivity ProfileActivityTest
DraftsActivity DraftsActivityTest

当使用Espresso API的时候,方法应该换行从而可以让声明更易读,举例如下:

onView(withId(R.id.text_title))
        .perform(scrollTo())
        .check(matches(isDisplayed()))

这种风格的链接调用不仅可以让我们每行不超过100个字符,同时也可以让Espresso测试中的链接更加易读。

Gradle Style(Gradle风格)

3.1 Dependencies(依赖)

3.1.1 Versioning
如果一个版本号在多个依赖中多被使用,那么应该在依赖的范围内定义成一个变量,如下:

//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
final SUPPORT_LIBRARY_VERSION = '23.4.0'
compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:percent:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:customtabs:$SUPPORT_LIBRARY_VERSION"

将来如果需要更新依赖,那么只需要更新版本号的变量就可以很轻松的控制所有依赖的版本号。

3.1.2 Grouping(分组)

  • 依赖应该以包名来分组,各个组之间应该有一定的间隙,如下:
//create by 逆流的鱼yuiop on 2016/9/22
//blog地址:http://blog.csdn.net/hejjunlin
compile "com.android.support:percent:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:customtabs:$SUPPORT_LIBRARY_VERSION"

compile 'io.reactivex:rxandroid:1.2.0'
compile 'io.reactivex:rxjava:1.1.5'

compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.jakewharton.timber:timber:4.1.2'

compile 'com.github.bumptech.glide:glide:3.7.0'

Compile、testCompile、androidTestCompile依赖同样应该分组到相对应的组别中,如下:

// App Dependencies
compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION"

// Instrumentation test dependencies
androidTestCompile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION"

// Unit tests dependencies
testCompile 'org.robolectric:robolectric:3.0'

这两种方法都可以很容易的找到特定的依赖关系,需要时,它保证依赖的声明既干净又整洁。

3.1.3 Independent Dependencies(独立的依赖关系)

  • 依赖只能应用在应用或者目的测试中,确保使用compile,testCompile,androidTestCompile来编译它们。例如,robolectric依赖只能被用来做单元测试,它应该如下:
testCompile 'org.robolectric:robolectric:3.0'

第一时间获得博客更新提醒,以及更多android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。

这里写图片描述

如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易

作者:hejjunlin 发表于2016/9/22 2:03:07 原文链接
阅读:135 评论:0 查看评论

Android NDk开发系列(Hello JNI)

$
0
0

前言

本篇博客主要记录NDK开发之入门小demo,虽说NDK开发包里面有hellojni的项目,但是博主还是记录一下学习的过程吧.AS2.2现在对NDK支持的已经很好了,但是博主学习NDK还是采用eclipse作为工具,学的是一个思路,工具只是使用方式不同,所以不用纠结工具的事情了哦

1.首先创建一个普通的Android项目



创建好我们的工程之后呢,我们需要先书写一个本地方法来使用JNI

xml布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}" >

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="bt_click"
        android:text="@string/hello_world" />

</RelativeLayout>

Activity代码

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	// 按钮的点击事件处理
	public void bt_click(View v) {
		Button bt = (Button) v;
		bt.setText(getString());
	}

	/**
	 * 调用c代码返回一个字符串
	 * 
	 * @return
	 */
	private native String getString();

}

可以看到我们的代码很简单,点击按钮触发bt_click事件,然后获取本地方法返回的字符串,然后显示在按钮上,这时候我们的app层面的代码已经写好了,本地方法是需要我们使用c代码去实现的

添加本地的支持


右键项目添加本地支持


这个是帮你创建的动态链接库的名称,也就是我们经常使用的第三方sdk中经常看到的.so文件,点击确定

确定以后你会发现在项目中多了一个文件夹jni,里面还有一个.cpp的文件,还有个Android.mk文件


我们更改.cpp后缀为.c,然后打开这个文件


里面没有任何的代码,这是需要我们自己去写Activity中那个本地方法的实现的

实现本地方法

使用javah命令生成头文件

右键项目拿到项目的目录


来到项目的目录下面,并且进入src目录


使用cmd进入该目录


生成头文件


后面的是你的activity的包名+Activity名称

成功就是不提示任何信息,如果你的activity中有中文会失败,提示不可映射的字符

成功之后在src目录中生成了一个头文件


我们打开它,复制其中的实现方法


红色框框里面的就是我们需要复制的代码,然后我们复制到我们的.c文件中,去掉最后的封号,加上一对花括号,然后在括号中加上变量的名字,然后我们返回一个字符串

#include <jni.h>


JNIEXPORT jstring JNICALL Java_com_xiaojinzi_jnidemo_MainActivity_getString
  (JNIEnv * env, jobject jb){

	return (*env) ->NewStringUTF(env,"welcome to JNI world");

}

实现方法必须是以下的格式:Java_包名_类名_方法名

NewStringUTF是一个JNI中的函数,现在可以先不管,知道他能返回一个字符串就行了,内容是后面的

"welcome to JNI world"

这时候我们的实现方法写好了,然后我们需要生成.so文件

编辑Application.mk文件

在jni文件夹中新建一个Application.mk文件,里面的内容为

APP_ABI := all

就一句话,是表示生成支持所有平台的.so文件

修改Android.mk

注意还有一点,打开我们的Android.mk文件


这里说明了实现的文件是哪个,我们刚刚改成了.c文件,所以这里需要改成JniDemo.c


在Activity中加载这个库

最后我们在activity中加载这个库,就可以运行项目啦

public class MainActivity extends Activity {
	
	static{
		System.loadLibrary("JniDemo");
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	public void bt_click(View v) {
		Button bt = (Button) v;
		bt.setText(getString());
	}

	private native String getString();

}

最后项目成功运行


作者:u011692041 发表于2016/9/23 22:18:53 原文链接
阅读:183 评论:1 查看评论

Android性能提升之强引用、软引用、弱引用、虚引用使用

$
0
0

转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52637333

背景:收到公众投稿,《从面试题中看Java的Reference(引用)》,分析的很不错,总感觉少了实际的例子和应用场景。于是结合自己工作中场景,小总结一下。看下Agenda如下:

  • 强引用
  • 软引用
  • 弱引用
  • 什么时候使用软引用,什么时候使用弱引用?
  • 虚引用

一、强引用

Java中的引用,类似于C++的指针。通过引用,可以对堆中的对象进行操作。在某个函数中,当创建了一个对象,该对象被分配在堆中,通过这个对象的引用才能对这个对象进行操作。

这里写图片描述

假设以上代码是在方法内运行的,那么局部变量str将被分配在栈空间上,而对象StringBuffer实例,被分配在堆空间中。局部变量str指向StringBuffer实例所在的堆空间,通过str可以操作该实例,那么str就是StringBuffer的引用。

这里写图片描述

此时,运行一个赋值语句:

这里写图片描述

那么,str所指向的对象也将被str1所指向,同时在局部栈空间上会分配空间存放str1变量。此时,该StringBuffer实例就有两个引用。对引用的”==”操作用于表示两个操作数所指向的堆空间地址是否相同,不表示两个操作数所指向的对象是否相等。

这里写图片描述

强引用特点:

  • 强引用可以直接访问目标对象。
  • 强引用所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。
  • 强引用可能导致内存泄露。

二、软引用

软引用是除了强引用外,最强的引用类型。可以通过java.lang.ref.SoftReference使用软引用。一个持有软引用的对象,不会被JVM很快回收,JVM会根据当前堆的使用情况来判断何时回收。当堆的使用率临近阈值时,才会回收软引用的对象。
看下我工作中使用到软引用的场景,加载一个1080x1920分辨率的图,约900多K, 对于我们来说,这个图已是非常大了。

这里写图片描述

  • 首先通过BitmapFactory.decodeStream构造一个大图bitmap
  • 然后把这个bitmap转成Drawble类型,构成强引用。
  • 接着使用SoftReference构造这个drawable对象的软引用drawables.
  • 最后通过软引用的get()方法,取得drawable对象实例的强引用,发现对象被未回收。在GC在内存充足的情况下,不会回收软引用对象。

  • 在实际中,一起请求很多相关图片,从网络,这时就会请求非常多的内存空间,导致内存吃紧,系统开始会GC。这次GC后,drawables.get()不再返回Drawable对象,而是返回null,这时屏幕上背景图不显示,说明在系统内存紧张的情况下,软引用被回收。

  • 使用软引用以后,在OutOfMemory异常发生之前,这些缓存的图片资源的内存空间可以被释放掉的,从而避免内存达到上限,避免Crash发生。

  • 需要注意的是,在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现NullPointerException异常导致应用崩溃。

到底什么时候使用软引用,什么时候使用弱引用呢?

个人认为,如果只是想避免OutOfMemory异常的发生,则可以使用软引用。如果对于应用的性能更在意,想尽快回收一些占用内存比较大的对象,则可以使用弱引用。

还有就是可以根据对象是否经常使用来判断。如果该对象可能会经常使用的,就尽量用软引用。如果该对象不被使用的可能性更大些,就可以用弱引用。

另外,和弱引用功能类似的是WeakHashMap。WeakHashMap对于一个给定的key,其映射的存在并不阻止垃圾回收器对该键的回收,回收以后,其条目从映射中有效地移除。WeakHashMap使用ReferenceQueue实现的这种机制。

三、 弱引用

弱引用是一种比软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。但是,由于垃圾回收器的线程通常优先级很低,因此,并一不定能很快的发现持有弱引用的对象。这种情况下,弱引用对象可以存在较长的一段时间。一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册引用队列中。
看一个工作中实例:播放器的播放Panel,是一个View,就是在视频播放时,可以show、hide, 也可以拖拽进度条之类,还有上面的音量,亮度调节等。这样一个view,我们用弱引用,因为在视频播放过程中,不论硬解还是软解,都将占用大量内存。保证视频的渲染效果。
在VideoControllerView.java 有如下一段代码:

这里写图片描述

在GC之前,弱引用对象并未被垃圾回收器发现,因此通过mView.get()方法可以取得对应的强引用。但是只要进行垃圾回收,弱引用对象一旦被发现,便会立即被回收,并加入注册引用队列中。此时,再次通过mView.get()方法取得强引用就会失败。

注意:软引用,弱引用都非常适合来保存那些可有可无的缓存数据。如果这样做,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间。

四、 虚引用

  • 虚引用是所有引用类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。
  • 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,销毁这个对象,奖这个虚引用加入引用队列。
  • 实际中几乎没用,暂不介绍。

最后一张图总结下:

这里写图片描述

第一时间获得博客更新提醒,以及更多android干货,源码分析,欢迎关注我的微信公众号,扫一扫下方二维码或者长按识别二维码,即可关注。

这里写图片描述

如果你觉得好,随手点赞,也是对笔者的肯定,也可以分享此公众号给你更多的人,原创不易

作者:hejjunlin 发表于2016/9/23 22:48:30 原文链接
阅读:646 评论:0 查看评论

一篇教程读懂微信应用号开发

$
0
0

开始开发应用号之前,先看看官方公布的「小程序」教程吧!(以下内容来自微信官方公布的「小程序」开发指南)

本文档将带你一步步创建完成一个微信小程序,并可以在手机上体验该小程序的实际效果。这个小程序的首页将会显示欢迎语以及当前用户的微信头像,点击头像,可以在新开的页面中查看当前小程序的启动日志。

1. 获取微信小程序的 AppID

首 先,我们需要拥有一个帐号,如果你能看到该文档,我们应当已经邀请并为你创建好一个帐号。注意不可直接使用服务号或订阅号的 AppID。 利用提供的帐号,登录 https://mp.weixin.qq.com ,就可以在网站的「设置」-「开发者设置」中,查看到微信小程序的 AppID 了。

一篇教程读懂微信应用号开发0

注意:如果我们不是用注册时绑定的管理员微信号,在手机上体验该小程序。那么我们还需要操作「绑定开发者」。即在「用户身份-开发者」模块,绑定上需要体验该小程序的微信号。本教程默认注册帐号、体验都是使用管理员微信号。

2. 创建项目

我们需要通过开发者工具,来完成小程序创建和代码编辑。

开发者工具安装完成后,打开并使用微信扫码登录。选择创建「项目」,填入上文获取到的 AppID,设置一个本地项目的名称(非小程序名称),比如「我的第一个项目」,并选择一个本地的文件夹作为代码存储的目录,点击「新建项目」就可以了。

为方便初学者了解微信小程序的基本代码结构,在创建过程中,如果选择的本地文件夹是个空文件夹,开发者工具会提示,是否需要创建一个 quick start 项目。选择「是」,开发者工具会帮助我们在开发目录里生成一个简单的 demo。

一篇教程读懂微信应用号开发1

项目创建成功后,我们就可以点击该项目,进入并看到完整的开发者工具界面,点击左侧导航,在「编辑」里可以查看和编辑我们的代码,在「调试」里可以测试代码并模拟小程序在微信客户端效果,在「项目」里可以发送到手机里预览实际效果。

3. 编写代码

点 击开发者工具左侧导航的「编辑」,我们可以看到这个项目,已经初始化并包含了一些简单的代码文件。最关键也是必不可少的,是 app.js、app.json、app.wxss 这三个。其中,.js 后缀的是脚本文件,.json 后缀的文件是配置文件,.wxss 后缀的是样式表文件。微信小程序会读取这些文件,并生成小程序实例。

下面我们简单了解这三个文件的功能,方便修改以及从头开发自己的微信小程序。

app.js 是小程序的脚本代码。我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量。调用 MINA 提供的丰富的 API,如本例的同步存储及同步读取本地数据。

一篇教程读懂微信应用号开发2

app.json 是对整个小程序的全局配置。我们可以在这个文件中配置小程序是由哪些页面组成,配置小程序的窗口  背景色,配置导航条样式,配置默认标题。注意该文件不可添加任何注释。

一篇教程读懂微信应用号开发3

app.wxss 是整个小程序的公共样式表。我们可以在页面组件的class属性上直接使用app.wxss中声明的样式规则。

一篇教程读懂微信应用号开发4

3. 创建页面

在 这个教程里,我们有两个页面,index 页面和 logs 页面,即欢迎页和小程序启动日志的展示页,他们都在 pages 目录下。微信小程序中的每一个页面的【路径+页面名】都需要写在 app.json 的 pages 中,且 pages 中的第一个页面是小程序的首页。

每一个小程序页面是由同路径下同名的四个不同后缀文件的组成,如:index.js、 index.wxml、index.wxss、index.json。.js 后缀的文件是脚本文件,.json 后缀的文件是配置文件,.wxss 后缀的是样式表文件,.wxml 后缀的文件是页面结构文件。

index.wxml是页面的结构文件:

一篇教程读懂微信应用号开发5

本例中使用了 <view/>、<image/>、<text/>来搭建页面结构,绑定数据和交互处理函数。

index.js 是页面的脚本文件,在这个文件中我们可以监听并处理页面的生命周期函数、获取小程序实例,声明并处理数据,响应页面交互事件等。

一篇教程读懂微信应用号开发6

index.wxss是页面的样式表:

一篇教程读懂微信应用号开发7

页面的样式表是非必要的。当有页面样式表时,页面的样式表中的样式规则会层叠覆盖 app.wxss 中的样式规则。如果不指定页面的样式表,也可以在页面的结构文件中直接使用 app.wxss 中指定的样式规则。

index.json是页面的配置文件:

页面的配置文件是非必要的。当有页面的配置文件时,配置项在该页面会覆盖 app.json 的 window 中相同的配置项。如果没有指定的页面配置文件,则在该页面直接使用 app.json 中的默认配置。

logs的页面结构

一篇教程读懂微信应用号开发8

logs 页面使用 <block/> 控制标签来组织代码,在 <block/> 上使用 wx:for-items 绑定 logs 数据,并将 logs 数据循环展开节点

一篇教程读懂微信应用号开发9

运行结果如下:

一篇教程读懂微信应用号开发10

4. 手机预览

开发者工具左侧菜单栏选择「项目」,点击「预览」,扫码后即可在微信客户端中体验。

一篇教程读懂微信应用号开发11

目前,预览和上传功能尚无法实现,需要等待微信官方的下一步更新。

如你所见,微信官方给出的开发指南还非常简单,很多细节、代码和功能都没有明确的展示,所以接下来就到博卡君展示实力的时候啦!开发教程正式开始!

第一章:准备工作

做好准备工作很重要。开发一个微信应用号,你需要提前到微信的官方网站(weixin.qq.com)下载开发者工具。

1. 下载最新微信开发者工具,打开后你会看到该界面:

一篇教程读懂微信应用号开发12

2. 点击「新建 web+」项目,随后出现如下画面:

一篇教程读懂微信应用号开发13

3. 该页面内的各项内容需要注意——

  • AppID:依照官方解释来填。
  • Appname: 项目最外层文件夹名称,如你将其命名为「ABC」,则之后的全部项目内容均将保存在「/ABC/…」目录下。
  • 本地开发目录:项目存放在本地的目录。

注:再次强调,如果你和团队成员共同开发该项目,则建议你们使用同样的目录名称及本地目录,以确保协同开发的统一性。如果你之前已有项目,则导入过程与以上内容近似,不再赘述。

4. 准备工作全部完成后,点击「新建项目」按钮,弹出框点「确定」。

一篇教程读懂微信应用号开发14

5. 如上图所示,此刻,微信开发者工具已经为你自动构建了一个初始的 demo 项目,该项目内包含了一个微信应用项目所需具备的基本内容和框架结构。点击项目名称(图中即「cards」)进入该项目,就能看到整个项目的基本架构了:

一篇教程读懂微信应用号开发15

第二章:项目构架

微信目前用户群体非常庞大,微信推出公众号以后,火爆程度大家都看得到,也同样推动着 Html 5 的高速发展,随着公众号业务的需求越来越复杂,应用号现在的到来也是恰到好处。

博 卡君发现,微信提供给开发者的方式也在发生全面的改变:从操作 DOM 转为操作数据,基于微信提供的一个过桥工具实现很多 Html 5 在公众号很难实现的功能,有点类似于 hybrid 开发,不同于 hybrid 开发的方式是:微信开放的接口更为严谨,结构必须采用他提供给的组件,外部的框架和插件都不能在这里使用上,让开发者完全脱离操作 DOM,开发思想转变很大。

工欲善其事,必先利其器。理解它的核心功能非常重要,先了解它的整个运作流程。

生命周期:

在index.js里面:

一篇教程读懂微信应用号开发16

开发者工具上 Console 可以看到:

一篇教程读懂微信应用号开发17

在首页 console 可以看出顺序是 App Launch–>App Show–>onLoad–>onShow–>onReady。

首先是整个 app 的启动与显示,app 的启动在 app.js 里面可以配置,其次再进入到各个页面的加载显示等等。

可以想象到这里可以处理很多东西了,如加载框之类的都可以实现等等。

路由:

路由在项目开发中一直是个核心点,在这里其实微信对路由的介绍很少,可见微信在路由方面经过很好的封装,也提供三个跳转方法。

wx.navigateTo(OBJECT):保留当前页面,跳转到应用内的某个页面,使用wx.navigateBack可以返回到原页面。

wx.redirectTo(OBJECT):关闭当前页面,跳转到应用内的某个页面。

wx.navigateBack():关闭当前页面,回退前一页面。

这三个基本上使用足够,在路由方面微信封装的很好,开发者根本不用去配置路由,往往很多框架在路由方面配置很繁琐。

组件:

此次微信在组件提供方面也是非常全面,基本上满足项目需求,故而开发速度非常快,开发前可以认真浏览几次,开发效率会很好。

其它:

任何外部框架以及插件基本上无法使用,就算原生的 js 插件也很难使用,因为以前的 js 插件也基本上全部是一操作 dom 的形式存在,而微信应用号此次的架构是不允许操作任何 dom,就连以前开发者们习惯使用的动态设置的rem.js也是不支持的。

此次微信还提供了 WebSocket,就可以直接利用它做聊天,可以开发的空间非常大。

跟公众号对比博卡君发现,开发应用号组件化,结构化,多样化。新大陆总是充满着惊喜,更多的彩蛋等着大家来发现。

接下来开始搞一些简单的代码了!

1. 找到项目文件夹,导入你的编辑器里面。在这里,博卡君使用了 Sublime Text 编辑器。你可以根据自己的开发习惯选择自己喜欢的编辑器。

一篇教程读懂微信应用号开发18

2. 接下来,你需要根据自己的项目内容调整项目结构。在范例项目中,「card_course」目录下面主要包含了「tabBar」页面以及该应用的一些配置文件。

3. 示例项目的「tabBar」是五个菜单按钮:

一篇教程读懂微信应用号开发19

4. 找到「app.json」文件,用来配置这个五个菜单。在代码行中找到「tabBar」:

一篇教程读懂微信应用号开发20

你可以根据实际项目需求更改,其中:

  • 「Color」是底部字体颜色,「selectedColor」是切换到该页面高亮颜色,「borderStyle」是切换菜单上面的一条线的颜色,「backgroundColor」是底部菜单栏背景颜色。文字描述较为抽象,建议你一一调试并查看其效果,加深印象。
  • 「list」下的代码顺序必须依次放置,不能随便更改。
  • 「pagePath」之后的文件名内,「.wxml」后缀被隐藏起来了,这是微信开发代码中人性化的一点——帮你节约写代码的时间,无须频繁声明文件后缀。
  • 「iconPath」为未获得显示页面的图标路径,这两个路径可以直接是网络图标。
  • 「selectedIconPath」为当前显示页面高亮图标路径,可以去掉,去掉之后会默认显示为「iconPath」的图标。
  • 「Text」为页面标题,也可以去掉,去掉之后纯显示图标,如只去掉其中一个,该位置会被占用。

注意:微信的底部菜单最多支持五栏(五个 icons),所以在你设计微信应用的 UI 和基本架构时就要预先考虑好菜单栏的排布。

5. 根据以上代码规则,博卡君做好了示例项目的基本架构,供你参考:

一篇教程读懂微信应用号开发21

一篇教程读懂微信应用号开发22

6. 「Json」文件配置好后,「card_course」的基本结构入上图所示,不需要的子集都可以暂时删除,缺少的子集则需要你主动新建。删除子集时记得顺带检查一下「app.json」里的相关内容是否已经一并删除。

注 意:博卡君个人建议你新建一个「wxml」文件的同时,把对应的「js」和「wxss」文件一起新建好,因为微信应用号的配置特点就是解析到一个 「wxml」文件时,会同时在同级目录下找到同文件名的「js」和「wxss」文件,所以「js」文件需及时在「app.json」里预先配置好。

编写「wxml」时,根据微信应用号提供的接口编码即可,大部分就是以前的「div」,而现在就用「view」即可。需要用其它子集时,可以根据微信提供的接口酌情选择。

使用「class」名来设置样式,「id」名在这里基本没有什么用处。主要操作数据,不操作「dom」。

一篇教程读懂微信应用号开发23

7. 以上是示例项目首页的「wxml」编码。从图中就可以看出,实现一个页面代码量非常少。

8. 「Wxss」文件是引入的样式文件,你也可以直接在里面写样式,示例中采用的是引入方式:

一篇教程读懂微信应用号开发24

一篇教程读懂微信应用号开发25

9. 修改代码后刷新一次,可以看到未设背景的「view」标签直接变成了粉色。

注意:修改「wxml」和「wxss」下的内容后,直接 F5 刷新就能直接看到效果,修改「js」则需点击重启按钮才能看到效果。

10. 另外,公共样式可以在「app.wxss」里直接引用。

一篇教程读懂微信应用号开发26

11. 「Js」文件需要在「app.json」文件的「page」里预先配置好。为了项目结构清晰化,博卡君在示例项目中的「index」首页同级目录新建其它四个页面文件,具体如下:

一篇教程读懂微信应用号开发27

一篇教程读懂微信应用号开发28

经过以上步骤,案例中的五个底部菜单就全部配置完毕了。

作者:qq_35114086 发表于2016/9/24 9:53:49 原文链接
阅读:231 评论:0 查看评论

Android UI--自定义ListView(实现下拉刷新+加载更多)

$
0
0
Android UI--自定义ListView(实现下拉刷新+加载更多)

关于实现ListView下拉刷新和加载更多的实现,我想网上一搜就一堆。不过我就没发现比较实用的,要不就是实现起来太复杂,要不就是不健全的。因为小巫近期要开发新浪微博客户端,需要实现ListView的下拉刷新,所以就想把这个UI整合到项目当中去,这里只是一个demo,可以根据项目的需要进行修改。

就不要太在乎界面了哈


       
       
         
       

     

       
     

       
       
         
       

     

       
       

     

       
       

     

知道你们想要源码了,去下吧: http://download.csdn.net/detail/wwj_748/6373183

自定义ListView:

package com.markupartist.android.widget;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.markupartist.android.example.pulltorefresh.R;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;



/**
 * 2013/8/13
 * 自定义ListView,实现OnScrollListener接口
 * 此ListView是为实现"下拉刷新"和"上拉加载更多"而定制的,具体效果可参考新浪微博、腾讯微博
 * @author wwj
 *
 */
public class PullToRefreshListView extends ListView implements OnScrollListener {

    private static final int TAP_TO_REFRESH = 1;			//(未刷新)
    private static final int PULL_TO_REFRESH = 2;			// 下拉刷新
    private static final int RELEASE_TO_REFRESH = 3;		// 释放刷新
    private static final int REFRESHING = 4;				// 正在刷新
    private static final int TAP_TO_LOADMORE = 5;			// 未加载更多
    private static final int LOADING = 6;					// 正在加载
    

    private static final String TAG = "PullToRefreshListView";

    private OnRefreshListener mOnRefreshListener;			// 刷新监听器

    /**
     * Listener that will receive notifications every time the list scrolls.
     */
    private OnScrollListener mOnScrollListener;				// 列表滚动监听器
    private LayoutInflater mInflater;		    			// 用于加载布局文件

    private RelativeLayout mRefreshHeaderView;				// 刷新视图(也就是头部那部分)	
    private TextView mRefreshViewText;						// 刷新提示文本		
    private ImageView mRefreshViewImage;					// 刷新向上向下的那个图片
    private ProgressBar mRefreshViewProgress;				// 这里是圆形进度条
    private TextView mRefreshViewLastUpdated;				// 最近更新的文本
    
    private RelativeLayout mLoadMoreFooterView;				// 加载更多
    private TextView mLoadMoreText;							// 提示文本
    private ProgressBar mLoadMoreProgress;					// 加载更多进度条
    

    private int mCurrentScrollState;						// 当前滚动位置			
    private int mRefreshState;								// 刷新状态	
    private int mLoadState;									// 加载状态

    private RotateAnimation mFlipAnimation;					// 下拉动画
    private RotateAnimation mReverseFlipAnimation;			// 恢复动画

    private int mRefreshViewHeight;							// 刷新视图高度					
    private int mRefreshOriginalTopPadding;					// 原始上部间隙
    private int mLastMotionY;								// 记录点击位置
    
  public PullToRefreshListView(Context context) {
        super(context);
        init(context);
    }

    public PullToRefreshListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    private void init(Context context) {
        // Load all of the animations we need in code rather than through XML
    	/** 定义旋转动画**/
    	// 参数:1.旋转开始的角度 2.旋转结束的角度 3. X轴伸缩模式 4.X坐标的伸缩值 5.Y轴的伸缩模式 6.Y坐标的伸缩值
        mFlipAnimation = new RotateAnimation(0, -180,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        mFlipAnimation.setInterpolator(new LinearInterpolator());
        mFlipAnimation.setDuration(250);				// 设置持续时间
        mFlipAnimation.setFillAfter(true);				// 动画执行完是否停留在执行完的状态
        mReverseFlipAnimation = new RotateAnimation(-180, 0,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
        mReverseFlipAnimation.setDuration(250);
        mReverseFlipAnimation.setFillAfter(true);

        // 获取LayoutInflater实例对象
        mInflater = (LayoutInflater) context.getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);

        // 加载下拉刷新的头部视图
    mRefreshHeaderView = (RelativeLayout) mInflater.inflate(
        R.layout.pull_to_refresh_header, this, false);
        mRefreshViewText =
            (TextView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_text);
        mRefreshViewImage =
            (ImageView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_image);
        mRefreshViewProgress =
            (ProgressBar) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_progress);
        mRefreshViewLastUpdated =
            (TextView) mRefreshHeaderView.findViewById(R.id.pull_to_refresh_updated_at);
    mLoadMoreFooterView = (RelativeLayout) mInflater.inflate(
        R.layout.loadmore_footer, this, false);
    mLoadMoreText = (TextView) mLoadMoreFooterView.findViewById(R.id.loadmore_text);
    mLoadMoreProgress = (ProgressBar) mLoadMoreFooterView.findViewById(R.id.loadmore_progress);
    

        mRefreshViewImage.setMinimumHeight(50);		// 设置图片最小高度
        mRefreshHeaderView.setOnClickListener(new OnClickRefreshListener());
        mRefreshOriginalTopPadding = mRefreshHeaderView.getPaddingTop();
        mLoadMoreFooterView.setOnClickListener(new OnClickLoadMoreListener());

        mRefreshState = TAP_TO_REFRESH;				// 初始刷新状态
        mLoadState = TAP_TO_LOADMORE;

        addHeaderView(mRefreshHeaderView);			// 增加头部视图
        addFooterView(mLoadMoreFooterView);			// 增加尾部视图

        super.setOnScrollListener(this);		

        measureView(mRefreshHeaderView);				// 测量视图
        mRefreshViewHeight = mRefreshHeaderView.getMeasuredHeight();	// 得到视图的高度
    }

    @Override
    protected void onAttachedToWindow() {
        setSelection(1);		// 设置当前选中的项
    }

    @Override
    public void setAdapter(ListAdapter adapter) {
        super.setAdapter(adapter);

        setSelection(1);
    }

    /**
     * Set the listener that will receive notifications every time the list
     * scrolls.
     * 
     * @param l The scroll listener. 
     */
    @Override
    public void setOnScrollListener(AbsListView.OnScrollListener l) {
        mOnScrollListener = l;
    }

    /**
     * Register a callback to be invoked when this list should be refreshed.
     * 注册监听器
     * @param onRefreshListener The callback to run.
     */
    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
        mOnRefreshListener = onRefreshListener;
    }

    /**
     * Set a text to represent when the list was last updated.
     * 设置一个文本来表示最近更新的列表,显示的是最近更新列表的时间
     * @param lastUpdated Last updated at.
     */
    public void setLastUpdated(CharSequence lastUpdated) {
        if (lastUpdated != null) {
            mRefreshViewLastUpdated.setVisibility(View.VISIBLE);
            mRefreshViewLastUpdated.setText("更新于: " + lastUpdated);
        } else {
            mRefreshViewLastUpdated.setVisibility(View.GONE);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int y = (int) event.getY();	 // 获取点击位置的Y坐标

        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:		// 手指抬起
                if (!isVerticalScrollBarEnabled()) {
                    setVerticalScrollBarEnabled(true);
                }
                if (getFirstVisiblePosition() == 0 && mRefreshState != REFRESHING) {
                    if ((mRefreshHeaderView.getBottom() > mRefreshViewHeight
                            || mRefreshHeaderView.getTop() >= 0)
                            && mRefreshState == RELEASE_TO_REFRESH) {
                        // Initiate the refresh
                        mRefreshState = REFRESHING;		// 刷新状态
                        prepareForRefresh();
                        onRefresh();
                    } else if (mRefreshHeaderView.getBottom() < mRefreshViewHeight
                            || mRefreshHeaderView.getTop() < 0) {
                        // Abort refresh and scroll down below the refresh view
                        resetHeader();
                        setSelection(1);
                    }
                }
                break;
            case MotionEvent.ACTION_DOWN:
                mLastMotionY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                applyHeaderPadding(event);
                break;
        }
        return super.onTouchEvent(event);
    }

    private void applyHeaderPadding(MotionEvent ev) {
        final int historySize = ev.getHistorySize();

        // Workaround for getPointerCount() which is unavailable in 1.5
        // (it's always 1 in 1.5)
        int pointerCount = 1;
        try {
            Method method = MotionEvent.class.getMethod("getPointerCount");
            pointerCount = (Integer)method.invoke(ev);
        } catch (NoSuchMethodException e) {
            pointerCount = 1;
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (IllegalAccessException e) {
            System.err.println("unexpected " + e);
        } catch (InvocationTargetException e) {
            System.err.println("unexpected " + e);
        }

        for (int h = 0; h < historySize; h++) {
            for (int p = 0; p < pointerCount; p++) {
                if (mRefreshState == RELEASE_TO_REFRESH) {
                    if (isVerticalFadingEdgeEnabled()) {
                        setVerticalScrollBarEnabled(false);
                    }

                    int historicalY = 0;
                    try {
                        // For Android > 2.0
                        Method method = MotionEvent.class.getMethod(
                                "getHistoricalY", Integer.TYPE, Integer.TYPE);
                        historicalY = ((Float) method.invoke(ev, p, h)).intValue();
                    } catch (NoSuchMethodException e) {
                        // For Android < 2.0
                        historicalY = (int) (ev.getHistoricalY(h));
                    } catch (IllegalArgumentException e) {
                        throw e;
                    } catch (IllegalAccessException e) {
                        System.err.println("unexpected " + e);
                    } catch (InvocationTargetException e) {
                        System.err.println("unexpected " + e);
                    }

                    // Calculate the padding to apply, we divide by 1.7 to
                    // simulate a more resistant effect during pull.
                    int topPadding = (int) (((historicalY - mLastMotionY)
                            - mRefreshViewHeight) / 1.7);

                    // 设置上、下、左、右四个位置的间隙间隙
                    mRefreshHeaderView.setPadding(
                            mRefreshHeaderView.getPaddingLeft(),
                            topPadding,
                            mRefreshHeaderView.getPaddingRight(),
                            mRefreshHeaderView.getPaddingBottom());
                }
            }
        }
    }

    /**
     * Sets the header padding back to original size.
     * 设置头部填充会原始大小
     */
    private void resetHeaderPadding() {
        mRefreshHeaderView.setPadding(
                mRefreshHeaderView.getPaddingLeft(),
                mRefreshOriginalTopPadding,
                mRefreshHeaderView.getPaddingRight(),
                mRefreshHeaderView.getPaddingBottom());
    }

    /**
     * Resets the header to the original state.
     * 重新设置头部为原始状态
     */
    private void resetHeader() {
        if (mRefreshState != TAP_TO_REFRESH) {
            mRefreshState = TAP_TO_REFRESH;

            resetHeaderPadding();

            // Set refresh view text to the pull label
            mRefreshViewText.setText(R.string.pull_to_refresh_tap_label);
            // Replace refresh drawable with arrow drawable
            mRefreshViewImage.setImageResource(R.drawable.ic_pulltorefresh_arrow);
            // Clear the full rotation animation
            mRefreshViewImage.clearAnimation();
            // Hide progress bar and arrow.
            mRefreshViewImage.setVisibility(View.GONE);
            mRefreshViewProgress.setVisibility(View.GONE);
        }
    }
    
    /**
     * 重设ListView尾部视图为初始状态
     */
    private void resetFooter() {
    	if(mLoadState != TAP_TO_LOADMORE) {
    		mLoadState = TAP_TO_LOADMORE;
    		
    		// 进度条设置为不可见
    		mLoadMoreProgress.setVisibility(View.GONE);
    		// 按钮的文本替换为“加载更多”
    		mLoadMoreText.setText(R.string.loadmore_label);
    	}
    	
    }
    

    /**
     * 测量视图的大小
     * @param child
     */
    private void measureView(View child) {
        ViewGroup.LayoutParams p = child.getLayoutParams();
        if (p == null) {
            p = new ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }

        int childWidthSpec = ViewGroup.getChildMeasureSpec(0,
                0 + 0, p.width);
        int lpHeight = p.height;
        int childHeightSpec;
        if (lpHeight > 0) {
            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
        } else {
            childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
        }
        child.measure(childWidthSpec, childHeightSpec);
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        // When the refresh view is completely visible, change the text to say
        // "Release to refresh..." and flip the arrow drawable.
        if (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL
                && mRefreshState != REFRESHING) {
            if (firstVisibleItem == 0) {		// 如果第一个可见条目为0
                mRefreshViewImage.setVisibility(View.VISIBLE);	// 让指示箭头变得可见
                /**如果头部视图相对与父容器的位置大于其自身高度+20或者头部视图的顶部位置>0,并且要在刷新状态不等于"释放以刷新"**/
                if ((mRefreshHeaderView.getBottom() > mRefreshViewHeight + 20
                        || mRefreshHeaderView.getTop() >= 0)
                        && mRefreshState != RELEASE_TO_REFRESH) {
                    mRefreshViewText.setText(R.string.pull_to_refresh_release_label);// 设置刷新文本为"Release to refresh..."
                    mRefreshViewImage.clearAnimation();					// 清除动画	
                    mRefreshViewImage.startAnimation(mFlipAnimation);	// 启动动画
                    mRefreshState = RELEASE_TO_REFRESH;					// 更改刷新状态为“释放以刷新"
                } else if (mRefreshHeaderView.getBottom() < mRefreshViewHeight + 20
                        && mRefreshState != PULL_TO_REFRESH) {
                    mRefreshViewText.setText(R.string.pull_to_refresh_pull_label);// 设置刷新文本为"Pull to refresh..."
                    if (mRefreshState != TAP_TO_REFRESH) {
                        mRefreshViewImage.clearAnimation();
                        mRefreshViewImage.startAnimation(mReverseFlipAnimation);
                    }
                    mRefreshState = PULL_TO_REFRESH;
                }
            } else {
                mRefreshViewImage.setVisibility(View.GONE);			// 让刷新箭头不可见
                resetHeader();	// 重新设置头部为原始状态
            }
        } else if (mCurrentScrollState == SCROLL_STATE_FLING
                && firstVisibleItem == 0
                && mRefreshState != REFRESHING) {
            setSelection(1);
        }

        if (mOnScrollListener != null) {
            mOnScrollListener.onScroll(view, firstVisibleItem,
                    visibleItemCount, totalItemCount);
        }
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        mCurrentScrollState = scrollState;

        if (mOnScrollListener != null) {
            mOnScrollListener.onScrollStateChanged(view, scrollState);
        }
    }
    

    /**为刷新做准备**/
    public void prepareForRefresh() {
        resetHeaderPadding();		

        mRefreshViewImage.setVisibility(View.GONE);			// 去掉刷新的箭头
        // We need this hack, otherwise it will keep the previous drawable.
        mRefreshViewImage.setImageDrawable(null);
        mRefreshViewProgress.setVisibility(View.VISIBLE);	// 圆形进度条变为可见

        // Set refresh view text to the refreshing label
        mRefreshViewText.setText(R.string.pull_to_refresh_refreshing_label);

        mRefreshState = REFRESHING;
    }
    
    /**为加载更多做准备**/
    public void prepareForLoadMore() {
    	mLoadMoreProgress.setVisibility(View.VISIBLE);	 
    	mLoadMoreText.setText(R.string.loading_label);
    	mLoadState = LOADING;
    }

    public void onRefresh() {
        Log.d(TAG, "onRefresh");

        if (mOnRefreshListener != null) {
            mOnRefreshListener.onRefresh();
        }
    }
    
    public void OnLoadMore() {
    	Log.d(TAG, "onLoadMore");
    	if(mOnRefreshListener != null) {
    		mOnRefreshListener.onLoadMore();
    	}
    }

    /**
     * Resets the list to a normal state after a refresh.
     * @param lastUpdated Last updated at.
     */
    public void onRefreshComplete(CharSequence lastUpdated) {
        setLastUpdated(lastUpdated);	// 显示更新时间
        onRefreshComplete();
    }

    /**
     * Resets the list to a normal state after a refresh.
     */
    public void onRefreshComplete() {        
        Log.d(TAG, "onRefreshComplete");

        resetHeader();

        // If refresh view is visible when loading completes, scroll down to
        // the next item.
        if (mRefreshHeaderView.getBottom() > 0) {
            invalidateViews();
            setSelection(1);
        }
    }
    
    public void onLoadMoreComplete() {
    	Log.d(TAG, "onLoadMoreComplete");
    	resetFooter();
    }

    /**
     * Invoked when the refresh view is clicked on. This is mainly used when
     * there's only a few items in the list and it's not possible to drag the
     * list.
     * 点击刷新
     */
    private class OnClickRefreshListener implements OnClickListener {

        @Override
        public void onClick(View v) {
            if (mRefreshState != REFRESHING) {
                prepareForRefresh();
                onRefresh();
            }
        }

    }
    
    /**
     * 
     * @author wwj
     * 加载更多
     */
    private class OnClickLoadMoreListener implements OnClickListener {

    @Override
    public void onClick(View v) {
      if(mLoadState != LOADING) {
        prepareForLoadMore();
        OnLoadMore();
      }
    }
    }

    /**
     * Interface definition for a callback to be invoked when list should be
     * refreshed.
     * 接口定义一个回调方法当列表应当被刷新
     */
    public interface OnRefreshListener {
        /**
         * Called when the list should be refreshed.
         * 当列表应当被刷新是调用这个方法
         * <p>
         * A call to {@link PullToRefreshListView #onRefreshComplete()} is
         * expected to indicate that the refresh has completed.
         */
        public void onRefresh();
        
        public void onLoadMore();
    }
}

使用方法:
package com.markupartist.android.example.pulltorefresh;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;

import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.widget.ArrayAdapter;

import com.markupartist.android.widget.PullToRefreshListView;
import com.markupartist.android.widget.PullToRefreshListView.OnRefreshListener;

public class PullToRefreshActivity extends Activity {
  private LinkedList<String> mListItems;
  public static PullToRefreshListView weiboListView;

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.pull_to_refresh);
    weiboListView = (PullToRefreshListView) findViewById(R.id.weibolist);

    // Set a listener to be invoked when the list should be refreshed.
    weiboListView.setOnRefreshListener(new OnRefreshListener() {
      @Override
      public void onRefresh() {
        // Do work to refresh the list here.
        new GetDataTask(PullToRefreshActivity.this, 0).execute();
      }

      @Override
      public void onLoadMore() {
        new GetDataTask(PullToRefreshActivity.this, 1).execute();
      }
    });

    mListItems = new LinkedList<String>();
    mListItems.addAll(Arrays.asList(mStrings));

    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, mListItems);

    weiboListView.setAdapter(adapter);
  }

  private class GetDataTask extends AsyncTask<Void, Void, String[]> {
    private Context context;
    private int index;

    public GetDataTask(Context context, int index) {
      this.context = context;
      this.index = index;
    }

    @Override
    protected String[] doInBackground(Void... params) {
      // Simulates a background job.
      try {
        Thread.sleep(2000);
      } catch (InterruptedException e) {
        ;
      }
      return mStrings;
    }

    @Override
    protected void onPostExecute(String[] result) {
      if (index == 0) {
        // 将字符串“Added after refresh”添加到顶部
        mListItems.addFirst("Added after refresh...");

        SimpleDateFormat format = new SimpleDateFormat(
            "yyyy年MM月dd日  HH:mm");
        String date = format.format(new Date());
        // Call onRefreshComplete when the list has been refreshed.
        weiboListView.onRefreshComplete(date);
      } else if (index == 1) {
        mListItems.addLast("Added after loadmore...");
        weiboListView.onLoadMoreComplete();
      }

      super.onPostExecute(result);
    }
  }

  public static String[] mStrings = { "一条微博", "两条微博", "三条微博", "四条微博", "五条微博",
      "六条微博", "七条微博", "八条微博", "九条微博", "十条微博", "十一条微博", "十二条微博" };

}

下拉刷新的那个头部布局

/2013.08.22_PullToRefresh_ListView_Demo/res/layout/pull_to_refresh_header.xml

<?xml version="1.0" encoding="utf-8"?>
<!--
     Copyright (C) 2011 Johan Nilsson <http://markupartist.com>

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    android:paddingBottom="15dip"
    android:paddingTop="10dip" >

    <!-- 小型圆形进度条,初始为不可见 -->

    <ProgressBar
        android:id="@+id/pull_to_refresh_progress"
        style="?android:attr/progressBarStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="30dip"
        android:layout_marginRight="20dip"
        android:layout_marginTop="10dip"
        android:indeterminate="true"
        android:visibility="gone" />
    <!-- 下拉刷新的那个箭头 -->

    <ImageView
        android:id="@+id/pull_to_refresh_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginLeft="30dip"
        android:layout_marginRight="20dip"
        android:gravity="center"
        android:src="@drawable/ic_pulltorefresh_arrow"
        android:visibility="gone" />
    <!-- 下拉刷新的提示文本 -->

    <TextView
        android:id="@+id/pull_to_refresh_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:paddingTop="5dip"
        android:text="@string/pull_to_refresh_tap_label"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/pull_to_refresh_updated_at"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/pull_to_refresh_text"
        android:layout_gravity="center"
        android:gravity="center"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:visibility="gone" />

</RelativeLayout>

加载更多的底部布局

/2013.08.22_PullToRefresh_ListView_Demo/res/layout/loadmore_footer.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/loadmore_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:paddingBottom="15dip"
    android:paddingTop="10dip" >

<!--     <Button
        android:id="@+id/loadmore_btn"
        android:layout_width="match_parent"
        android:layout_height="60.0dip"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="0.0dip"
        android:background="@drawable/weibo_list_item_selector"
        android:text="@string/loadmore_label"
        android:textColor="@color/loadmore_btn_selector"
        android:textSize="18.0sp" />
 -->
    <ProgressBar
        android:id="@+id/loadmore_progress"
        style="?android:attr/progressBarStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="30dip"
        android:layout_marginRight="20dip"
        android:layout_marginTop="10dip"
        android:indeterminate="true"
        android:visibility="gone" />

   <TextView
        android:id="@+id/loadmore_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:paddingTop="5dip"
        android:text="@string/loadmore_label"
        android:textAppearance="?android:attr/textAppearanceMedium"
        android:textStyle="bold" />

</RelativeLayout>

/2013.08.22_PullToRefresh_ListView_Demo/res/layout/pull_to_refresh.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!--
    The PullToRefreshListView replaces a standard ListView widget.
    	自定义列表在这
    -->

    <com.markupartist.android.widget.PullToRefreshListView
        android:id="@+id/weibolist"
        android:layout_width="match_parent"
        android:layout_height="match_parent" 
        android:cacheColorHint="#00000000"
        android:fastScrollEnabled="true"/>

</LinearLayout>

作者:qq_35114086 发表于2016/9/24 9:54:38 原文链接
阅读:246 评论:0 查看评论

好文章之——PHP系列(一)

$
0
0

注:最近实习的公司是一家做电商企业,后台主要是php开发,好久不怎么接触php的我看了几篇相关文章,提高下对它的认识与理解,发现里面的学习思路还是非常好的,当然也会重新拾一下基础知识啦!

其实自己心中还是有点小纠结的,感觉自己学的东西太杂了,没有非常精通的。毕竟人的精力是有限,举个例子:“如果1年干php,第二年又干java,第三年可能是Python。但是当第四年他可能想去找另外一份工作,那他到底要找什么样的工作呢? 每一种技能都不精,如果面试PHP,人家就按你php的水平给你薪水,而不太会看重你还java或者python。 结果你薪资水平可能就是个中下等水平的薪水。”其实知识面广点是很好的,但是就怕你什么都能只会个皮毛,必须得确立自己的长项和着重点,这是我必须得想明白的问题~

 

高级PHP工程师所应该具备一些技能

一、平静的心态

  和所有程序员一样,要写一手好的程序,没有好的心态是不行的。

  遇事不可急躁,不可轻言放弃。

  在程序开发过程中,尤其是初中级程序员,写出的程序或架构会遇到很多问题,其中一些问题比较弱智,而有些问题根本没有碰到过,于是不可太过急躁,应该逐个排查问题的最初源泉,将其干掉。急躁的心态去开发系统是对项目的一种不负责。急躁会让人学会将就,让人学会逃避。而我个人北京两年的简单生活,给我其中一个最大的历练也就是:我的心态更加平静了。

  相信,这样的心态也会有助于你其他方面的处事能力。

  为什么将心态列入其中,我是想说明:它不同于销售的职能,需要很大激情澎湃,而是需要静静的思考

  二、一套烂熟于心的问题解决思路

  曾经有位程序开发的同事在QQ签名中写到:每解决一个bug,就给自己一个提升。的确,没有真正解决过无数的bug或问题的程序员,谈不上专家,谈不上高级程序员。而一个高级程序员正是从这种解决问题的过程中不断地历练自己,形成一套烂熟于心的问题解决思路,让自己强大的。

  我也简单说说PHP程序员成长过程中经常遇到的一些问题,如果你一个也没遇到或很少遇到,那么您就是两个极端的人:要么初级入门,要么高级了,哈哈。

  • 1、编码问题。

  • 2、PHP和SQL数据库执行效率问题。

  • 3、Session和Cookie域和加密解析问题。

  • 4、程序的执行顺序问题。

  • 5、程序编写的多环境适用问题。

  • 6、分类的构建和结构设计问题。

  • 7、字符串处理问题:正则表达式处理或简单PHP字符串处理函数来处理。

  • 8、各种模板引擎的编写局限性问题。

  • 9、PHP和web端数据交互问题(如ajax,接口调用等)。

  三、过硬的PHP基础知识

  没有过硬的PHP基础知识,哪怕心态再好,问题解决的能力再强,也只能纸上谈兵。

  过硬的基础知识会让你在项目开发过程中游刃有余。

  我也简单说说哪些属于PHP工程师所应具备的基础知识(其实这些在招聘需求中很常见):

  • 1、语法规则,这个不说了,这个不会,就没入门,赶紧买本书或找个网站补补。

  • 2、MYSQL各种sql语句的写法,增删改查基本的不说了,in(),union,left(),left join,as,replace,alter table,where的字段排序,各种索引建立的方法要特别熟悉。

  • 3、会自己搭建LAMP环境和WAMP环境,用集成软件一键式安装的不算。开发程序,对于自己开发的环境构建结构都不清楚,怎么排查问题?所以至少要会用对立的msi文件来安装自己需要的开发环境。安装3-5遍成功,这个算还行,还得会安装各种扩展,配置apache服务,知道各种参数设置的地方以及知道怎么设置各种参数;会linux操作系统的基本命令。

  • 4、熟悉web方面的其他程序,因为PHP不是一个完全独立的东西,他是一个和其他语言和要素配合来完成一个项目的,如果对其他语言和要素不太熟悉,在团队协作过程中会非常吃力。这些其他要素包括:html,java,jquery,xml,http协议,正则表达式等。

  四、综合的互联网应用及项目管理知识和素养

  1、见识广博,擅于学习

  只顾自己钻研,不看看、学学人家的做法,会像井底之蛙,难以看到广阔的天空的;所以,不要只顾着天天编程,学会抽点时间去看看一些大型开源系统的架构思路,以及大型商务网站的构建方式。向他们学习,补充自己的不足。

  比如至少该晓得不同类型的开源系统有哪些吧,比如Uchome,dede,phpcms,wordpress,discuz,帝国等等。

  看多了,你也会总结发现一些常规性的思路,比如缓存的机制,比如模板机制,比如静态页面生成等等。

  2、项目解决方案选型

  不同需求,用不同的机构和选型。也就是常说的“水来土掩,兵来将挡”,有些架构固然强大,但是用于小型项目也会很吃力,就是杀机不用牛刀。根据需求来选型很重要。

  选型不是随口就能定的,需要一个PHP程序员用于良好的储备,个人觉得至少需要以下储备,才具备选型能力:

  • 熟练应用至少一个PHP框架,两-三个PHP开源系统;

  • 拥有自己的一套应用系统。

  3、良好的项目管理素养

  项目不是一直开发过程中,项目也会进入运营期,维护期,这样,具备良好的项目管理素养会使项目更加稳定,可控。

  良好的项目管理素养包括:

  • 良好的项目开发及维护习惯,记住:千万别为了一时的省力,造成后面多次的重复劳动。时时提醒自己将工作流程化,流程规划化,规范简单化。

  • 良好的多人合作管理意识:项目不是一个人的,是多人协作的产物,也是服务于大众的,因而,要提升协作意识,让相关人员一同来完善项目。

  4、丰富的项目开发应用经验

  学理论,去考试或考核是学校里面的事儿,没有项目经验,就像满肚子经文,吐也难吐出。

  这就需要实际的项目将自己的知识去学会转化为需求实现。

  5、良好的开发规范

  • 代码可读性强:对象,方法,函数的注释;一套成熟的命名规范。

  • 代码冗余度底:程序和文件的重用性大,高内聚,低耦合。

  • 执行效率高:用最简单的程序流程实现应用需求,勿扰大弯子。

  • 代码安全性好:做一名警惕的程序员,任何有用户输入和上传文件的地方都得额外谨慎,也许一个程序员一时的疏忽就会导致一个系统顷刻间崩溃。

  另外,多说几句,PHP高级工程师,其实对于一个稍微能坚持,并喜欢PHP的来说不太难;难的是学会用工具来实现想法,不管是自己的想法还是他人的需求,学会转化。

  这样,不防多了解些互联网发展的趋势,项目开发管理流程等等。

 

PHP程序员的技术成长规划

 

按照了解的很多PHP/LNMP程序员的发展轨迹,结合个人经验体会,抽象出很多程序员对未来的迷漫,特别对技术学习的盲目和慌乱,简单梳理了这个每个阶段PHP程序员的技术要求,来帮助很多PHP程序做对照设定学习成长目标。

本文按照目前主流技术做了一个基本的梳理,整个是假设PHP程序员不是基础非常扎实的情况进行的设定,并且所有设定都非常具体明确清晰,可能会让人觉得不适,请理解仅代表一家之言。(未来技术变化不在讨论范围)

第一阶段:基础阶段(基础PHP程序员)

重点:把LNMP搞熟练(核心是安装配置基本操作)

目标:能够完成基本的LNMP系统安装,简单配置维护;能够做基本的简单系统的PHP开发;能够在PHP中型系统中支持某个PHP功能模块的开发。

时间:完成本阶段的时间因人而异,有的成长快半年一年就过了,成长慢的两三年也有。

1.Linux:

基本命令、操作、启动、基本服务配置(包括rpm安装文件,各种服务配置等);会写简单的shell脚本和awk/sed 脚本命令等。

2.Nginx:

做到能够安装配置nginx+php,知道基本的nginx核心配置选项,知道 server/fastcgi_pass/access_log 等基础配置,目标是能够让nginx+php_fpm顺利工作。

3.MySQL:

会自己搭建mysql,知道基本的mysql配置选项;知道innodb和myisam的区别,知道针对InnoDB和MyISAM两个引擎的不同配置选项;知道基本的两个引擎的差异和选择上面的区别;能够纯手工编译搭建一个MySQL数据库并且配置好编码等正常稳定运行;核心主旨是能够搭建一个可运行的MySQL数据库。

4.PHP:

基本语法数组、字符串、数据库、XML、Socket、GD/ImageMgk图片处理等等;熟悉各种跟MySQL操作链接的api(mysql/mysqli/PDO),知道各种编码问题的解决;知道常规熟练使用的PHP框架(ThinkPHP、Zendframework、Yii、Yaf等);了解基本MVC的运行机制和为什么这么做,稍微知道不同的PHP框架之间的区别;能够快速学习一个MVC框架。能够知道开发工程中的文件目录组织,有基本的良好的代码结构和风格,能够完成小系统的开发和中型系统中某个模块的开发工作。

5.前端:

如果条件时间允许,可以适当学习下 HTML/CSS/JS 等相关知识,知道什么web标准,div+css的web/wap页面模式,知道HTML5和HTML4的区别;了解一些基本的前端只是和JS框架(jQuery之类的);了解一些基本的JavaScript编程知识;(本项不是必须项,如果有时间,稍微了解一下是可以的,不过不建议作为重点,除非个人有强烈兴趣)

6.系统设计:

能够完成小型系统的基本设计,包括简单的数据库设计,能够完成基本的:浏览器 -> Nginx+PHP -> 数据库 架构的设计开发工作;能够支撑每天几十万到数百万流量网站的开发维护工作;

第二阶段:提高阶段 (中级PHP程序员)

重点:提高针对LNMP的技能,能够更全面的对LNMP有熟练的应用。

目标:能够随时随地搭建好LNMP环境,快速完成常规配置;能够追查解决大部分遇到的开发和线上环境的问题;能够独立承担中型系统的构架和开发工作;能够在大型系统中承担某个中型模块的开发工作;

1. Linux: 

在第一阶段的基础上面,能够流畅的使用Shell脚本来完成很多自动化的工作;awk/sed/perl 也操作的不错,能够完成很多文本处理和数据统计等工作;基本能够安装大部分非特殊的Linux程序(包括各种库、包、第三方依赖等等,比如MongoDB/Redis/Sphinx/Luncene/SVN之类的);了解基本的Linux服务,知道如何查看Linux的性能指标数据,知道基本的Linux下面的问题跟踪等。

2. Nginx: 

在第一阶段的基础上面,了解复杂一些的Nginx配置;包括 多核配置、events、proxy_pass,sendfile/tcp_*配置,知道超时等相关配置和性能影响;知道nginx除了web server,还能够承担代理服务器、反向静态服务器等配置;知道基本的nginx配置调优;知道如何配置权限、编译一个nginx扩展到nginx;知道基本的nginx运行原理(master/worker机制,epoll),知道为什么nginx性能比apache性能好等知识;

3. MySQL/MongoDB:

在第一阶段的基础上面,在MySQL开发方面,掌握很多小技巧,包括常规SQL优化(group by/order by/rand优化等);除了能够搭建MySQL,还能够冷热备份MySQL数据,还知道影响innodb/myisam性能的配置选项(比如key_buffer/query_cache/sort_buffer/innodb_buffer_pool_size/innodb_flush_log_at_trx_commit等),也知道这些选项配置成为多少值合适;另外也了解一些特殊的配置选项,比如  知道如何搭建mysql主从同步的环境,知道各个binlog_format的区别;知道MySQL的性能追查,包括slow_log/explain等,还能够知道基本的索引建立处理等知识;原理方面了解基本的MySQL的架构(Server+存储引擎),知道基本的InnoDB/MyISAM索引存储结构和不同(聚簇索引,B树);知道基本的InnoDB事务处理机制;了解大部分MySQL异常情况的处理方案(或者知道哪儿找到处理方案)。条件允许的情况,建议了解一下NoSQL的代表MongoDB数据库,顺便对比跟MySQL的差别,同事能够在合适的应用场景安全谨慎的使用MongoDB,知道基本的PHP与MongoDB的结合开发。

4. Redis/Memcached:

在大部分中型系统里面一定会涉及到缓存处理,所以一定要了解基本的缓存;知道Memcached和Redis的异同和应用场景,能够独立安装 Redis/Memcached,了解Memcahed的一些基本特性和限制,比如最大的value值,知道PHP跟他们的使用结合;Redis了解基本工作原理和使用,了解常规的数据类型,知道什么场景应用什么类型,了解Redis的事务等等。原理部分,能够大概了解Memcached的内存结构(slab机制),redis就了解常用数据类型底层实现存储结构(SDS/链表/SkipList/HashTable)等等,顺便了解一下Redis的事务、RDB、AOF等机制更好

5. PHP:

除了第一阶段的能力,安装配置方面能够随意安装PHP和各种第三方扩展的编译安装配置;了解php-fpm的大部分配置选项和含义(如max_requests/max_children/request_terminate_timeout之类的影响性能的配置),知道mod_php/fastcgi的区别;在PHP方面已经能够熟练各种基础技术,还包括各种深入些的PHP,包括对PHP面向对象的深入理解/SPL/语法层面的特殊特性比如反射之类的;在框架方面已经阅读过最少一个以上常规PHP MVC框架的代码了,知道基本PHP框架内部实现机制和设计思想;在PHP开发中已经能够熟练使用常规的设计模式来应用开发(抽象工厂/单例/观察者/命令链/策略/适配器 等模式);建议开发自己的PHP MVC框架来充分让开发自由化,让自己深入理解MVC模式,也让自己能够在业务项目开发里快速升级;熟悉PHP的各种代码优化方法,熟悉大部分PHP安全方面问题的解决处理;熟悉基本的PHP执行的机制原理(Zend引擎/扩展基本工作机制);

6. C/C++: 

开始涉猎一定的C/C++语言,能够写基本的C/C++代码,对基本的C/C++语法熟悉(指针、数组操作、字符串、常规标准API)和数据结构(链表、树、哈希、队列)有一定的熟悉下;对Linux下面的C语言开发有基本的了解概念,会简单的makefile文件编写,能够使用简单的GCC/GDB的程序编译简单调试工作;对基本的网络编程有大概了解。(本项是为了向更高层次打下基础)

7. 前端:

在第一阶段的基础上面,熟悉基本的HTTP协议(协议代码200/300/400/500,基本的HTTP交互头);条件允许,可以在深入写出稍微优雅的HTML+CSS+JavaScript,或者能够大致简单使用某些前端框架(jQuery/YUI/ExtJS/RequireJS/Bootstrap之类);如果条件允许,可以深入学习JavaScript编程,比如闭包机制、DOM处理;再深入些可以读读jQuery源码做深入学习。(本项不做重点学习,除非对前端有兴趣)

8. 系统设计:

能够设计大部分中型系统的网站架构、数据库、基本PHP框架选型;性能测试排查处理等;能够完成类似:浏览器 -> CDN(Squid) -> Nginx+PHP -> 缓存 -> 数据库 结构网站的基本设计开发维护;能够支撑每天数百万到千万流量基本网站的开发维护工作;

第三阶段:高级阶段 (高级PHP程序员)

重点:除了基本的LNMP程序,还能够在某个方向或领域有深入学习。(纵深维度发展)

目标:除了能够完成基本的PHP业务开发,还能够解决大部分深入复杂的技术问题,并且可以独立设计完成中大型的系统设计和开发工作;自己能够独立hold深入某个技术方向,在这块比较专业。(比如在MySQL、Nginx、PHP、Redis等等任一方向深入研究)

1. Linux:

除了第二阶段的能力,在Linux下面除了常规的操作和性能监控跟踪,还能够使用很多高级复杂的命令完成工作(watch/tcpdump/starce/ldd/ar等);在shell脚本方面,已经能够编写比较复杂的shell脚本(超过500行)来协助完成很多包括备份、自动化处理、监控等工作的shell;对awk/sed/perl 等应用已经如火纯青,能够随意操作控制处理文本统计分析各种复杂格式的数据;对Linux内部机制有一些了解,对内核模块加载,启动错误处理等等有个基本的处理;同时对一些其他相关的东西也了解,比如NFS、磁盘管理等等;

2. Nginx: 

在第二阶段的基础上面,已经能够把Nginx操作的很熟练,能够对Nginx进行更深入的运维工作,比如监控、性能优化,复杂问题处理等等;看个人兴趣,更多方面可以考虑侧重在关于Nginx工作原理部分的深入学习,主要表现在阅读源码开始,比如具体的master/worker工作机制,Nginx内部的事件处理,内存管理等等;同时可以学习Nginx扩展的开发,可以定制一些自己私有的扩展;同时可以对Nginx+Lua有一定程度的了解,看看是否可以结合应用出更好模式;这个阶段的要求是对Nginx原理的深入理解,可以考虑成为Nginx方向的深入专业者。

3. MySQL/MongoDB:

在第二阶段的基础上面,在MySQL应用方面,除了之前的基本SQL优化,还能够在完成一些复杂操作,比如大批量数据的导入导出,线上大批量数据的更改表结构或者增删索引字段等等高危操作;除了安装配置,已经能够处理更多复杂的MySQL的问题,比如各种问题的追查,主从同步延迟问题的解决、跨机房同步数据方案、MySQL高可用架构等都有涉及了解;对MySQL应用层面,对MySQL的核心关键技术比较熟悉,比如事务机制(隔离级别、锁等)、对触发器、分区等技术有一定了解和应用;对MySQL性能方面,有包括磁盘优化(SAS迁移到SSD)、服务器优化(内存、服务器本身配置)、除了二阶段的其他核心性能优化选项(innodb_log_buffer_size/back_log/table_open_cache/thread_cache_size/innodb_lock_wait_timeout等)、连接池软件选择应用,对show *(show status/show profile)类的操作语句有深入了解,能够完成大部分的性能问题追查;MySQL备份技术的深入熟悉,包括灾备还原、对Binlog的深入理解,冷热备份,多IDC备份等;在MySQL原理方面,有更多了解,比如对MySQL的工作机制开始阅读部分源码,比如对主从同步(复制)技术的源码学习,或者对某个存储引擎(MyISAM/Innodb/TokuDB)等等的源码学习理解,如果条件允许,可以参考CSV引擎开发自己简单的存储引擎来保存一些数据,增强对MySQL的理解;在这个过程,如果自己有兴趣,也可以考虑往DBA方向发展。MongoDB层面,可以考虑比如说在写少读多的情况开始在线上应用MongoDB,或者是做一些线上的数据分析处理的操作,具体场景可以按照工作来,不过核心是要更好的深入理解RMDBS和NoSQL的不同场景下面的应用,如果条件或者兴趣允许,可以开始深入学习一下MongoDB的工作机制。

4. Redis/Memcached:

在第二阶段的基础上面,能够更深入的应用和学习。因为Memcached不是特别复杂,建议可以把源码进行阅读,特别是内存管理部分,方便深入理解;Redis部分,可以多做一些复杂的数据结构的应用(zset来做排行榜排序操作/事务处理用来保证原子性在秒杀类场景应用之类的使用操作);多涉及aof等同步机制的学习应用,设计一个高可用的Redis应用架构和集群;建议可以深入的学习一下Redis的源码,把在第二阶段积累的知识都可以应用上,特别可以阅读一下包括核心事件管理、内存管理、内部核心数据结构等充分学习了解一下。如果兴趣允许,可以成为一个Redis方面非常专业的使用者。

5. PHP:

作为基础核心技能,我们在第二阶段的基础上面,需要有更深入的学习和应用。从基本代码应用上面来说,能够解决在PHP开发中遇到95%的问题,了解大部分PHP的技巧;对大部分的PHP框架能够迅速在一天内上手使用,并且了解各个主流PHP框架的优缺点,能够迅速方便项目开发中做技术选型;在配置方面,除了常规第二阶段会的知识,会了解一些比较偏门的配置选项(php auto_prepend_file/auto_append_file),包括扩展中的一些复杂高级配置和原理(比如memcached扩展配置中的memcache.hash_strategy、apc扩展配置中的apc.mmap_file_mask/apc.slam_defense/apc.file_update_protection之类的);对php的工作机制比较了解,包括php-fpm工作机制(比如php-fpm在不同配置机器下面开启进程数量计算以及原理),对zend引擎有基本熟悉(vm/gc/stream处理),阅读过基本的PHP内核源码(或者阅读过相关文章),对PHP内部机制的大部分核心数据结构(基础类型/Array/Object)实现有了解,对于核心基础结构(zval/hashtable/gc)有深入学习了解;能够进行基本的PHP扩展开发,了解一些扩展开发的中高级知识(minit/rinit等),熟悉php跟apache/nginx不同的通信交互方式细节(mod_php/fastcgi);除了开发PHP扩展,可以考虑学习开发Zend扩展,从更底层去了解PHP。

6. C/C++:

在第二阶段基础上面,能够在C/C++语言方面有更深入的学习了解,能够完成中小型C/C++系统的开发工作;除了基本第二阶段的基础C/C++语法和数据结构,也能够学习一些特殊数据结构(b-tree/rb-tree/skiplist/lsm-tree/trie-tree等)方便在特殊工作中需求;在系统编程方面,熟悉多进程、多线程编程;多进程情况下面了解大部分多进程之间的通信方式,能够灵活选择通信方式(共享内存/信号量/管道等);多线程编程能够良好的解决锁冲突问题,并且能够进行多线程程序的开发调试工作;同时对网络编程比较熟悉,了解多进程模型/多线程模型/异步网络IO模型的差别和选型,熟悉不同异步网络IO模型的原理和差异(select/poll/epoll/iocp等),并且熟悉常见的异步框架(ACE/ICE/libev/libevent/libuv/Boost.ASIO等)和使用,如果闲暇也可以看看一些国产自己开发的库(比如muduo);同时能够设计好的高并发程序架构(leader-follow/master-worker等);了解大部分C/C++后端Server开发中的问题(内存管理、日志打印、高并发、前后端通信协议、服务监控),知道各个后端服务RPC通信问题(struct/http/thirft/protobuf等);能够更熟络的使用GCC和GDB来开发编译调试程序,在线上程序core掉后能够迅速追查跟踪解决问题;通用模块开发方面,可以积累或者开发一些通用的工具或库(比如异步网络框架、日志库、内存池、线程池等),不过开发后是否应用要谨慎,省的埋坑去追bug;

7. 前端:

深入了解HTTP协议(包括各个细致协议特殊协议代码和背后原因,比如302静态文件缓存了,502是nginx后面php挂了之类的);除了之前的前端方面的各种框架应用整合能力,前端方面的学习如果有兴趣可以更深入,表现形式是,可以自己开发一些类似jQuery的前端框架,或者开发一个富文本编辑器之类的比较琐碎考验JavaScript功力;

8. 其他领域语言学习:

在基础的PHP/C/C++语言方面有基本积累,建议在当前阶段可以尝试学习不同的编程语言,看个人兴趣爱好,脚本类语言可以学学 Python/Ruby 之类的,函数式编程语言可以试试 Lisp/Haskell/Scala/Erlang 之类的,静态语言可以试试 Java/Golang,数据统计分析可以了解了解R语言,如果想换个视角做后端业务,可以试试 Node.js还有前面提到的跟Nginx结合的Nginx_Lua等。学习不同的语言主要是提升自己的视野和解决问题手段的差异,比如会了解除了进程/线程,还有轻量级协程;比如在跨机器通信场景下面,Erlang的解决方案简单的惊人;比如在不想选择C/C++的情况下,还有类似高效的Erlang/Golang可用等等;主要是提升视野。

9. 其他专业方向学习:

在本阶段里面,会除了基本的LNMP技能之外,会考虑一些其他领域知识的学习,这些都是可以的,看个人兴趣和长期的目标方向。目前情况能够选择的领域比较多,比如、云计算(分布式存储、分布式计算、虚拟机等),机器学习(数据挖掘、模式识别等,应用到统计、个性化推荐),自然语言处理(中文分词等),搜索引擎技术、图形图像、语音识别等等。除了这些高大上的,也有很多偏工程方面可以学习的地方,比如高性能系统、移动开发(Android/IOS)、计算机安全、嵌入式系统、硬件等方向。

10. 系统设计:

系统设计在第二阶段的基础之上,能够应用掌握的经验技能,设计出比较复杂的中大型系统,能够解决大部分线上的各种复杂系统的问题,完成类似 浏览器 -> CDN -> 负载均衡 ->接入层 -> Nginx+PHP -> 业务缓存 -> 数据库 -> 各路复杂后端RPC交互(存储后端、逻辑后端、反作弊后端、外部服务) -> 更多后端 酱紫的复杂业务;能够支撑每天数千万到数亿流量网站的正常开发维护工作。

 

PHP工程师面临的成长瓶颈

PHP工程师面临成长瓶颈

  先明确这里所指的PHP工程师,是指主要以PHP进行Web系统的开发,没有使用其的语言工作过。工作经验大概在3~4年,普通的Web系统(百万级访问,千成级数据以内或业务逻辑不是特别复杂)开发起基本得心应手,没有什么问题。但他们会这样的物点:

  ◆除了PHP不使用其它的语言,可能会点shell 脚本。

  ◆对PHP的掌握不精(很多PHP手册都没有看完,库除外)。

  ◆知识面比较窄(面对需求,除开使用PHP和mysql ,不知道其它的解决办法)。

  ◆PHP代码以过程为主,认为面向对象的实现太绕,看不懂。

  这些PHPer在遇到需要高性能,处理高并发,大量数据的项目或业务逻辑比较复杂(系统需要解决多领域业务的问题)时,缺少思路。不能分析问题的本质,技术判断力比较差,对于问题较快能找出临时的解决办法,但常常在不断临时性的解决办法中,系统和自己一步步走向崩溃。那怎么提高自己呢?怎么可以挑战难度更高的系统?

  更高的挑战在那里?

  结合我自己的经验,我列出一些具体挑战,让大家先有个感性的认识。

  高性能系统的挑战在那里?

  ◆如何选择Web服务器?要不要使用fast-cgi 模式;

  ◆要不要使用反向代理服务?选择全内存缓存还是硬盘缓存?

  ◆是否需要负载均衡?是基于应用层,还是网络层? 如何保证高可靠性?

  ◆你的PHP代码性能如何,使用优化工具后怎么样? 性能瓶颈在那里? 是否需要写成C的扩展?

  ◆用户访问有什么特点,是读多还是写多?是否需要读写分离?

  ◆数据如何存储?写入速度和读出速度如何? 数据增涨访问速读如何变化?

  ◆如何使用缓存? 怎么样考虑失效?数据的一致性怎么保证?

  高复杂性系统的挑战在那里?

  ◆能否识别业务所对应的领域?是一个还是多个?

  ◆能否合理对业务进行抽象,在业务规则变化能以很小的代价实现?

  ◆数据的一致性、安全性可否保证?

  ◆是否撑握了面向对象的分析和设计的方法

  这里所列出的问题,你都能肯定的回答,说明在技术上你基本已经可能成为架构师了。如何你还不能回答,你需要在以下几个方向加强。

  怎么样提高,突破瓶颈

  如何你还不能回答,你需要在以下几个方向加强:

  ◆分析你所使用的技术其原理和背后运行的机制,这样可以提高你的技术判断力,提高你技术方案选择的正确性;

  ◆学习大学期间重要的知识, 操作系统原理,数据结构和算法。知道你以前学习都是为了考试,但现在你需要为自己学习,让自己知其所以然;

  ◆重新开始学习C语言,虽然你在大学已经学过。这不仅是因为你可能需要写PHP扩展,而且还因为,在做C的应用中,有一个时刻关心性能、内存控制、变量生命周期、数据结构和算法的环境;

  ◆学习面向对象的分析与设计,它是解决复杂问题的有效的方法。学习抽象,它是解决复杂问题的唯一之道。

作者:qq_35114086 发表于2016/9/24 10:04:25 原文链接
阅读:221 评论:0 查看评论

PHP验证码类,简单安全的PHP验证码

$
0
0

PHP验证码类,简单安全的PHP验证码

作者:zouyi615

一,验证码示例



二,php验证码类,secoder.class.php

<?php
/**
 * 安全验证码
 * 
 * 安全的验证码要:验证码文字扭曲、旋转,使用不同字体,添加干扰码
 *
 * @author 流水孟春 <cmpan(at)qq.com>
 * @link http://labs.yulans.cn/YL_Security_Secoder
 * @link http://wiki.yulans.cn/docs/yl/security/secoder
 */
class YL_Security_Secoder {
	/**
	 * 验证码的session的下标
	 * 
	 * @var string
	 */
	//public static $seKey = 'sid.sek ey.ylans.cn';
	public static $seKey = 'sid';
	public static $expire = 3000;     // 验证码过期时间(s)
	/**
	 * 验证码中使用的字符,01IO容易混淆,建议不用
	 *
	 * @var string
	 */
	public static $codeSet = '346789ABCDEFGHJKLMNPQRTUVWXY';
	public static $fontSize = 25;     // 验证码字体大小(px)
	public static $useCurve = true;   // 是否画混淆曲线
	public static $useNoise = true;   // 是否添加杂点	
	public static $imageH = 0;        // 验证码图片宽
	public static $imageL = 0;        // 验证码图片长
	public static $length = 4;        // 验证码位数
	public static $bg = array(243, 251, 254);  // 背景
	
	protected static $_image = null;     // 验证码图片实例
	protected static $_color = null;     // 验证码字体颜色
	
	/**
	 * 输出验证码并把验证码的值保存的session中
	 * 验证码保存到session的格式为: $_SESSION[self::$seKey] = array('code' => '验证码值', 'time' => '验证码创建时间');
	 */
	public static function entry() {
		// 图片宽(px)
		self::$imageL || self::$imageL = self::$length * self::$fontSize * 1.5 + self::$fontSize*1.5; 
		// 图片高(px)
		self::$imageH || self::$imageH = self::$fontSize * 2;
		// 建立一幅 self::$imageL x self::$imageH 的图像
		self::$_image = imagecreate(self::$imageL, self::$imageH); 
		// 设置背景      
		imagecolorallocate(self::$_image, self::$bg[0], self::$bg[1], self::$bg[2]); 
		// 验证码字体随机颜色
		self::$_color = imagecolorallocate(self::$_image, mt_rand(1,120), mt_rand(1,120), mt_rand(1,120));
		// 验证码使用随机字体 
		//$ttf = dirname(__FILE__) . '/ttfs/' . mt_rand(1, 20) . '.ttf';  4
		$ttf = dirname(__FILE__) . '/ttfs/4.ttf';  
		
		if (self::$useNoise) {
			// 绘杂点
			self::_writeNoise();
		} 
		if (self::$useCurve) {
			// 绘干扰线
			self::_writeCurve();
		}
		
		// 绘验证码
		$code = array(); // 验证码
		$codeNX = 0; // 验证码第N个字符的左边距
		for ($i = 0; $i<self::$length; $i++) {
			$code[$i] = self::$codeSet[mt_rand(0, 27)];
			$codeNX += mt_rand(self::$fontSize*1.2, self::$fontSize*1.6);
			// 写一个验证码字符
			imagettftext(self::$_image, self::$fontSize, mt_rand(-40, 70), $codeNX, self::$fontSize*1.5, self::$_color, $ttf, $code[$i]);
		}
		
		// 保存验证码
		isset($_SESSION) || session_start();
		$_SESSION[self::$seKey]['code'] = join('', $code); // 把校验码保存到session
		$_SESSION[self::$seKey]['time'] = time();  // 验证码创建时间
				
		header('Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate');
		header('Cache-Control: post-check=0, pre-check=0', false);		
		header('Pragma: no-cache');		
		header("content-type: image/png");
	
		// 输出图像
		imagepng(self::$_image); 
		imagedestroy(self::$_image);
	}
	
	/** 
	 * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数) 
     *      
     *      高中的数学公式咋都忘了涅,写出来
	 *		正弦型函数解析式:y=Asin(ωx+φ)+b
	 *      各常数值对函数图像的影响:
	 *        A:决定峰值(即纵向拉伸压缩的倍数)
	 *        b:表示波形在Y轴的位置关系或纵向移动距离(上加下减)
	 *        φ:决定波形与X轴位置关系或横向移动距离(左加右减)
	 *        ω:决定周期(最小正周期T=2π/∣ω∣)
	 *
	 */
    protected static function _writeCurve() {
		$A = mt_rand(1, self::$imageH/2);                  // 振幅
		$b = mt_rand(-self::$imageH/4, self::$imageH/4);   // Y轴方向偏移量
		$f = mt_rand(-self::$imageH/4, self::$imageH/4);   // X轴方向偏移量
		$T = mt_rand(self::$imageH*1.5, self::$imageL*2);  // 周期
		$w = (2* M_PI)/$T;
						
		$px1 = 0;  // 曲线横坐标起始位置
		$px2 = mt_rand(self::$imageL/2, self::$imageL * 0.667);  // 曲线横坐标结束位置 	    	
		for ($px=$px1; $px<=$px2; $px=$px+ 0.9) {
			if ($w!=0) {
				$py = $A * sin($w*$px + $f)+ $b + self::$imageH/2;  // y = Asin(ωx+φ) + b
				$i = (int) ((self::$fontSize - 6)/4);
				while ($i > 0) {	
				    imagesetpixel(self::$_image, $px + $i, $py + $i, self::$_color);  // 这里画像素点比imagettftext和imagestring性能要好很多				    
				    $i--;
				}
			}
		}
		
		$A = mt_rand(1, self::$imageH/2);                  // 振幅		
		$f = mt_rand(-self::$imageH/4, self::$imageH/4);   // X轴方向偏移量
		$T = mt_rand(self::$imageH*1.5, self::$imageL*2);  // 周期
		$w = (2* M_PI)/$T;		
		$b = $py - $A * sin($w*$px + $f) - self::$imageH/2;
		$px1 = $px2;
		$px2 = self::$imageL;
		for ($px=$px1; $px<=$px2; $px=$px+ 0.9) {
			if ($w!=0) {
				$py = $A * sin($w*$px + $f)+ $b + self::$imageH/2;  // y = Asin(ωx+φ) + b
				$i = (int) ((self::$fontSize - 8)/4);
				while ($i > 0) {			
				    imagesetpixel(self::$_image, $px + $i, $py + $i, self::$_color);  // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多	
				    $i--;
				}
			}
		}
	}
	
	/**
	 * 画杂点
	 * 往图片上写不同颜色的字母或数字
	 */
	protected static function _writeNoise() {
		for($i = 0; $i < 10; $i++){
			//杂点颜色
		    $noiseColor = imagecolorallocate(
		                      self::$_image, 
		                      mt_rand(150,225), 
		                      mt_rand(150,225), 
		                      mt_rand(150,225)
		                  );
			for($j = 0; $j < 5; $j++) {
				// 绘杂点
			    imagestring(
			        self::$_image,
			        5, 
			        mt_rand(-10, self::$imageL), 
			        mt_rand(-10, self::$imageH), 
			        self::$codeSet[mt_rand(0, 27)], // 杂点文本为随机的字母或数字
			        $noiseColor
			    );
			}
		}
	}
	
	/**
	 * 验证验证码是否正确
	 *
	 * @param string $code 用户验证码
	 * @param bool 用户验证码是否正确
	 */
	public static function check($code) {
		isset($_SESSION) || session_start();
		// 验证码不能为空
		if(empty($code) || empty($_SESSION[self::$seKey])) {
			//echo $_SESSION[self::$seKey]['code'].'1';
			return false;
					
		}
		// session 过期
		if(time() - $_SESSION[self::$seKey]['time'] > self::$expire) {
			unset($_SESSION[self::$seKey]);
			//echo $_SESSION[self::$seKey]['code'].'2';
			return false;
			//return 0;
		}

//		if($code == $_SESSION[self::$seKey]['code']) {
		if(strtoupper($code) == $_SESSION[self::$seKey]['code']) { //不区分大小写比较
			//echo $_SESSION[self::$seKey]['code'].'3';
			return true;		
		}
		//echo $_SESSION[self::$seKey]['code'].'4';
		return false;
				
	}
}


// useage
/*
YL_Security_Secoder::$useNoise = false;  // 要更安全的话改成true
YL_Security_Secoder::$useCurve = true;
YL_Security_Secoder::entry();
*/

/*
// 验证验证码
if (!YL_Security_Secoder::check(@$_POST['secode'])) {
	print 'error secode';
}
*/


三,调用方法

1,显示验证码页面code.php

<?php  
	session_start();
	require 'secoder.class.php';  //先把类包含进来,实际路径根据实际情况进行修改。  
	$vcode = new YL_Security_Secoder();      //实例化一个对象  
	$vcode->entry();  
?> 
2,检查验证码是否正确
<?php  
	session_start();
	require 'secoder.class.php';  //先把类包含进来,实际路径根据实际情况进行修改。  
	$vcode = new YL_Security_Secoder();      //实例化一个对象  
	//$vcode->entry();  
	$code = $_GET['code']; 
	echo $vcode->check($code);        
	//$_SESSION['code'] = $vc->getCode();//验证码保存到SESSION中
?> 
3,验证码输入框调用页面

<img id="messageImg" src='images/tishis2.gif' width='16' height='16'> 单击图片重新获取验证码<br>
<a href="#"><img src="code.php" onclick="javascript:this.src='code.php?tm='+Math.random();" />


作者:qq_35114086 发表于2016/9/24 10:23:02 原文链接
阅读:236 评论:0 查看评论

Android热修复学习(HotFix)

$
0
0

参考1
参考2
参考3
参考4

一:热修复相关

热修复概念: 以补丁的方式动态修复紧急Bug,不再需要重新发布App,不再需要用户重新下载。
PathClassloader和DexClassLoader:
(1)PathClassloader作为其系统类和应用类的加载器,只能去加载已经安装到Android系统中的apk文件。
(2)DexClassLoader可以用来从.jar和.apk类型的文件内部加载classes.dex文件。可以用来执行非安装的程序代码。
(3)Android使用PathClassLoader作为其类加载器,DexClassLoader可以从.jar和.apk类型的文件内部加载classes.dex文件。
热修复原理:
PathClassLoader和DexClassLoader都继承自BaseDexClassLoader
在BaseDexClassLoader中有如下源码:

#BaseDexClassLoader
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    Class clazz = pathList.findClass(name);

    if (clazz == null) {
        throw new ClassNotFoundException(name);
    }

    return clazz;
}

#DexPathList
public Class findClass(String name) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext);
            if (clazz != null) {
                return clazz;
            }
        }
    }

    return null;
}

#DexFile
public Class loadClassBinaryName(String name, ClassLoader loader) {
    return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);1  n j                                                                                                                         
n 

1 BaseDexClassLoader中有个pathList对象,pathList中包含一个DexFile的集合dexElements,而对于类加载呢,就是遍历这个集合,通过DexFile去寻找。
2 一个ClassLoader可以包含多个dex文件,每个dex文件是一个Element,多个dex文件排列成一个有序的数组dexElements,当找类的时候,会按顺序遍历dex文件,然后从当前遍历的dex文件中找类,如果找类则返回,如果找不到从下一个dex文件继续查找。
3 理论上,如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类,如下图:

图1
4 把有问题的类打包到一个dex(patch.dex)中去,然后把这个dex插入到Elements的最前面,如下图:

图2

二:阻止相关类打上CLASS_ISPREVERIFIED标志

dex校验: 如果两个相关联的类在不同的dex中就会报错,例如ClassA 引用了ClassB,但是发现这这两个类所在的dex不在一起,其中:
1. ClassA 在classes.dex中
2. ClassB 在patch.dex中
结果发生了错误。

dex校验的前提: 如果引用者这个类被打上了CLASS_ISPREVERIFIED标志,那么就会进行dex的校验。

相关类打上CLASS_ISPREVERIFIED标志的发生场景:
在虚拟机启动的时候,当verify选项被打开的时候,如果static方法、private方法、构造函数等,其中的直接引用(第一层关系)到的类都在同一个dex文件中,那么这个类就会被打上CLASS_ISPREVERIFIED
下图是class A 打上CLASS_ISPREVERIFIED标志
图三
下图是class A 没有打上CLASS_ISPREVERIFIED标志
图四
其中AntilazyLoad类会被打包成单独的hack.dex,这样当安装apk的时候,classes.dex内的类都会引用一个在不相同dex中的AntilazyLoad类,这样就防止了类被打上CLASS_ISPREVERIFIED的标志了,只要没被打上这个标志的类都可以进行打补丁操作。

在class文件中插入代码来阻止相关类打上CLASS_ISPREVERIFIED标志:在dx工具执行之前,将LoadBugClass.class文件呢,进行修改,再其构造中添加System.out.println(dodola.hackdex.AntilazyLoad.class),然后继续打包的流程。
原始代码

package dodola.hackdex;
public class AntilazyLoad
{

}

package dodola.hotfix;
public class BugClass
{
    public String bug()
    {
        return "bug class";
    }
}

package dodola.hotfix;
public class LoadBugClass
{
    public String getBugString()
    {
        BugClass bugClass = new BugClass();
        return bugClass.bug();
    }
}

三:插入jar

插入代码

System.out.println(dodola.hackdex.AntilazyLoad.class)

在构造函数中插入操作代码(javassist)

package test;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

public class InjectHack
{
    public static void main(String[] args)
    {
        try
        {
            String path = "/Users/zhy/develop_work/eclipse_android/imooc/JavassistTest/";
            ClassPool classes = ClassPool.getDefault();
            classes.appendClassPath(path + "bin");//项目的bin目录即可
            CtClass c = classes.get("dodola.hotfix.LoadBugClass");
            CtConstructor ctConstructor = c.getConstructors()[0];
            ctConstructor
                    .insertAfter("System.out.println(dodola.hackdex.AntilazyLoad.class);");
            c.writeFile(path + "/output");
        } catch (Exception e)
        {
            e.printStackTrace();
        }

    }
}

把AntilazyLoad.class打包成jar包,然后写入App的私有目录,最后把该jar对应的dexElements文件插入到数组的最前面。

public class HotfixApplication extends Application
{

    @Override
    public void onCreate()
    {
        super.onCreate();
       //创建jar对应的文件
        File dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "hackdex_dex.jar");
       //将asset文件中的jar写到App的私有目录下面。
        Utils.prepareDex(this.getApplicationContext(), dexPath, "hackdex_dex.jar");
       // 把jar对应的dexElements插入到dex数组最前面。
        HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hackdex.AntilazyLoad");
        try
        {
            this.getClassLoader().loadClass("dodola.hackdex.AntilazyLoad");
        } catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        }

    }
}

创建jar对应的文件

public class Utils {
    private static final int BUF_SIZE = 2048;

    public static boolean prepareDex(Context context, File dexInternalStoragePath, String dex_file) {
        BufferedInputStream bis = null;
        OutputStream dexWriter = null;
        bis = new BufferedInputStream(context.getAssets().open(dex_file));
        dexWriter = new BufferedOutputStream(new FileOutputStream(dexInternalStoragePath));
        byte[] buf = new byte[BUF_SIZE];
        int len;
        while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) {
            dexWriter.write(buf, 0, len);
        }
        dexWriter.close();
        bis.close();
        return true;

}

找相应的ClassLoader进行操作

public final class HotFix
{
    public static void patch(Context context, String patchDexFile, String patchClassName)
    {
        if (patchDexFile != null && new File(patchDexFile).exists())
        {
            try
            {
                if (hasLexClassLoader())
                {
                    injectInAliyunOs(context, patchDexFile, patchClassName);
                } else if (hasDexClassLoader())
                {
                    injectAboveEqualApiLevel14(context, patchDexFile, patchClassName);
                } else
                {

                    injectBelowApiLevel14(context, patchDexFile, patchClassName);

                }
            } catch (Throwable th)
            {
            }
        }
    }
 }

Combine(合并)App的DexElements和*AntilazyLoad.class的DexElements*

 private static boolean hasDexClassLoader()
{
    try
    {
        Class.forName("dalvik.system.BaseDexClassLoader");
        return true;
    } catch (ClassNotFoundException e)
    {
        return false;
    }
}


 private static void injectAboveEqualApiLevel14(Context context, String str, String str2)
            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException
{
    PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
    Object a = combineArray(getDexElements(getPathList(pathClassLoader)),
            getDexElements(getPathList(
                    new DexClassLoader(str, context.getDir("dex", 0).getAbsolutePath(), str, context.getClassLoader()))));
    Object a2 = getPathList(pathClassLoader);
    setField(a2, a2.getClass(), "dexElements", a);
    pathClassLoader.loadClass(str2);
}

将Patch.jar补丁插入到APP中,过程和插入AntilazyLoad.class一样

public class HotfixApplication extends Application
{

    @Override
    public void onCreate()
    {
        super.onCreate();
        File dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "hackdex_dex.jar");
        Utils.prepareDex(this.getApplicationContext(), dexPath, "hack_dex.jar");
        HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hackdex.AntilazyLoad");
        try
        {
            this.getClassLoader().loadClass("dodola.hackdex.AntilazyLoad");
        } catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        }

        dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "path_dex.jar");
        Utils.prepareDex(this.getApplicationContext(), dexPath, "path_dex.jar");
        HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hotfix.BugClass");

    }
}

四:总结

(1)因为我们的Patch是以独立的jar包,插入到APP的DexElements中,
所以如果APP中中的类引用了Patch中的类,就会在校验时报错。因为当进行dex校验时,如果两个相关联的类在不同的dex中就会报错。(LoadBugClass引用BugClass)
(2) 为了防止上述错误就要阻止Dex校验,阻止Dex校验的方法是阻止相关类打上CLASS_ISPREVERIFIED标志。
(3) 阻止相关类打上CLASS_ISPREVERIFIED标志的做法是:在相关引用的类(LoadBugClass.class)的构造方法中,引用另外一个jar中类AntilazyLoad.class
(4)因为AntilazyLoad.class在另一个jar中,所以需要把该jar对应的dex插入到App中。并且在Application中的onCreate()方法中将该类加载进来。
(5)把Patch.jar对应的dexElement加载进App中。因为Patch.jar放在dex数组的第一个位置,所以首先被加载。即:如果在不同的dex中有相同的类存在,那么会优先选择排在前面的dex文件的类。

作者:shuixingge1992 发表于2016/9/24 10:30:49 原文链接
阅读:220 评论:0 查看评论

android handler详解

$
0
0

先看演示:

一个Handler允许你发送和处理消息(Message)以及与一个线程的消息队列相关的Runnable对象。每个Handler实例都和单个线程以及该线程的消息队列有关。当你创建了一个新Handler,它就会和创建它的线程/消息队列绑定,在那以后,它就会传递消息以及runnable对象给消息队列,然后执行它们。

1为什么使用Handler

 需要使用Handler有两大主要的原因:

      (1)在将来的某个时间点调度处理消息和runnable对象;

      (2)将需要执行的操作放到其他线程之中,而不是自己的;

     调度处理消息是通过调用post(Runnable), postAtTime(Runnable, long),postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message),sendMessageAtTime(Message, long)和sendMessageDelayed(Message,long)等方法完成的。其中的post版本的方法可以让你将Runnable对象放进消息队列;sendMessage版本的方法可以让你将一个包含有bundle对象的消息对象放进消息队列,然后交由handleMessage(Message)方法处理。(这个需要你复写Handler的handleMessage方法)

    Handler在实际开发中是很常用的,主要是用来接收子线程发送的数据,然后主线程结合此数据来更新界面UI。

     Android应用程序启动时,他会开启一个主线程(也就是UI线程),管理界面中的UI控件,进行事件派发,比如说:点击一个按钮,Android会分发事件到Button上从而来响应你的操作。但是当你需要执行一个比较耗时的操作的话,例如:进行IO操作,网络通信等等,若是执行时间超过5s,那么Android会弹出一个“经典”的ANR无响应对话框,然后提示按“Force quit”或是“Wait”。解决此类问题的方法就是:我们把一些耗时的操作放到子线程中去执行。但因为子线程涉及到UI更新,而Android主线程是线程不安全的,所以更新UI的操作只能放在主线程中执行,若是放在子线程中执行的话很会出问题。所以这时就需要一种机制:主线程可以发送“命令/任务”给子线程执行,然后子线程反馈执行结果;

若在主线程中实例化一个Handler对象,例如:

     Handler mHandler = newHandler();

     此时它并没有新派生一个线程来执行此Handler,而是将此Handler附加在主线程上,故此时若你在Handler中执行耗时操作的话,还是会弹出ANR对话框!

2 例子

下面就Handler的使用举一些例子,加深理解。

2.1、post版本的Handler 

public class MainActivity extends Activity     

                implements OnClickListener {     

    private final static String TAG = "HandlerTest";     

    private final static int DELAY_TIME = 1000;     

    private Button btnStart;     

    private Button btnStop;     

    Context mContext = null;     

    /** Called when the activity is first created. */    

    @Override    

    public void onCreate(Bundle savedInstanceState) {     

        super.onCreate(savedInstanceState);     

        setContentView(R.layout.main);     

        mContext = this;     

    

        Log.i(TAG, "Main thread id = " +      

                Thread.currentThread().getId());     

        btnStart = (Button) findViewById(R.id.btn_start);     

        btnStart.setOnClickListener(this);     

        btnStop = (Button) findViewById(R.id.btn_stop);     

        btnStop.setOnClickListener(this);     

    }     

    @Override    

    public void onClick(View view) {     

        switch (view.getId()) {     

        case R.id.btn_start:     

            mHandler.postDelayed(workRunnable, DELAY_TIME);     

            break;     

        case R.id.btn_stop:     

            mHandler.removeCallbacks(workRunnable);     

            break;     

        }     

    }     

    Runnable workRunnable = new Runnable() {     

        int counter = 0;     

             

        public void run() {     

            if (counter++ < 1) {     

                Log.i(TAG, "workRunnable thread id = " +      

                        Thread.currentThread().getId());     

                mHandler.postDelayed(workRunnable, DELAY_TIME);     

            }     

        }     

    };     

    Handler mHandler = new Handler();     

}     

  

说明:发现thread id是相同的,这就说明:默认情况下创建的Handler会绑定到主线程上,你不能做太耗时的操作。

2.2 HandlerThread

  

public class MyThread2 extends Activity {     

    private Handler handler = null;     

    @Override    

    public void onCreate(Bundle savedInstanceState) {     

        super.onCreate(savedInstanceState);     

        HandlerThread handlerThread = new HandlerThread("myHandlerThread");     

        handlerThread.start();     

        handler = new Handler(handlerThread.getLooper());     

        handler.post(new MyRunnable());     

        System.out.println("Oncreate---The Thread id is :"    

                + Thread.currentThread().getId());     

        setContentView(R.layout.main);     

    }     

    private class MyRunnable implements Runnable {     

        public void run() {     

            System.out.println("Runnable---The Thread is running"); 

            System.out.println("Runnable---The Thread id is :"    

                    + Thread.currentThread().getId());     

            try {     

                Thread.sleep(6000);     

            } catch (InterruptedException e) {     

                // TODO Auto-generated catch block    

                e.printStackTrace();     

            }     

        }     

    }     

}    

  

 

在这个demo中,用到了HandlerThread,在HandlerThread对象中可以通过getLooper方法获取一个Looper对象控制句柄,我们可以将其这个Looper对象映射到一个Handler中去来实现一个线程同步机制。于是就有以下结果;

1:控制台的输出: Oncreate---The Thread id is :1

                             Runnable---The Threadis running

                             Runnable---The Threadid is :10

2:程序启动后,我们立刻看到main.xml中的内容。

这样就达到了多线程的结果

2.3 sendMessage版本的Handler

这里介绍几种模型:

2.3.1 默认的Handler

(消息处理队列挂在主线程上)

  public class MainActivity extends Activity     

                      implements OnClickListener {     

        private final static String TAG = "HandlerTest";     

        private final static int TASK_BEGIN = 1;     

        private final static int TASK_1 = 2;     

        private final static int TASK_2 = 3;     

        private final static int TASK_END   = 4;     

        private Button btnStart = null;     

        private Button btnStop = null;     

        /** Called when the activity is first created. */    

        @Override    

        public void onCreate(Bundle savedInstanceState) {     

            super.onCreate(savedInstanceState);     

            setContentView(R.layout.main);     

                 

            btnStart = (Button) findViewById(R.id.btn_start);     

            btnStart.setOnClickListener(this);     

            btnStop = (Button) findViewById(R.id.btn_stop);     

            btnStop.setOnClickListener(this);     

                 

            Log.i(TAG, "[M_TID:" + Thread.currentThread().getId() + "]");     

        }     

             

        Handler mHandler = new Handler() {     

            // 注意:在各个case后面不能做太耗时的操作,否则出现ANR对话框     

            @Override    

            public void handleMessage(Message msg) {     

                switch (msg.what) {     

                case TASK_BEGIN:     

                    Log.i(TAG, "[H_TID:" +     

                        Thread.currentThread().getId() + "] Get TASK_BEGIN");     

                    break;     

                         

                case TASK_1:     

                    Log.i(TAG, "[H_TID:" +     

                        Thread.currentThread().getId() + "] Get TASK_1");     

                    break;     

                         

                case TASK_2:     

                    Log.i(TAG, "[H_TID:" +     

                        Thread.currentThread().getId() + "] Get TASK_2");     

                    break;     

                         

                case TASK_END:     

                    Log.i(TAG, "[H_TID:" +     

                        Thread.currentThread().getId() + "] Get TASK_END");     

                    finish();     

                    break;     

                }     

                super.handleMessage(msg);     

            }     

        };     

             

        public void onClick(View view) {     

            switch (view.getId()) {     

            case R.id.btn_start:     

                // 启动任务(消息只有标识,立即投递)     

                mHandler.sendEmptyMessage(TASK_BEGIN);     

                Log.i(TAG, "Send TASK_BEGIN to handler.");     

                     

                // 开始任务1(在mHandler的消息队列中获取一个Message对象,避免重复构造)     

                Message msg1 = mHandler.obtainMessage(TASK_1);     

                msg1.obj = "This is task1";     

                mHandler.sendMessage(msg1);     

                Log.i(TAG, "Send TASK_1 to handler.");     

                     

                // 开启任务2(和上面类似)     

                Message msg2 = Message.obtain();     

                msg2.arg1 = 10;     

                msg2.arg2 = 20;     

                msg2.what = TASK_2;     

                mHandler.sendMessage(msg2);     

                Log.i(TAG, "Send TASK_2 to handler.");     

                break;     

                     

            case R.id.btn_stop:     

                // 结束任务(空消息体,延时2s投递)     

                mHandler.sendEmptyMessageDelayed(TASK_END, 2000);     

                Log.i(TAG, "Send TASK_END to handler.");     

                break;     

            }     

    }     

    }  

2.3.2消息队列仍绑定在主线程上

在子线程中发送消息。

   

 public class MainActivity extends Activity {     

        private final static String TAG = "HandlerTest";     

        private final static int TASK_BEGIN = 1;     

        private final static int TASK_1 = 2;     

        private final static int TASK_2 = 3;     

        private final static int TASK_END   = 4;     

            /** Called when the activity is first created. */    

        @Override    

        public void onCreate(Bundle savedInstanceState) {     

            super.onCreate(savedInstanceState);     

            setContentView(R.layout.main);     

                 

            Log.i(TAG, "[M_TID:" + Thread.currentThread().getId() + "]" +     

                    "This is in main thread.");     

                 

            workThread.start();     

        }     

             

        Handler mHandler = new Handler() {     

            // 注意:在各个case后面不能做太耗时的操作,否则出现ANR对话框     

            @Override    

            public void handleMessage(Message msg) {     

                switch (msg.what) {     

                case TASK_BEGIN:     

                    Log.i(TAG, "[H_TID:" +     

                    Thread.currentThread().getId() + "] Get TASK_BEGIN");     

                    break;     

                         

                case TASK_1:     

                    Log.i(TAG, "[H_TID:" +     

                    Thread.currentThread().getId() + "] Get TASK_1");     

                    break;     

                         

                case TASK_2:     

                    Log.i(TAG, "[H_TID:" +     

                    Thread.currentThread().getId() + "] Get TASK_2");     

                    break;     

                         

                case TASK_END:     

                    Log.i(TAG, "[H_TID:" +     

                    Thread.currentThread().getId() + "] Get TASK_END");     

                    finish();     

                    break;     

                }     

                super.handleMessage(msg);     

            }     

        };     

             

        Thread workThread = new Thread() {     

            // 你可以在run方法内做任何耗时的操作,然后将结果以消息形式投递到主线程的消息队列中     

            @Override    

            public void run() {     

                // 启动任务(消息只有标识,立即投递)     

                mHandler.sendEmptyMessage(TASK_BEGIN);     

                Log.i(TAG, "[S_TID:" + Thread.currentThread().getId() + "]" +     

                        "Send TASK_START to handler.");     

                     

                // 开始任务1(在mHandler的消息队列中获取一个Message对象,避免重复构造)     

                Message msg1 = mHandler.obtainMessage(TASK_1);     

                msg1.obj = "This is task1";     

                mHandler.sendMessage(msg1);     

                Log.i(TAG, "[S_TID:" + Thread.currentThread().getId() + "]" +     

                        "Send TASK_1 to handler.");     

                     

                // 开启任务2(和上面类似)     

                Message msg2 = Message.obtain();     

                msg2.arg1 = 10;     

                msg2.arg2 = 20;     

                msg2.what = TASK_2;     

                mHandler.sendMessage(msg2);     

                Log.i(TAG, "[S_TID:" + Thread.currentThread().getId() + "]" +     

                        "Send TASK_2 to handler.");     

                     

                // 结束任务(空消息体,延时2s投递)     

                mHandler.sendEmptyMessageDelayed(TASK_END, 2000);     

                Log.i(TAG, "[S_TID:" + Thread.currentThread().getId() + "]" +     

                        "Send TASK_END to handler.");     

            }     

        };     

    }    

2.3.3将消息队列绑定到子线程上,

主线程只管通过Handler往子线程的消息队列中投递消息即可。

public class MainActivity extends Activity {     

    private final static String TAG = "HandlerTest";        

    private final static int TASK_BEGIN = 1;     

    private final static int TASK_1 = 2;     

    private final static int TASK_2 = 3;     

    private final static int TASK_END   = 4;     

    private MyHandler mHandler = null;     

    /** Called when the activity is first created. */    

    @Override    

    public void onCreate(Bundle savedInstanceState) {     

        super.onCreate(savedInstanceState);     

        setContentView(R.layout.main);     

    

        Log.i(TAG, "[M_TID:" + Thread.currentThread().getId() + "]" +     

                "This is in main thread.");     

             

        HandlerThread myLooperThread = new HandlerThread("my looper thread");     

        myLooperThread.start();     

             

        Looper looper = myLooperThread.getLooper();     

        mHandler = new MyHandler(looper);     

             

        // 启动任务(消息只有标识,立即投递)     

        mHandler.sendEmptyMessage(TASK_BEGIN);     

        Log.i(TAG, "[S_ID:" + Thread.currentThread().getId() + "]" +     

                "Send TASK_START to handler.");     

             

        // 开始任务1(在mHandler的消息队列中获取一个Message对象,避免重复构造)     

        Message msg1 = mHandler.obtainMessage(TASK_1);     

        msg1.obj = "This is task1";     

        mHandler.sendMessage(msg1);     

        Log.i(TAG, "[S_ID:" + Thread.currentThread().getId() + "]" +     

                "Send TASK_1 to handler.");     

             

        // 开启任务2(和上面类似)     

        Message msg2 = Message.obtain();     

        msg2.arg1 = 10;     

        msg2.arg2 = 20;     

        msg2.what = TASK_2;     

        mHandler.sendMessage(msg2);     

        Log.i(TAG, "[S_ID:" + Thread.currentThread().getId() + "]" +     

                "Send TASK_2 to handler.");     

             

        // 结束任务(空消息体,延时2s投递)     

        mHandler.sendEmptyMessageDelayed(TASK_END, 2000);     

        Log.i(TAG, "[S_ID:" + Thread.currentThread().getId() + "]" +     

                "Send TASK_END to handler.");     

    }     

         

    class MyHandler extends Handler {     

        public MyHandler(Looper looper) {     

            super(looper);     

        }     

             

        // 现在在每个case之后,你可以做任何耗时的操作了     

        @Override    

        public void handleMessage(Message msg) {     

            switch (msg.what) {     

            case TASK_BEGIN:     

                Log.i(TAG, "[H_TID:" +     

                    Thread.currentThread().getId() + "] Get TASK_BEGIN");     

                break;     

                     

            case TASK_1:     

                Log.i(TAG, "[H_TID:" +     

                    Thread.currentThread().getId() + "] Get TASK_1");     

                break;     

                     

            case TASK_2:     

                Log.i(TAG, "[H_TID:" +     

                    Thread.currentThread().getId() + "] Get TASK_2");     

                break;     

                     

            case TASK_END:     

                Log.i(TAG, "[H_TID:" +     

                    Thread.currentThread().getId() + "] Get TASK_END");     

                finish();     

                break;     

            }     

            super.handleMessage(msg);     

        }     

    }     

}    

  

3 Demo源码

package mm.shandong.com.testhandler;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

public class TestHandlerActivity extends AppCompatActivity {
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_handler);
        textView= (TextView) findViewById(R.id.textView);
    }
    public void createSimpleHandler(View view){
        final Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 1:///一般用16进制表示如0x0001
                        textView.setText(String.valueOf(msg.arg1));
                        break;
                }
            }
        };
        new Thread(){
            @Override
            public void run() {
                int i=0;
                while (i<50){
                    try {
                        Thread.sleep(100);
                        Message msg=new Message();
                        msg.what=1;
                        msg.arg1=i;
                        handler.sendMessage(msg);
                        i++;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
    public void createSimpleHandler2(View view){
        final Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 0x0002:///一般用16进制表示如0x0001
                        Bundle bundle=msg.getData();
                        Map map= (Map) bundle.get("map");
                        String name= (String) map.get("name");
                        textView.setText(name);
                        break;
                }
            }
        };
        new Thread(){
            @Override
            public void run() {
                int i=0;
                while (i<50){
                    try {
                        Thread.sleep(100);
                        Message msg=new Message();
                        msg.what=0x0002;
                        Bundle bundle=new Bundle();
                        Map map=new HashMap<>();
                        map.put("name","张三"+i);
                        bundle.putSerializable("map", (Serializable) map);
                        msg.setData(bundle);
                        handler.sendMessage(msg);
                        i++;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
    public  void createSimpleHandler3(View view){
        final Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 1:///一般用16进制表示如0x0001
                        Toast.makeText(TestHandlerActivity.this,"这是一条空消息",Toast.LENGTH_SHORT).show();
                        break;
                }
            }
        };
        handler.sendEmptyMessage(1);
    }
    public  void createSimpleHandler4(View view){
        final Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 1:///一般用16进制表示如0x0001
                        Toast.makeText(TestHandlerActivity.this,"这是延迟发送的一条空消息",Toast.LENGTH_SHORT).show();
                        break;
                }
            }
        };
        handler.sendEmptyMessageDelayed(1,2000);
    }
    public  void createSimpleHandler5(View view){
        final Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 1:///一般用16进制表示如0x0001
                        Toast.makeText(TestHandlerActivity.this,"这是通过obtainMessage从消息池中得到一条消息并发送," +
                                ""+"参数是:"+msg.arg1,Toast.LENGTH_LONG).show();
                        break;
                }
            }
        };
        Message msg=handler.obtainMessage();
        msg.what=1;
        msg.arg1=3;
        handler.sendMessage(msg);
    }
    public  void createSimpleHandler6(View view){
        MThreadNoLooper mThreadNoLooper=new MThreadNoLooper();
        mThreadNoLooper.start();
        if(mThreadNoLooper.handler!=null){
            mThreadNoLooper.handler.sendEmptyMessage(1);
        }
    }
    public  void createSimpleHandler7(View view){
        MThread mThread=new MThread();
        mThread.start();
        try {
            Thread.sleep(100);
            if(mThread.handler!=null){
                mThread.handler.sendEmptyMessage(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    public class  MThreadNoLooper extends  Thread{
        public Handler handler;
        @Override
        public void run() {
            handler=new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what){
                        case 1:///一般用16进制表示如0x0001
                            Toast.makeText(TestHandlerActivity.this,"这是子线程中的handler监听到的消息",Toast.LENGTH_LONG).show();
                            break;
                    }
                }
            };
        }
    }
    public class  MThread extends  Thread{
        public Handler handler;
        @Override
        public void run() {
            Looper.prepare();
            handler=new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what){
                        case 1:///一般用16进制表示如0x0001
                            Toast.makeText(TestHandlerActivity.this,"这是子线程中的handler监听到的消息",Toast.LENGTH_LONG).show();
                            break;
                    }
                }
            };
            Looper.loop();
        }
    }
}


 本人微博:honey_11

 Demo下载
最后,以上例子都来源与安卓无忧,请去应用宝或者豌豆荚下载:例子源码
,源码例子文档一网打尽。

 

作者:androidWuYou 发表于2016/9/24 10:40:29 原文链接
阅读:227 评论:0 查看评论

微信应用号开发知识贮备之Webpack实战

$
0
0

这里写图片描述

天地会珠海分舵注:随着微信应用号的呼之欲出,相信新一轮的APP变革即将发生。作为行业内人士,我们很应该去拥抱这个趋势。这段时间在忙完工作之余准备储备一下这方面的知识点,以免将来被微信应用号的浪潮所淹没

通过上一篇《微信应用号开发知识贮备之altjs官方实例初探》,我们已经将altjs的官方实例所用到的依赖包升到最新,且修改的源码相应的部分来适应最新的依赖。

今天本人的目标是将实例中的打包工具从browserify切换到当前更火的更接近nodejs编写习惯的weback上来。

既然要用wepack,那么当然就需要去学习一下weback相关的基本知识了。因为altjs的官方实例不复杂,所以有针对性的学习需要用到的webpack功能,够用就好。至于今后需要扩展的,另外开篇再说。

1. weback 配置文件简介

根据webpack官网的说法,Webpack 是当下最热门的前端资源模块化管理和打包工具。

它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。

通过 loader 的转换,任何形式的资源都可以视作模块,比如 ES6 模块、CSS、图片、 JSON等。

其实无论是哪种打包工具,最终的目的就是将所有依赖打包到相应的一个或者多个独立的文件上来。比如以我们官方的altjs-tutorial项目为例,我么可以看到顶层的入口index.html代码非常简单:

<!doctype html>
<html>
  <head>
  </head>
  <body>
    <div id="ReactApp"></div>
  </body>
  <script src="build/app.js"></script>
</html>

最终使用到的脚本其实就是build/app.js这个bundle文件,而这个文件就是通过package.json中指定的browserify命令打包而成的:

  "scripts": {
    "build": "browserify -t [reactify --es6] src/App.jsx > build/app.js",
    "start": "npm run build && open 'index.html' "
  },

其实webpack也一样,最终的目的也是通过相应的规则和策略生成相应的bundle文件。

1.1. 配置文件基本项简介

webpack的使用主要就是围绕着对应的配置文件webpack.config.js来进行的,这就是我们上面所说的规则和策略。我们可以通过这个配置文件来对如何将项目进行打包进行配置,最基本的配置相信有以下方面:

  • 依赖遍历入口。webpack打包的最终目的就是找出所有相关的依赖,然后将需要的依赖打包成独立的文件,所以必然需要找到遍历的入口文件。
  • 输出bundle。将所有依赖打包好后,我们应该将其放到一个固定的地方,以便我们的顶层入口程序(index.html)可以引用到。
  • 依赖文件类型解析和转换。因为不同的文件可能需要不同的规则来进行解析,比如配合reactjs的组件文件格式jsx,和javascript的语法规则就不一样,所以需要用到其它解析器或者转换器来将其转成javascript格式,以便打包到同一个bundle里面。这里我们统一将这些解析器和转换器称作加载器。

下面就是摘录自webpack官网的一个典型的webpack配置文件,其目的就是通过bable-loader这个加载器来将将项目中从src/app.js遍历得出的js后缀的依赖文件,打包成./bin/app.bundle.js文件。

 module.exports = {
     entry: './src/app.js',
     output: {
         path: './bin',
         filename: 'app.bundle.js',
     },
     module: {
         loaders: [{
             test: /\.js$/,
             exclude: /node_modules/,
             loader: 'babel-loader'
         }]
     }
 }

这里的基本项意义就是:

  • entry:依赖遍历入口文件
  • output:bundle打包结果,其中path定义了输出的文件夹,filename则定义了打包结果文件的名称
    module:定义了对依赖模块的处理逻辑,这里可以用loaders定义了一系列的加载器,以及一些正则。当需要加载的文件匹配test的正则时,就会调用后面的loader对文件进行处理,这正是webpack强大的原因。比如这里定义了凡是.js结尾的文件都是用babel-loader做处理。当然这些loader也需要通过npm install安装

1.2 插件

除了以上的基本项外,webpack.config.js还支持插件plugins来完成一些loader完成不了的功能。比如,配置OpenBrowserPlugin插件可以在构建完成之后自动在浏览器中打开”localhost:8080”,这样就不需要我们每次构建完后都要手动在浏览器中打开该地址来进行调试(请参考下面的构建和调试章节)。


var OpenBrowserPlugin = require('open-browser-webpack-plugin');

 module.exports = {
     entry: './src/app.js',
     output: {
         path: './bin',
         filename: 'app.bundle.js',
     },
     module: {
         loaders: [{
             test: /\.js$/,
             exclude: /node_modules/,
             loader: 'babel-loader'
         }]
     },
     plugins: [
        new OpenBrowserPlugin({ url: 'http://localhost:8080' })
    ]
 }

2. Babel加载器如何支持es6和jsx

Babel是个比较大的课题,总的来说Babel是一个转换编译器,有了它我们可以轻松使用上es6的新特性,而不需要等到浏览器支持上。同时Babel也可以支持上reactjs的jsx格式文件的解析转换。

为了支持上这些特性,我们的webpack.config.js的加载器应该写成以下的模式:

    module: {
        loaders: [{
            test: /\.(js|jsx)$/,
            loader: 'babel',
            query:
            {
                presets:['es2015',  'react']
            }
        }]
    },

加载器的其它配置项我们都有提及,这里额外的query存在的意义就是:

  • query: 为加载器提供额外的配置选项

我们这里的配置选项presets就是为了支持上:

  • ‘es2015’: 为了支持es6新特性,暂时我们没有用到,但是在往下的学习过程中,我打算引入一些es6的特性,所以这里一并配置上。
  • ‘react’: 为了支持上react的jsx。

3. 实战alt-tutorial的webpack配置文件编写

有了以上的知识点作为铺垫,那么我们的alt-tutorial项目的webpack.config.js配置文件也就跃然纸上了:

var path = require('path');
var OpenBrowserPlugin = require('open-browser-webpack-plugin');

var config = {
    entry: [
        path.resolve(__dirname, 'src/App.jsx'),
        ],
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: 'bundle.js'
    },
    module: {
        loaders: [{
            test: /\.(js|jsx)$/,
            loader: 'babel',
            query:
            {
                presets:[ 'react']
            }
        }]
    },

    plugins: [
        new OpenBrowserPlugin({ url: 'http://localhost:8080' })
    ]
};

module.exports = config;

4. 构建和调试

我们当前的alt-tutial项目的package.json中指定的构建工具用的还是browserify,所以我们需要将其改过来。

  "scripts": {
    "build": "browserify -t [reactify --es6] src/App.jsx > build/app.js",
    "start": "npm run build && open 'index.html' "
  },

webpack的build非常简单,只需要执行一个webpack命令就好了。

  "scripts": {
    "build": "webpack"
  },

但是构造好之后,我们还需要将相应的文件发布到一个http服务器上,然后再通过浏览器访问,这也甚是麻烦。

幸好,我们有webpack-dev-server这个命令工具,在开发的时候其实我们只需要像上面的构建build调用webpack命令一样,我们可以在安装了webpack-dev-server的模块后,直接调用该命令来进行打包的同时,它还会以当前目录作为根目录启动一个基于express的http服务器,默认的监听端口就是8080。这样配合上面webpack.config.js的OpenBrowserPlugin插件,构建后的运行调试就方便多了。

最终我们的package.json的scripts改成如下:

  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server "
  },

当然,webpack-dev-server还支持很多其它的高级参数选项,但是作为初学者,本人就先不深究了。

5. 依赖包更新和运行

为了能让构建脚本跑起来,我们需要把我们的webpack工具,babel加载器,插件等其它的依赖包给安装上。这里本人就直接给出packge.json的配置,大家直接npm install就好了。

{
  "name": "alt-tutorial",
  "version": "1.0.0",
  "description": "A simple flux tutorial built with alt and react",
  "main": "App.jsx",
  "dependencies": {
    "alt": "^0.18.6",
    "alt-container": "^1.0.2",
    "alt-utils": "^1.0.0",
    "babel-core": "^6.14.0",
    "babel-loader": "^6.2.5",
    "babel-preset-es2015": "^6.14.0",
    "babel-preset-react": "^6.11.1",
    "open-browser-webpack-plugin": "0.0.2",
    "react": "^15.3.2",
    "react-addons-test-utils": "^15.3.2",
    "react-dom": "^15.3.2"

  },
  "devDependencies": {
    "webpack": "^1.13.2",
    "webpack-dev-server": "^1.16.1"
  },
  "scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server "
  },
  "author": "Josh Perez <josh@goatslacker.com>",
  "license": "MIT"
}

最后执行以下命令就可以进行构建并在后台打开webpack-dev-server的同时在浏览器自动打开http://localhost:8080进行访问

npm run dev

6. 源码获取

和上一篇的源码一样,本篇的改动都checkin到了github上面,感兴趣的可以通过以下github链接进行获取:https://github.com/kzlathander/alt-tutorial-webpack.git

clone下来后进入项目切换到02这个分支就可以进行构建:

git clone https://github.com/kzlathander/alt-tutorial-webpack.git
cd alt-tutorial-webpack
git checkout 02
npm install 
npm run dev

最终运行结果如下:
运行结果

7. 下一篇文章计划

现在我们的alt-tutorial项目使用到的模块是最新的,构建的工具也已经使用上最火热的webpack,所以下一步我是准备走一遍这个项目的运行流程,通过代码阅读来学习一下reactjs和alt的相关知识点,同时也为往后的代码改造以及加入新功能做准备。至于这个过程需要一篇还是多篇文章才能cover完,现在就不得而知了,我们到时再看吧。敬请大家期待,也期待大牛的点评指点。

《未完待续》

作者:zhubaitian 发表于2016/9/24 10:49:58 原文链接
阅读:281 评论:0 查看评论

RxJava操作符(7)-条件

$
0
0

All

All操作符对Observable发送的所有数据根据某个条件进行判断,当其发射出去的数据都满足该条件时,则返回true,否则返回false。

原理图如下:


All操作符使用如下:

    @Override
    protected void createObservable() {
        super.createObservable();
        mObservable = Observable.just(1, 2, 3, 4).all(new Func1<Integer, Boolean>() {
            @Override
            public Boolean call(Integer integer) {
                if (integer % 2 == 0) {
                    return true;
                } else {
                    return false;
                }
            }
        });
    }


运行代码,结果如下:

Amb

Amb操作符是对2到9个Observable进行处理,这些Observable会形成一种竞争关系,当哪个Observable最先发射出数据,则amb进行发射这个Observable里的数据,而其它的Observable将被丢弃。

原理图如下:


Amb操作符使用如下:

    @Override
    protected void createObservable() {
        super.createObservable();
        mObservable = Observable.amb(createDelayObservable(4),
                createDelayObservable(3),
                createDelayObservable(2),
                createDelayObservable(1))
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread());
    }

    private Observable createDelayObservable(int index) {
        return Observable.just(1 * index, 2 * index, 3 * index).delay(index, TimeUnit.SECONDS);
    }


运行代码,结果如下:

Contains

Contains操作符判断Observable发射出去的数据是否包含某个数据,如果包含则返回true,如果Observable数据发射完了还没找到该数据,则返回false。

原理图如下:


Contains操作符使用如下:

    @Override
    protected void createObservable() {
        super.createObservable();
        mObservable = Observable.just(1, 2, 3, 4).contains(3);
    }


运行代码,结果如下:

IsEmpty

IsEmpty操作符会判断Observable是否有发射过数据,如果有,则返回false;如果没有,则返回true。

原理图如下:


IsEmpty操作符使用如下:

    @Override
    protected void createObservable() {
        super.createObservable();
        mObservable = createObservable(false).isEmpty();
    }

    private Observable createObservable(final boolean isNext) {
        return Observable.create(new Observable.OnSubscribe<Integer>() {
            @Override
            public void call(Subscriber<? super Integer> subscriber) {
                if (isNext) {
                    subscriber.onNext(1);
                } else {
                    subscriber.onCompleted();
                }
            }
        });
    }


运行代码,结果如下:

DefaultIfEmpty

DefaultIfEmpty操作符会判断Observable是否有发射过数据,如果有,则返回发射出去的数据;如果没有,则发射自己定义好的数据。

原理图如下:


DefaultIfEmpty操作符使用如下:

    @Override
    protected void createObservable() {
        super.createObservable();
        mObservable = createObservable(false).defaultIfEmpty(4);
    }

    private Observable createObservable(final boolean isNext) {
        return Observable.create(new Observable.OnSubscribe<Integer>() {
            @Override
            public void call(Subscriber<? super Integer> subscriber) {
                if (isNext) {
                    subscriber.onNext(1);
                } else {
                    subscriber.onCompleted();
                }
            }
        });
    }


运行代码,结果如下:

SequenceEqual

SequenceEqual操作符比较两个Observable是否相同,如果相同则返回true;如果不相同则返回false。

原理图如下:


SequenceEqual操作符使用如下:

    @Override
    protected void createObservable() {
        super.createObservable();
        mObservable = Observable.sequenceEqual(Observable.just(1, 2, 3),
                Observable.just(1, 2, 3));
    }


运行代码,结果如下:

SkipUntil

SkipUtil操作符是根据一个目标Observable为基准,当目标Observable没发射出去数据的时,原Observable发射出去的数据将会被忽略,当目标Observable发射数据时,则原Observable才开始发射数据。

原理图如下:


SkipUntil操作符使用如下:

    @Override
    protected void createObservable() {
        super.createObservable();
        mObservable = Observable.interval(1, TimeUnit.SECONDS)
                .skipUntil(Observable.timer(5, TimeUnit.SECONDS))
                .observeOn(AndroidSchedulers.mainThread());
    }


运行代码,结果如下:

SkipWhile

SkipWhile操作符会根据定义的函数逻辑来判断是否跳过数据,当函数返回true时,则跳过数据,返回数据为false时,则发射数据。

原理图如下:


SkipWhile操作符使用如下:

    @Override
    protected void createObservable() {
        super.createObservable();
        mObservable = Observable.just(1, 2, 3, 4, 5).skipWhile(new Func1<Integer, Boolean>() {
            @Override
            public Boolean call(Integer integer) {
                return integer != 3;
            }
        });
    }


运行代码,结果如下:

TakeUntil

TakeUtil操作符是根据一个目标Observable为基准,当目标Observable没发射出去数据的时,原Observable发射出去的数据将会发射,当目标Observable发射数据时,则原Observable的数据将被丢弃

原理图如下:


TakeUntil操作符使用如下:

    @Override
    protected void createObservable() {
        super.createObservable();
        mObservable = Observable.interval(1, TimeUnit.SECONDS)
                .takeUntil(Observable.timer(5, TimeUnit.SECONDS))
                .observeOn(AndroidSchedulers.mainThread());
    }


运行代码,结果如下:

TakeWhile

TakeWhile操作符会根据定义的函数逻辑来判断是否跳过数据,当函数返回true时,则发射数据,返回数据为false时,则丢弃数据。

原理图如下:


TakeWhile操作符使用如下:

    @Override
    protected void createObservable() {
        super.createObservable();
        mObservable = Observable.just(1, 2, 3, 4, 5).takeWhile(new Func1<Integer, Boolean>() {
            @Override
            public Boolean call(Integer integer) {
                return integer != 3;
            }
        });
    }


运行代码,结果如下:



作者:DylanZhuang 发表于2016/9/24 11:23:29 原文链接
阅读:248 评论:0 查看评论

OC-手势&变形&坐标系bounds、frame、center

$
0
0

1. 手势

将用户物理性的触屏操作变成对象存储起来,所有手势的父类 UIGestureRecognizer
系统将一些有特点的触屏操作封装成不同的手势类型包括以下几种:

  • UITapGestureRecognizer 点击
  • UISwipeGestureRecognizer 轻扫
  • UILongPressGestureRecognizer 长按
  • UIPinchGestureRecognizer 捏合
  • UIPanGestureRecognizer 拖拽
  • UIRotationGestureRecognizer 旋转

如何使用手势?

  • step1:创建指定手势的实例,在创建时设定当该手势发生时,系统会自动发什么消息
  • step2.设置手势的核心属性
  • step3.将手势添加到某个视图中,当用户在该视图上做了相应的动作,就会触发手势,系统会捕获并调用手势的事件方法

a.Taps手势

  • 核心属性
    • numberOfTapsRequired
    • numberOfTouchesRequired
- (void)viewDidLoad {
    [super viewDidLoad];
    //1.创建手势对象
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
    //2.设置手势的核心属性
    tap.numberOfTapsRequired = 5; //点几次
    tap.numberOfTouchesRequired = 1; //几个触摸点
    //3.将手势添加到某个视图中,当用户在该视图上做了相应的动作,就会触发手势,系统会捕获并调用手势的事件方法
    [self.myView addGestureRecognizer:tap];
}

-(void)tap:(UITapGestureRecognizer*)gr {

    NSLog(@"点击手势触发,点中的位置是 %@",  NSStringFromCGPoint([gr locationInView:self.myView]));
}

b.Swipe 手势 (轻扫)

  • 核心属性
    • direction 方向
- (void)viewDidLoad {
    [super viewDidLoad];
    UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];

    //上下左右 只 触发 左右
    swipe.direction = UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight | UISwipeGestureRecognizerDirectionUp | UISwipeGestureRecognizerDirectionDown;

    [self.view addGestureRecognizer:swipe];
}


-(void)swipe:(UISwipeGestureRecognizer*)gr {
    NSLog(@"清扫方向 %lu", gr.direction);
    //结束父视图编辑  收键盘
    [self.view endEditing:YES];
}

上面两个手势 都是 一次性手势,即手势发生过程中,响应方法只执行依次

c. UILongPress 手势 长按

  • 核心属性
    • minimumPressDuration 长按所需最小时间
- (void)viewDidLoad {
    [super viewDidLoad];
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];
    //设置长按触发最小时间
    longPress.minimumPressDuration = 2;
    [self.view addGestureRecognizer:longPress];
}
-(void)longPress:(UILongPressGestureRecognizer*)gr {
    NSLog(@"%@",  NSStringFromCGPoint([gr locationInView:self.view]));
    if (gr.state == UIGestureRecognizerStateBegan) {
            NSLog(@"开始长按");
    }else if(gr.state == UIGestureRecognizerStateChanged) {
            NSLog(@"移动");
    }else if(gr.state == UIGestureRecognizerStateEnded) {
            NSLog(@"长按手势结束");
    }
}

d. UIPan 手势 拖拽

三种拖拽方式

- (void)viewDidLoad {
    [super viewDidLoad];
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
    [self.view addGestureRecognizer:pan];
}

-(void)pan:(UIPanGestureRecognizer*)gr {
    NSLog(@"1--%@",NSStringFromCGPoint([gr locationInView:self.view]));
    NSLog(@"2--%@",NSStringFromCGPoint([gr translationInView:self.view]));
    //方式一
    if (gr.state == UIGestureRecognizerStateBegan) {
        self.startPos = self.imageView.center;
    }else {
        CGPoint translation = [gr translationInView:self.view];
        CGPoint center = self.imageView.center;
        center.x = self.startPos.x + translation.x;
        center.y = self.startPos.y + translation.y;
        self.imageView.center = center;
    }

    //方式二
//    CGPoint translation = [gr translationInView:self.view];
//    CGPoint center = self.imageView.center;
//    center.x += translation.x;
//    center.y += translation.y;
//    self.imageView.center = center;
//    [gr setTranslation:CGPointZero inView:self.view];

    //方式三
//    if (gr.state == UIGestureRecognizerStateBegan) {
//        //手势开始时 记录 起始位置
//        self.startPos = [gr locationInView:self.view];
//    }else if (gr.state == UIGestureRecognizerStateChanged){
//        //获取本次 移动的位置
//        CGPoint move = [gr locationInView:self.view];
//        //图片的位置的等于当前位置 加上 移动位置-上次位置 的 偏移
//        CGPoint center = self.imageView.center;
//        center.x += move.x - self.startPos.x;
//        center.y += move.y - self.startPos.y;
//        self.imageView.center = center;
//        //设置本次位置  为下次的 起始位置
//        self.startPos = move;
//    }
}

e. UIPich 手势 捏合

- (void)viewDidLoad {
    [super viewDidLoad];
    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
    [self.view addGestureRecognizer:pinch];
}

-(void)pinch:(UIPinchGestureRecognizer*)gr {
    NSLog(@"速率 %.2f",gr.velocity);
    NSLog(@"缩放比 %.2f",gr.scale);
}

f. UIRotation 手势 旋转

- (void)viewDidLoad {
    [super viewDidLoad];
    UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotation:)];
    [self.view addGestureRecognizer:rotation];
}

-(void)rotation:(UIRotationGestureRecognizer*)gr {
    NSLog(@"%.2f",gr.rotation);
}

2.变形 (transform)

  • 什么是变形
    • 视图发生了 位移, 缩放, 旋转这样的变化叫做变形
  • 如何实现视图的变形
    • 通过修改视图对象 transform属性就能完成变形
  • transform属性
    • 类型 CGAffineTransform (结构体类型)
  • 修改transform
    • translation 位移
    • scale 缩放
    • rotation 旋转
  • CGAffineTransformMakeTranslation
  • CGAffineTransformTranslate
  • CGAffineTransformMakeScale
  • CGAffineTransformScale
  • CGAffineTransformMakeRotation
  • CGAffineTransformRotate
  • 清除视图的所有变形
    • CGAffineTransformIdentity
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //位移变化
    //CGAffineTransformMakeTranslation 变形是基于变形前的那个基础状态
//    self.imageView.transform = CGAffineTransformMakeTranslation(50, 50);
    //变形是在 当前 变形基础上 继续变形
    self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, 50, 50);


    //缩放变形
//    self.imageView.transform = CGAffineTransformMakeScale(1.1, 1.5);
    self.imageView.transform = CGAffineTransformScale(self.imageView.transform, 1.02, 1.02);

    //旋转变化
//    self.imageView.transform = CGAffineTransformMakeRotation(M_PI_4);
    self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, M_PI_4);


    NSLog(@"transform %@",NSStringFromCGAffineTransform(self.imageView.transform));
}
- (IBAction)resetTranform:(id)sender {
//    CGAffineTransformIdentity 是个常量, 用该常量给 transform属性赋值 会 清空 transform 的所有变形
    self.imageView.transform = CGAffineTransformIdentity;
    NSLog(@"transform %@",NSStringFromCGAffineTransform(self.imageView.transform));
}

手势加变形综合应用

多手势同时出发必须使用 UIGestureRecognizerDelegate 代理

//手势代理方法  返回YES是可以同时触发
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //拖拽
    UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGR:)];
    self.imageView.userInteractionEnabled = YES;//imageView默认关闭用户交互
    [self.view addGestureRecognizer:panGR];
    //捏合
    UIPinchGestureRecognizer *pinchGR = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinchGR:)];
    pinchGR.delegate = self;
    [self.view addGestureRecognizer:pinchGR];
    //旋转
    UIRotationGestureRecognizer *rotationGR = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotationGR:)];
    rotationGR.delegate = self;
    [self.view addGestureRecognizer:rotationGR];
    //点击
    UITapGestureRecognizer *tapGR = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapGR:)];
    [self.view addGestureRecognizer:tapGR];

}

-(void)tapGR:(UITapGestureRecognizer*)gr {
    self.imageView.transform = CGAffineTransformIdentity;
}

-(void)rotationGR:(UIRotationGestureRecognizer*)gr {
    CGFloat rotation = gr.rotation;
    self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, rotation);
    gr.rotation = 0;
}

-(void)pinchGR:(UIPinchGestureRecognizer*)gr {
    CGFloat scale = gr.scale;
    self.imageView.transform = CGAffineTransformScale(self.imageView.transform, scale, scale);
    gr.scale = 1;
}

-(void)panGR:(UIPanGestureRecognizer*)gr {
//    CGPoint translation = [gr translationInView:self.view];
//    CGPoint center = self.imageView.center;
//    center.x += translation.x;
//    center.y += translation.y;
//    self.imageView.center = center;
//    [gr setTranslation:CGPointZero inView:self.view];


    CGPoint translation = [gr translationInView:self.view];
    self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, translation.x, translation.y);
    [gr setTranslation:CGPointZero inView:self.view];
}

3.深入坐标系 (frame bounds center transform)

1.frame

  • 类型:CGRect类型
  • 作用:定位—视图的左顶点在父视图坐标系中的对应的点的坐标,以及视图在父视图中占据了多大的空间

2.bounds

  • 类型:CGRect类型
  • 作用:描述了视图自身的坐标系的顶点的值,以及视图自身的尺寸大小

3.center

  • 类型:CGPoint类型
  • 作用:描述了视图中心点在父视图坐标系下的位置

4.transform

  • 类型:CGAffineTransform 类型
  • 作用:描述视图的变形状态

  • 通过更改bounds实现scroll滑动效果

//手势的事件方法
- (IBAction)panMove:(UIPanGestureRecognizer*)sender {
    CGPoint move = [sender translationInView:self.view];
    CGRect bounds = self.myView.bounds;
    if (move.y < 0) { //向上拖拽
        //bounds 应该越来越大
        bounds.origin.y += -move.y;
    }else if (move.y > 0) { //向下拖拽
        //bounds 应该越来越小
        bounds.origin.y -= move.y;
    }
    self.myView.bounds = bounds;

    [sender setTranslation:CGPointZero inView:self.view];
}

———————————————————————————————

- frame bounds center transform
frame 圆点不变 宽高会变
bounds 圆点不变 宽高会变 不变 不变
center 不变 不变
transform 不变 不变 不变

———————————————————————————————

作者:shuan9999 发表于2016/9/24 11:46:20 原文链接
阅读:244 评论:0 查看评论

OC-UIImage动画&UIImageView动画&UIView动画NSTimer动画

$
0
0

1.UIKit 层面的动画

预备:动画 — 帧动画

1.1UIImage

1.2UIImageView

预备:动画 — 补间动画

1.3UIView

系统为UIView提供的专门用于控制视图实现动画的方法,这些方式以类方法出现的,方法名开头为animate….

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

//    [UIView animateWithDuration:2 animations:^{
//        //设置动画的最终状态
//        self.imageView.alpha = 1;
//        CGPoint center = self.imageView.center;
//        center.y += 300;
//        self.imageView.center = center;
//    }];

//    [UIView animateWithDuration:2 animations:^{
//        self.imageView.alpha = 1;
//    } completion:^(BOOL finished) {
//        //动画执行完 执行 该代码块
//        NSLog(@"动画执行完");
//    }];

    /*
     Duration 动画持续时间
     delay  等待时间
     options 动画选项  (动画匀速 变速 重复)
     animations 动画结束后什么样子 (最终状态)
     completion 动画结束后做什么
    */
     [UIView animateWithDuration:2 delay:3 options:UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse animations:^{
         CGPoint center = self.imageView.center;
         center.y += 300;
         self.imageView.center = center;
     } completion:nil];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //设置 动画的初始状态
//    self.imageView.alpha = 0;
}

转场动画:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.imageIndex++;
    if (self.imageIndex > 43) return;

    NSString *imageName = [NSString stringWithFormat:@"a%ld",self.imageIndex];

    //添加转场动画
    [UIView transitionWithView:self.imageView duration:1 options:UIViewAnimationOptionTransitionCurlUp animations:^{
        self.imageView.image = [UIImage imageNamed:imageName];
    } completion:nil];

}
- (void)viewDidLoad {
    [super viewDidLoad];
    //ship-anim 图片名称 但是索引0 不要
    //duration 动画持续时间
    UIImage *image = [UIImage animatedImageNamed:@"ship-anim" duration:1];
    self.imageView.image = image;


    [self runAnimationWithImageView:self.npcImageView imageFileName:@"yinJiaoDaWang" imageCount:32 speed:1/20.0];
//
    [self runAnimationWithImageView:self.npcImageView2 imageFileName:@"shaoNv3_" imageCount:40 speed:1/15.0];

  /******** UIImageView动画 *********************************/
    NSMutableArray *allImages = [NSMutableArray array];
    for (NSInteger i = 0; i < 32; i++) {
        //拼接图片的名称
        NSString *fileName = [NSString stringWithFormat:@"yinJiaoDaWang%02ld",i+1];
        //创建图片对象
        UIImage *image = [UIImage imageNamed:fileName];
        //将创建好的图片 添加到数组中
        [allImages addObject:image];
    }
    //设置 动画所需图片 (需要的是一个图片数组)
    self.npcImageView.animationImages = allImages;
    //设置动画的时长  (一次多长时间)
    self.npcImageView.animationDuration = 1 / 10.0 * 32;
    //设置动画运行次数   值为0 是无限运行
    self.npcImageView.animationRepeatCount = 0;
    //运行动画
    [self.npcImageView startAnimating];

    if (self.npcImageView.animationRepeatCount != 0) {
        //动画运行完 要释放动画数组
        //获取动画总时间
        CGFloat afterDelay = self.npcImageView.animationDuration * self.npcImageView.animationRepeatCount;
        //等待 afterDelay 时间后 向 self.npcImageView 发送setAnimationImages 消息 并把 nil 做为参数传给 setAnimationImages 方法
        [self.npcImageView performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:afterDelay];
    }
}

页面跳转动画:

MyViewController *myVC = [[MyViewController alloc]init];
    /*
    UIModalTransitionStyleCoverVertical
    UIModalTransitionStyleFlipHorizontal
    UIModalTransitionStyleCrossDissolve
    UIModalTransitionStylePartialCurl
     */
    //设置跳转动画的类型
    myVC.modalTransitionStyle = UIModalTransitionStylePartialCurl;

    [self presentViewController:myVC animated:YES completion:nil];

2.NSTimer动画

  • 创建定时器
    • [NSTimer schedulexxx];
    • [NSTimer timerWithxxx];
  • 销毁定时器
    • [timer invalidate];

动画主要修改frame bounds center alpha等属性

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(timer:) userInfo:nil repeats:YES];
}

- (void)viewDidLoad {
    [super viewDidLoad];
//    self.imageView.alpha = 0;
}
-(void)timer:(NSTimer *)sender {

    CGRect bounds = self.imageView.bounds;
    bounds.size.width +=5;
    bounds.size.height += 5;
    self.imageView.bounds = bounds;

//    CGRect frame = self.imageView.frame;
//    frame.size.width +=5;
//    frame.size.height += 5;
//    self.imageView.frame = frame;


//    CGPoint center = self.imageView.center;
//    center.y += 5;
//    self.imageView.center = center;

//    self.imageView.alpha += 0.1;
//    if (self.imageView.alpha >= 1) {
//        [sender invalidate]; //销毁定时器
//    }

    NSLog(@"-------");
}
作者:shuan9999 发表于2016/9/24 11:56:22 原文链接
阅读:241 评论:0 查看评论

java/android 设计模式学习笔记(21)---备忘录模式

$
0
0

  这篇博客我们来介绍一下备忘录模式(Memento Pattern),也是行为型模式设计模式之一,备忘录模式又称为快照(Snapshot Pattern)模式或者 Token 模式,该模式用于保存对象当前状态,并且在之后可以再次恢复到此状态。备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问(an opaque object),目的是为了保护被保存的这些对象状态的完整性以及内部实现不向外暴露。
  转载请注明出处:http://blog.csdn.net/self_study/article/details/52561728
  PS:对技术感兴趣的同鞋加群544645972一起交流。

设计模式总目录

  java/android 设计模式学习笔记目录

特点

  在不破坏封装的前提下,捕捉一个对象的内部状态,并在该对象之外保存这个状态,这样,以后就可将该对象恢复到原先保存的状态。
  备忘录模式使用的场景:

  • 需要保存一个对象在某一个时刻的状态或部分状态;
  • 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以简洁访问其内部状态。

UML类图

  来看看备忘录模式的 uml 类图:
  这里写图片描述
备忘录模式有三个角色:

  • Originator:负责创建一个备忘录,可以记录、恢复自身的内部状态,同时 Originator 还可以根据需要决定 Memento 存储自身的哪些内部状态。
  • Memento:备忘录角色,用于存储 Originator 的内部状态,并且可以防止 Originator 以外的对象访问 Memento。
  • CareTaker:负责存储备忘录,不能对备忘录的内容进行操作和访问,只能够将备忘录传递给其他对象。
  我们这里可以写出通用代码:
Originator.class

public class Originator {
    private int state = 0;

    public void setState(int state) {
        this.state = state;
    }

    public void print() {
        System.out.print("state = " + state + "\n");
    }

    public void restore(Memento memento) {
        setState(memento.getState());
    }

    public Memento createMemoto() {
        Memento memento = new Memento();
        memento.setState(state);
        return memento;
    }

    public static void main(String args[]) {
        Originator originator = new Originator();
        originator.setState(1);
        Caretaker caretaker = new Caretaker();
        caretaker.storeMemento(originator.createMemoto());
        System.out.print("before\n");
        originator.print();
        originator.setState(2);
        System.out.print("after\n");
        originator.print();
        originator.restore(caretaker.restoreMemento());
        System.out.print("restore to the original state\n");
        originator.print();
    }
}

Memento.class

public class Memento {
    private int mState = 0;

    public void setState(int state) {
        this.mState = state;
    }

    public int getState() {
        return mState;
    }
}

Caretaker.class

public class Caretaker {
    private Memento memento;

    public Memento restoreMemento() {
        return memento;
    }

    public void storeMemento(Memento memento) {
        this.memento = memento;
    }
}

最后输出:

before
state = 1
after
state = 2
restore to the original state
state = 1

  上面的实现方式中备忘录角色的内部所存储的状态对所有对象公开,因此很多时候这个被称为“白箱”备忘录模式,它将发起人角色的状态存储在一个大家都看得到的地方,因此是破坏封装性的,但是通过程序员自律,同样可以一定程度上实现模式的大部分用意,所以它仍然是有意义的;“黑箱”备忘录模式和白箱备忘录模式的不同之处是 Memento 设计成了 Originator 的内部类,从而将 Memento 的对象封装在 Originator 里面,在外面提供一个标识接口 MementoIF 给 Caretaker 以及其他对象,并且让 Memento 实现 MementoIF 接口,或者不用 MementoIF 接口,直接将 Memento 类设计成静态内部类也是可以的。
这里写图片描述

示例与源码

  在 Android 源码中,我们经常会用到的 onSaveInstanceState 和 onRestoreInstanceState 就是一个备忘录模式,在这个过程中,Activity 扮演了Caretaker 的角色,负责存储、恢复 UI 的状态信息;Activity、Fragment、View、ViewGroup 等对象为 Originator 角色,也就是需要存储状态的对象;Memento 则是由 Bundle 类扮演。Activity 在停止之前会根据 Activity 的退出情景来选择是否需要存储状态,在重新启动该 Activity 时会判断 ActivityClientRecord 对象中是否存储了 Activity 的状态,如果含有状态则调用 Activity 的 onRestoreInstanceState 函数,从而使得 Activity 的 UI 效果和上次保持一致,这样一来,就保证了在非正常情况退出 Activity 时不会丢失数据的情况,很好的提升了用户体验。
  我们这里以 wiki 上的”黑箱”备忘录模式为例,看一下和”白箱”备忘录模式的区别:
Originator.class

class Originator {
    private String state;
    // The class could also contain additional data that is not part of the
    // state saved in the memento..

    public void set(String state) {
        System.out.println("Originator: Setting state to " + state);
        this.state = state;
    }

    public Memento saveToMemento() {
        System.out.println("Originator: Saving to Memento.");
        return new Memento(this.state);
    }

    public void restoreFromMemento(Memento memento) {
        this.state = memento.getSavedState();
        System.out.println("Originator: State after restoring from Memento: " + state);
    }

    public static class Memento {
        private final String state;

        public Memento(String stateToSave) {
            state = stateToSave;
        }

        private String getSavedState() {
            return state;
        }
    }
}

Caretaker.class

public static void main(String[] args) {
        List<Originator.Memento> savedStates = new ArrayList<Originator.Memento>();

        Originator originator = new Originator();
        originator.set("State1");
        originator.set("State2");
        savedStates.add(originator.saveToMemento());
        originator.set("State3");
        // We can request multiple mementos, and choose which one to roll back to.
        savedStates.add(originator.saveToMemento());
        originator.set("State4");

        originator.restoreFromMemento(savedStates.get(1));   
    }

输出:

Originator: Setting state to State1
Originator: Setting state to State2
Originator: Saving to Memento.
Originator: Setting state to State3
Originator: Saving to Memento.
Originator: Setting state to State4
Originator: State after restoring from Memento: State3

将 Memento 类设置成为 Originator 的静态内部类之后,Memento 的内部实现细节就可以只对 Originator 暴露了,实现了”黑箱”的封装性,我们这里省掉了 Caretaker 和 MementoIF 这两个角色,根据需要也可以去实现这两个角色。

总结

  备忘录模式是在不破坏封装的条件下,通过备忘录对象(Memento)存储另一个对象内部状态的快照,在将来合适的时候把这个对象还原到存储起来的状态。

  • 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态;
  • 实现了信息的封装,维护内聚,使得用户不需要关心状态的保存细节。
  缺点:消耗资源,如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存,恢复状态的过程也可能会很耗时。

源码下载

  https://github.com/zhaozepeng/Design-Patterns/tree/master/MementoPattern

引用

https://en.wikipedia.org/wiki/Memento_pattern
http://www.cnblogs.com/java-my-life/archive/2012/06/06/2534942.html

作者:zhao_zepeng 发表于2016/9/24 13:07:08 原文链接
阅读:288 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live