Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

PE 101 Optimum polynomial(拉格朗日插值)

$
0
0

Optimum polynomial

Problem 101

If we are presented with the first k terms of a sequence it is impossible to say with certainty the value of the next term, as there are infinitely many polynomial functions that can model the sequence.

As an example, let us consider the sequence of cube numbers. This is defined by the generating function, 
un = n3: 1, 8, 27, 64, 125, 216, ...

Suppose we were only given the first two terms of this sequence. Working on the principle that "simple is best" we should assume a linear relationship and predict the next term to be 15 (common difference 7). Even if we were presented with the first three terms, by the same principle of simplicity, a quadratic relationship should be assumed.

We shall define OP(kn) to be the nth term of the optimum polynomial generating function for the first k terms of a sequence. It should be clear that OP(kn) will accurately generate the terms of the sequence for n ≤ k, and potentially the first incorrect term (FIT) will be OP(k,k+1); in which case we shall call it a bad OP (BOP).

As a basis, if we were only given the first term of sequence, it would be most sensible to assume constancy; that is, for n ≥ 2, OP(1, n) =u1.

Hence we obtain the following OPs for the cubic sequence:

OP(1, n) = 1 1, 1, 1, 1, ...
OP(2, n) = 7n−6 1, 8, 15, ...
OP(3, n) = 6n2−11n+6      1, 8, 27, 58, ...
OP(4, n) = n3 1, 8, 27, 64, 125, ...

Clearly no BOPs exist for k ≥ 4.

By considering the sum of FITs generated by the BOPs (indicated in red above), we obtain 1 + 15 + 58 = 74.

Consider the following tenth degree polynomial generating function:

un = 1 − n + n2 − n3 + n4 − n5 + n6 − n7 + n8 − n9 + n10

Find the sum of FITs for the BOPs.



题意:

最优多项式

如果我们知道了一个数列的前k项,我们仍无法确定地给出下一项的值,因为有无穷个多项式生成函数都有可能是这个数列的模型。

例如,让我们考虑立方数的序列,它可以用如下函数生成,
un = n3: 1, 8, 27, 64, 125, 216, …

如果我们只知道数列的前两项,秉承“简单至上”的原则,我们应当假定这个数列遵循线性关系,并且预测下一项为15(公差为7)。即使我们知道了数列的前三项,根据同样的原则,我们也应当首先假定数列遵循二次函数关系。

给定数列的前k项,定义OP(k, n)是由最优多项式生成函数给出的第n项的值。显然OP(k, n)可以精确地给出n ≤ k的那些项,而可能的第一个不正确项(First Incorrect Term,简记为FIT)将会是OP(k, k+1);如果事实的确如此,我们称这个多项式为坏最优多项式(Bad OP,简记为BOP)。

在最基本的情况下,如果我们只得到了数列的第一项,我们应当假定数列为常数,也就是说,对于n ≥ 2,OP(1, n) = u1

由此,我们得到了立方数列的最优多项式如下:



OP(1, n) = 1 1, 1, 1, 1, …
OP(2, n) = 7n−6 1, 8, 15, …
OP(3, n) = 6n2−11n+6 1, 8, 27, 58, …
OP(4, n) = n3 1, 8, 27, 64, 125, …

显然,当k ≥ 4时不存在坏最优多项式。

所有坏最优多项式的第一个不正确项(用红色标示的数)之和为1 + 15 + 58 = 74。

考虑下面这个十阶多项式生成函数:

un = 1 − n + n2 − n3 + n4 − n5 + n6 − n7 + n8 − n9 + n10

求其所有坏最优多项式的第一个不正确项之和。


题解:

因为我们知道了一个数列的前k项,但我们仍无法确定地给出下一项的值,因为有无穷个多项式生成函数都有可能是这个数列的模型。从这里,我们可以想到拉格朗日插值。什么是拉格朗日插值?我简单得解释一下。

我们都做过数学的规律题,就是给你一个数列,然后让你猜下一个数是什么。这题也是这样。

先写一下拉格朗日插值的定义:

对某个多项式函数,已知有给定的k + 1个取值点:

(x_{0},y_{0}),\ldots ,(x_{k},y_{k})

其中x_{j}对应着自变量的位置,而y_{j}对应着函数在这个位置的取值。

假设任意两个不同的xj都互不相同,那么应用拉格朗日插值公式所得到的拉格朗日插值多项式为:

L(x):=\sum _{{j=0}}^{{k}}y_{j}\ell _{j}(x)

其中每个\ell _{j}(x)拉格朗日基本多项式(或称插值基函数),其表达式为:

\ell _{j}(x):=\prod _{{i=0,\,i\neq j}}^{{k}}{\frac  {x-x_{i}}{x_{j}-x_{i}}}={\frac  {(x-x_{0})}{(x_{j}-x_{0})}}\cdots {\frac  {(x-x_{{j-1}})}{(x_{j}-x_{{j-1}})}}{\frac  {(x-x_{{j+1}})}{(x_{j}-x_{{j+1}})}}\cdots {\frac  {(x-x_{{k}})}{(x_{j}-x_{{k}})}}.

拉格朗日基本多项式\ell _{j}(x)的特点是在x_{j}上取值为1,在其它的点x_{i},\,i\neq j上取值为0

所谓的插值,就是“插”“值”,就是指找出一个通过给出离散数据点的函数。例如,数列中给出数据可以表示为在坐标系上的点,x坐标就是第几项,y坐标就是该项的值。然后画个图,找出这些数在哪个函数数,求出这个函数,这样就可以求出下一项了。先试一个简单的数列:1、8、27…那下一个是什么呢?

我们知道:

 

 

 , 

那么:

那么下一项就是将x=4代入上面的二元一次方程,就是58啦。

如果你发现这个答案其实是错的!正确的是y=x^3? 其实答案是64。那就是拉格朗日插值是错的咯?

再仔细看一下坑爹的高数课本,才发现原来是我们一直搞错了。如果我们给的是n个点,那么拉格朗日给出的函数将会是(n-1)次的。

也就是拉格朗日插值:n次多项式可以用n+1个点唯一确定.

这不坑爹吗…用公式之前还得想清楚这个函数是几次的,而且如果是更高次数的还没办法加上点去求。

这就意味着,就算是1、2、3、4、5、6…这样的数列,拉格朗日插值法在耗尽你大量的考试时间去求出通项公式以后,还会给出一个超级坑爹的答案!

那么这个方法还有什么用!
别急,前面的计算都是为后面做铺垫的。现在才是重点。


无论是分布得多么奇怪的点,拉格朗日插值法总能给出一条经过这些点的函数图象。也就是说,就算是1、2、3、4、5、6、1568这样明显不靠谱的答案也是“有规律的”。因为你总可以设一个六次多项式,找出这个数列的通项公式。
所以说:
1、3、5、7、9、1598,是对的
3、1、4、1、5、9、2、999,是对的
1,1,2,3,5,7,8989,是对的
2,4,6,8,5,是对的

你想怎么写都是对的,即使看起来不靠谱,但其仍然存在内在关系与规律。

解释完啦!

插值公式是:

拉格朗日插值法可以帮助我们解决以下的问题:
例如:已知x取值0,1,-1,2时, f{x}取值2,2,0,6 。
求x=3时f{x}的值。

#include<cstdio>
#include<iostream>
using namespace std;
int xs[4]={0,1,-1,2};
int ys[4]={2,2,0,6};
//f(3)?
      
int lagrange_Polynomial(int x, int xs[],int ys[]) 
{
    int c1,c2; 
    int n=4;
    c1=0; 
    for(int i=0;i<n;i++)
	{ 
        c2=ys[i]; 
        for(int j=0;j<n;j++)
        {
            if(j!=i) c2=c2*((x-xs[j])*1.0)/((xs[i]-xs[j])*1.0); 
        }
    	c1+=c2; 
    } 
    return c1;
}
int main()
{
	int val=lagrange_Polynomial(3,xs,ys);
	cout<<val<<endl; //输出 :20 
	return 0;
}

同理,PE101这题就可以按相同的方法解出来啦。


如果你做出了PE101,你可以做另外一题:Codeforces 622F

作者:liangzhaoyang1 发表于2016/12/11 19:45:37 原文链接
阅读:45 评论:0 查看评论

Android开发艺术探索

$
0
0

    ScaleDrawable对应于xml文件中的<scale>标签,可以根据自己的level将指定的drawable缩放到一定比例。基本语法如下:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <scale xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:drawable="@color/blue"
  4. android:level="1"
  5. android:scaleGravity="center"
  6. android:scaleHeight="20%"
  7. android:scaleWidth="20%" />
  • android:scaleGravity属性相当于gravity属性,缩放之后的效果。
  • android:scaleHeight/scaleWidth 表示Drawable的缩放比例,20%就是显示的时候还剩80%
需要注意的是: android:level不能为0,否则没有效果,来看下ScaleDrawable源码的draw方法:
  1. @Override
  2. public void draw(Canvas canvas) {
  3. final Drawable d = getDrawable();
  4. if (d != null && d.getLevel() != 0) {
  5. d.draw(canvas);
  6. }
  7. }
分析获取属性值的方法,可以看出mDrawable的大小和等级以及缩放关系。
  1. @Override
  2. protected void onBoundsChange(Rect bounds) {
  3. final Drawable d = getDrawable();
  4. final Rect r = mTmpRect;
  5. final boolean min = mState.mUseIntrinsicSizeAsMin;
  6. final int level = getLevel();
  7. int w = bounds.width();
  8. if (mState.mScaleWidth > 0) {
  9. final int iw = min ? d.getIntrinsicWidth() : 0;
  10.            //这里的iw一般都为0 ,可以得到:ScaleDrawable级别最大值10000,level越高,看起来越大,定义的缩放比例越大,看起来越小。
  11. w -= (int) ((w - iw) * (10000 - level) * mState.mScaleWidth / 10000);//iw一般都为0
  12. }
  13. int h = bounds.height();
  14. if (mState.mScaleHeight > 0) {
  15. final int ih = min ? d.getIntrinsicHeight() : 0;
  16. h -= (int) ((h - ih) * (10000 - level) * mState.mScaleHeight / 10000);
  17. }
  18. final int layoutDirection = getLayoutDirection();
  19. Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection);
  20. if (w > 0 && h > 0) {
  21. d.setBounds(r.left, r.top, r.right, r.bottom);
  22. }
  23. }
示例将一个Drawable缩放到30%:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <scale xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:drawable="@mipmap/ic_launcher"
  4. android:scaleGravity="center"
  5. android:scaleHeight="70%"
  6. android:scaleWidth="70%" />
  1. View scale = findViewById(R.id.scaleDrawable);
  2. ScaleDrawable scaleDrawable = (ScaleDrawable) scale.getBackground();
  3. scaleDrawable.setLevel(1);


作者:player_android 发表于2016/12/11 19:45:53 原文链接
阅读:34 评论:0 查看评论

spring mvc使用Annotation验证对表单数据验证

$
0
0

简介说明

使用Spring MVC的Annotation验证可以直接对view model的简单数据验证,比如格式、长度等,如果model的数据验证需要有一些比较复杂的业务逻辑性在里头。

以下是使用Spring MVC自带的annotation验证,加上自定义的一个@Tel的annotation验证例子,此例子具有:

1、支持多语言(国际化)
2、对默认数据先进行转化,比如int、date类型如果传入空值时,会抛异常,默认给定值

案例代码

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    <display-name>Test Spring MVC - 1</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </context-param>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:mvc="http://www.springframework.org/schema/mvc"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

    <!--注解说明 -->
    <context:annotation-config />

    <!-- 默认的注解映射的支持 -->
    <mvc:annotation-driven validator="validator" conversion-service="conversionService" />

    <!-- 把标记了@Controller注解的类转换为bean -->
    <context:component-scan base-package="com.my" />

    <!-- 视图解释类 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/"/>
        <property name="suffix" value=".jsp"/><!--可为空,方便实现自已的依据扩展名来选择视图解释类的逻辑  -->
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
    </bean>

    <!-- 资源文件:messages.properties -->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>messages</value>
            </list>
        </property>
    </bean>
    <!-- 验证器 -->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="validationMessageSource" ref="messageSource"/>
    </bean>

    <!-- 自定义数据类型转换器 -->
    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <list>
                <bean class="com.my.controller.converter.IntConverter" />
                <bean class="com.my.controller.converter.DateConverter" />
            </list>
        </property>
    </bean>

</beans>

1)在<mvc:annotation-driven/>中加入conversionService
2) 在conversionService中加入转换器,如上有IntConverter和DateConverter,可以是自定义/系统的类型,这是全局的。
3)在validator验证器中加入了支持多语言的properties – 支持国际化

Controller

package com.my.controller;

import java.util.List;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

import com.my.controller.bean.User4;

@Controller
@RequestMapping(value="av")
public class TestAnnotationValidController {

    @RequestMapping
    public ModelAndView index() {
        ModelAndView view = new ModelAndView("/TestAnnotationValid/index", "user4", new User4());
        return view;
    }

    @RequestMapping(value="/add", method=RequestMethod.POST)
    public ModelAndView add(@ModelAttribute @Valid User4 user, BindingResult result) {
        ModelAndView view = new ModelAndView("/TestAnnotationValid/index");
        view.addObject("user4", user);

        if(result.hasErrors()) {
            List<FieldError> errors = result.getFieldErrors();
            for(FieldError err : errors) {
                System.out.println("ObjectName:" + err.getObjectName() + "\tFieldName:" + err.getField()
                        + "\tFieldValue:" + err.getRejectedValue() + "\tMessage:" + err.getDefaultMessage() + "\tCode:");
            }
        }

        return view;
    }

}

在add中,有一个@Valid的annotation,这是必需的,不加这个,annotation验证将不起作用

User4.java model实体类

package com.my.controller.bean;

import java.util.Date;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;

public class User4 {

    private long id;

    @NotBlank(message="{valid.name}")
    private String name;

    @Length(min=4, max=20, message="{valid.password}")
    private String password;

    @NotBlank(message="{valid.required}")
    @Email(message="{valid.email}")
    private String email;

    @NotNull(message="{valid.required}")
    private boolean married;

    @Min(value=18, message="{valid.ageMin}")
    @Max(value=100, message="{valid.ageMax}")
    private int age;

    @NotNull(message="{valid.required}")
    @Past(message="{valid.birthday}")
    private Date birthday;

    @Pattern(regexp="^[a-zA-Z]{2,}$", message="{valid.address}")
    private String address;

    @Size(min=1, message="{valid.likesMin}")
    private String[] likes;

    @com.my.controller.validator.Tel(message="{valid.tel}", min=3)
    private String tel;

    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public boolean isMarried() {
        return married;
    }
    public void setMarried(boolean married) {
        this.married = married;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    public String[] getLikes() {
        return likes;
    }
    public void setLikes(String[] likes) {
        this.likes = likes;
    }
    public String getTel() {
        return tel;
    }
    public void setTel(String tel) {
        this.tel = tel;
    }

}

除了@Tel之外,其它都是spring自带的annotation,这些注解中包含规则以及验证失败的返回信息。

message.properties

valid.required=字段值不能为空
valid.name=用户名不能为空
valid.password=密码最小4位
valid.ageMin=年龄不能小于{1}岁
valid.ageMax=年龄不能大于{1}岁
valid.email=邮箱格式不正确
valid.address=联系地址不正确
valid.birthday=生日不能大于今天
valid.likesMin=喜好最小不能小于1个
valid.tel=手机号码不能小于{min}位

对应的是User4 model的annotation的message值。如果需要国际化的多语言,只需要加入多一个messages_en_US.properties这样名字的文件即可。

自定义注解@Tel

package com.my.controller.validator;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=TelValidator.class)
public @interface Tel {

    int min() default 0;

    String message();

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

验证器TelValidator

package com.my.controller.validator;

import javax.annotation.Resource;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.springframework.context.support.ResourceBundleMessageSource;

public class TelValidator implements ConstraintValidator<Tel, String> {

    @Resource
    private ResourceBundleMessageSource messageSource;

    private Tel tel;

    @Override
    public void initialize(Tel tel) {
        this.tel = tel;
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintContext) {
        boolean isValid;

        if(value != null && value.length() >= tel.min()) {
            isValid = true;
        }
        else {
            isValid = false;
        }

        if(!isValid) {
            constraintContext.disableDefaultConstraintViolation();
            constraintContext.buildConstraintViolationWithTemplate(tel.message()).addConstraintViolation();
        }
        return isValid;
    }

}

转换器Converter

package com.my.controller.converter;

import org.springframework.core.convert.converter.Converter;

public class IntConverter implements Converter<String, Integer> {

    @Override
    public Integer convert(String text) {
        if (text == null || "".equals(text)) {
            return 0;
        } else {
            try {
                Integer value = Integer.parseInt(text);
                return value;
            } catch (Exception e) {
                return 0;
            }
        }
    }

}
package com.my.controller.converter;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.springframework.core.convert.converter.Converter;

public class DateConverter implements Converter<String, Date> {

    @Override
    public Date convert(String text) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        try {
            return dateFormat.parse(text);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

}

测试JSP

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib prefix="st" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="sf" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Index</title>
</head>
<body>
    <sf:form action="${pageContext.request.contextPath}/av/add" method="post" modelAttribute="user4">
        User name:<sf:input path="name"/><sf:errors path="name" /><br/>
        Password:<sf:input path="password"/><sf:errors path="password" /><br/>
        E-mail:<sf:input path="email"/><sf:errors path="email" /><br/>
        Age:<sf:input path="age"/><sf:errors path="age" /><br/>
        Birthday:<sf:input path="birthday"/><sf:errors path="birthday" /><br/>
        Address:<sf:input path="address"/><sf:errors path="address" /><br/>
        Married:
            <sf:radiobutton path="married" label="Yes" value="true"/>
            <sf:radiobutton path="married" label="No" value="false"/>
            <sf:errors path="married" /><br/>
        Likes:
            <sf:checkbox path="likes" label="Football" value="Football"/>
            <sf:checkbox path="likes" label="Badminton" value="Badminton"/>
            <sf:checkbox path="likes" label="Pingpong" value="Pingpong"/>
            <sf:errors path="likes" /><br/>
        Tel:<sf:input path="tel"/><sf:errors path="tel" /><br/>
        <input type="submit" value="Add" />
        <hr/>
        Errors:<br/><sf:errors path="*"></sf:errors>
        <hr/>
        Likes:<c:forEach items="${user4.likes}" var="item">${item},</c:forEach>
    </sf:form>
</body>
</html>

That’s All!测试跑一跑~

作者:changqing5818 发表于2016/12/11 19:46:21 原文链接
阅读:24 评论:0 查看评论

读《必然》的总结与感悟

$
0
0

  前段时从图书馆借了《必然》(《The inevitable》),这是凯文凯利(KK)三部曲之一(其它两部是《失控》和科技想要什么),其中主要内容是对未来的展望。对未来描述的书很多,前段时间读的《奇点临近》也算是一本。但是这两者区别很大,《必然》主要是从现在预言在未来10~30年内人类生活会有什么样的变化,科技如何融入在我们生活之中,而且KK的风格感觉很像碎碎念。《奇点临近》是纵观进化历史的发展,总结历史发展规律,进而描述未来的状态。其跨越的时间段相对较长,作者满篇旁征博引,参考文献的厚度在书中占了大概20%,让人读来感觉言之凿凿。两本书无法评判孰优孰劣,都有很大的闪光点,都有不同的观点和见解。我觉得能学到不同的观点,了解不同的想法,这才是非常重要的。这本书给我的一个强烈的感觉是它把目前出现的一些新的事物进行了总结,并在合理的基础上进行扩展和发散,描绘出未来20~30年的景象。读这一类书,都感觉是在看一个全新的世界,充满了好奇和期待。


  《必然》全书共十二章,包括形成(Becoming)、知化(Cognifying)、流动(Flowing)、屏读(Screening)、使用(Accessing)、共享(Sharing)、过滤(Filtering)、重混(Remixing)、互动(Interacting)、追踪(Tracking)、提问(Questioning),以及开始(Beginning)。每一章都有新的内容,但每一章的内容并不独立,相同的观点融会贯通。比如第三章流动,之所以目前很多东西能流动,能更广泛的传播,因为当前的数字化技术,让很多东西能够轻易的被复制和传播,比如音乐,电影等等。免费复制又恰恰像催化剂一样,让流动更加剧烈。然而,我觉得流动,也正是后面很多章节的基础,比如第4章屏读,第6章共享等。这两个都离不开“流动”这个前提。但是这两章又不仅仅是“流动”一个特例,它们又包含了很多新的内容和新的体验。


  • 第一章 形成(Becoming)。这一章中,作者提出我们现在处于“进托邦”,也就是说现在每天都是比昨天好的,都比昨天有进步。作者举了互联网的发展历程作为一个例子。在这个例子中,让我印象最深的是关于人们对互联网认识的一个转变。最开始,互联网诞生的时候,很少人看好它,特别是当时的媒体,没有想过通过互联网来扩大信息受众,加强自己。因为,他们觉得观众始终是观众,是信息的接收者。而自己才是信息的产生和传播者。如果要生成更加大量的信息,必然要投入很多。然而,在今天,媒体公司并不再是信息的产生着,他们不再创造和生产信息,他们仅仅是提供一个平台,让每一个用户参与其中。此时,用户不仅仅是信息的受众,同样也是信息的生产者。在互联网刚出来的时候,那些媒体公司难以想象这种运作模式。作者在这里提出,类比于互联网的产生到现在,如果我们能站在未来看今天,可能很多新的东西我们还能够做,还能够成为第一人。但是处在今天的我们,估计眼光还会受限吧。但是到达未来是从今天开始,从现在开始,因此作者认为目前仍然是绝无仅有最佳开始时机。
  • 知化(Cognifying)。这一章中作者介绍了以后无处不在的智能,身边的东西似乎都将变得有了“灵性”,能给我们提供更强大更好的服务。然而它们的智能和我们的人类的智能却不一样,我们可能会难以理解这种“异类智能”。但是在作者说的人类和机器人的工作关系的四大类中,或许只有我们不断创造新的工作(只有人类能干的工作),才能不被机器人征服。然后这些新的工作又给机器人来做,我们再创造新的工作,如此循环,也许这是人类和机器人长久的相处之道。

  • 第三章 流动(Flowing)。在现在的科技社会中,数字化给我们带了很大的好处,其中一项就是我们能够轻易的复制被数字化的东西。比如音乐,电影,小说等等。因为能够方便的复制这些东西,这就加速了这些东西的流动,让更多人能够接触和获取,信息的传播更加快速而广泛。再进一步,免费,让我们更加自由的去复制,去传播,去获取,它犹如催化剂一般,让流动更加汹涌澎湃。在免费这个趋势中,无法复制的东西才真正更有价值,比如说人与人之间的信任。作者列举了8中性质(1即时性;2个性化;3解释性;4可靠性;5获取权;6实体化;7可赞助;8可寻性),这些原生性为免费商品增加了价值,从而变成了可出售的商品。作者还指出,流动让我们能改变曾经只能是固定的东西,从而以更低的成本创造出更大的价值。流动让我们在获得产品的过程中能够参与的更多,获得更加个性化的服务。我觉得这些便是在免费使用中获得价值的来源。

  • 第四章 屏读(Screening)。这个对于我们现在的人来说应该不陌生了,通过显示屏获取信息已经成为我们生活的日常部分。从语言口口相传, 到文字在纸上的印刷,再到屏幕上五颜六色,变换各异地显示。虽然纸质的书让我们觉得很靠谱,但是采用屏幕显示文字,已经是个趋势。然而,纸质版的书仍然有存在的必要,因为它被印刷出来后确定了,很难像电子书那么容易改变,除非我们重新印刷。除此以外,电子书有更强大的笔记,我们能在不懂的地方注释,还能加入超链接。这让书本不在独立,而是形成一个网络。我们能够通过超链接,从这本书跳转到其他地方。而且我们以后的屏幕更加先进,更加小巧,随意折叠让其能够更加自由的阅读和携带。作者又幻想了一下,在屏读普遍的时候,他的生活是如何的。

  • 第五章 使用(Accessing)。未来的30年里,减物质化、去中心化、即时性、平台协同和云端将持续发展。我们使用东西并不需要拥有,我们相当于有一个虚拟橱柜,或者处在魔法租赁店,需要使用什么都能轻易获取。比如Uber,我们不需要拥有车,也不需要在街上拦车,我们只需要打开APP就能让车到身边来,从而使用。作为服务的使用者,我们以后需要的只是订阅,并不是拥有,便能获得更新更全的服务。现在已经进入到这一块儿的,就是软件行业,我们并不需要不断的去购买最新的软件使用权,而是逐年订阅,获得不断的更新。

  • 第六章 共享(Sharing)。linux,维基百科等便是共享的例子。虽然有人认为,比如比尔盖茨,这是共产主义的形式,对于身处于资本主义世界的他们,或许觉得这些便是值得嘲笑的。然而共产主义之火可以燎原,人们都希望能够免费获取使用,而且能够参与其中,贡献一份力量,从而促进了共享信息的更新和壮大。

  • 第七章 过滤(Filtering)。我们现在处在信息的洪流之中,如何在其中获取我们想要的信息呢?过滤必不可少。搜索引擎便是一个很好的例子,我们通过输入关键字获取我们想要的信息,过滤掉无关信息。而且搜索引擎现在越来越智能化,能给我们每个人提供个性化的搜索结果。KK说,进行更多的过滤是必然的,因为我们不停地制造新东西。而在我们将要制造的新东西中,首要一点就是创造新的方式来过滤信息和个性化定制,以突显我们的差异。

  • 第八章 重混(Remixing)。经济学家Brain Arthur认为所有的新技术都源自已有技术的组合。现在,数字技术让我们能够容易的拆分获得原有的元素,然后组合,创造出新的元素。 进而,文化作品是出现重混最多的地方。我们能够轻易的获取别人图片中的一部分,我们能够把影片的中片段提取出来,这便是原有的元素。我们在进一步的组合,融入我们自己的想法,变出现了新的东西。这也是对已有的实物重新排列再应用。作者举例最多的便是现在的电影和小短片之间的关系。作者认为,如果好莱坞是金字塔的定点,那么底层才是滋生各种行动的地方,是开启动态影像未来的地方

  • 第九章 互动(Interacting)。这一章中作者举的例子,便是目前很火的VR技术。这种方式让使用者感觉身临其境,还能有除了视觉以外的其他感觉。这个技术不仅仅可以用在游戏之中,当然用在游戏中能大大提高游戏的可玩性,还可以用在生活的各个方面。我们能够虚拟地试穿衣服,能够举行更加现实的远程会议等等。当然这仅仅是互动的一种方式,我们还能够使用语音控制设备,设备能观察我们眼球的运动并作出反应,还能够识别我们的表情。这些新的互动方式是更加深层次,更加便利的。我觉得如果能通过新的互动方式,得到对方(同样是人)的感觉和想法,这或许能打破目前人与人之间的交往方式。

  • 第十章 追踪(Tracking)。作者认为我们能够获取我们身体上各种数据指标,从而实时监测身体状况,这好像是传感器网络的一种应用。通过部署传感器,我们能够实时获取数据,并且进行监测和分析,及时发现问题并进行响应和处理。当然,这里就不得不提一下物联网了,把身边的事物都能接入互联网中,这必然会产生数据的井喷,因此我们需要在云端通过合理的算法进行追踪和处理数据。

  • 第十一章 提问(Questioning)。作者以维基百科为例阐述了这个问题。在为维基百科添加词条,丰富内容的人群中,不再有特别的身份限制,我们每个人都能贡献出自己的知识,不断丰富维基百科的内容。这使得维基百科越来越专业,越来越受信任。虽然作者开始认为维基百科不可能成功,然而这不可能成为了可能。

  • 第十二章 开始(Beginning)。这一章,作者把全球级别的层级叫做holos,它包括了所有人的集体心智、所有机器的集体行为、自然界的智能相结合形成的整体,以及出现在这个整体中的任何行为。感觉我们目前的就生活在holos这个“智慧之物”下面,我们每一次的行为都在丰富着holos,推动holos的进步和发展。作者在这块儿提到了奇点的概念,他不认为人工智能是“我们最后的发明”,相反,我们创造出的东西让自己成为更好的人。这便是开始。

  通过这本书,看到作者描绘的未来,而且感觉这个未来并不像其它很多书中描述的虚无缥缈。这本书中的未来,很多事情已经端倪显现,让我觉得书中描绘的东西更加现实。没错!更加现实的未来!虽然作者确实碎碎念,但是几乎完整地整理出了在计算机领域相关的发展方向(当然还有其他技术,比如生物技术,纳米技术,量子技术等等),也还是值得一读。



作者:cjbct 发表于2016/12/11 19:49:30 原文链接
阅读:27 评论:0 查看评论

UICollectionView教程:开始

$
0
0

原文:UICollectionView Tutorial: Getting Started
作者:Bradley Johnson
译者:kmyhy

注:本文由 Bardley Johnson 升级至 Swift 和 iOS 9,原文作者是 Brandon Trebitowski。

iOS 的照片程序采用时髦的多布局方式显示照片。你可以用网格方式浏览照片:

还可以通过“相册”的方式浏览照片:

你还可以通过捏合手势来切换两种布局。你可能会想:“我也想在我的 App 中使用这个!”

通过 UICollectionView 你可以很容易实现自定义布局和布局转换动画(就如照片程序一样)。

不仅仅是网格布局或栈式布局,因为 Collection View 是非常可定制化的。你可以用它们实现环形布局,封面流布局,Pulse news style 布局——只要你能想到的都能做到。

幸好,只要你对 UITableView 不陌生,那么 Collection View 对你来说都不会成为问题——它们的用法和 Table View 的数据源/委托模式差不多。

在本文,你会通过创建自己的网格式照片浏览 App 来学习 UICollectionView。当你学完本教程,你会学会如何使用基本的 Collection View,并可以在自己的 App 中使用这些神奇的技术!

剖析 UICollectionView

我们来看一下最终完成的项目。UICollectionView 有几个构建组件,如下所示:

它们分别是:

1、UICollectionView – 用于显示主要内容的 UIView。和 UITableView 一样,Collection View 也是 UIScrollView 子类。
2、UICollectionViewCell – 和 TableView 中的 UITableViewCell 一样。这些 cell 构成了视图的内容,并以 Collection View 的 subview 形式存在。cell 可以编码创建,也可以用 IB 创建。
3、Supplementary Views – 如果你需要显示一些不能在 cell 中展示、但仍然需要放在 Collection View 中显示的额外信息,你可以使用 Supplementary View。比如 header 或 footer。

注:Collection View 也可以有 Decoration View —— 如果你想通过一些特殊的 view 增强 Collection View 的外观(通常不会放真正有用的数据),你可以使用 Decoration View。比如背景图片或者其他可视化的装饰性组件。在本文中你不会用到 Decoration View,因为它需要我们编写自定义的布局类。

除此之外,Collection View 还有一个 layout 对象用于管理内容的大小、位置或其它属性。Layout 对象是一个 UICollectionViewLayout 子类。Layout 对象可以在运行时改变,Collection View 甚至为 Layout 之间的切换提供内置的动画。

你可以继承 UICollectionViewLayout 来创建自己的布局,苹果为开发者提供了一个基本的“流式”布局即 UICollectionViewFlowLayout。它将 cell 根据它们的大小一个接一个地排列,就像一个网格。你可以用这个现成的布局,或者对它进行扩展(子类化)实现自己的行为和可视化效果。

在本文和下一篇教程中,你讲深入学习这些元素。现在,请从一个实际项目开始!

FlickrSearch 简介

本文接下来的内容,将创建一个很酷的照片浏览程序,叫做 FlickrSearch。它允许你从著名的照片分享网站 Flickr 上按关键词搜索照片,并将搜索到的照片下载和显示到一个网格视图中,正如早先的截图中所示。

准备好了吗?打开 Xcode ,点开 File\New\Project… 菜单,选择 iOS\Application\Single View Application 模板。

这个模板会提供一个简单的 UIViewController 和 storyboard 故事板文件。这是一个很好的开头。

点击 Next 填写 App 信息。将 Product Name 设为 FlickrSearch, 设备类型选择 iPad ,语言设为 Swift。点击 Next 选择一个文件路径,然后点 Create。

View Controller 和故事板文件对你来说没有用处——我们要使用的是 UICollectionViewController。它和 UITableViewController一样,是一个特殊的 view controller 子类,拥有一个 collection view。将ViewController.swift 删除,然后将 Main.storyboard 中的 View controller 也删除。现在你的故事板是空的了。

打开 AppDelegate.swift,加一个常量用于存放一个阴影色,叫做 Wenderlich Green。在 import 语句下加入:

let themeColor = UIColor(red: 0.01, green: 0.41, blue: 0.22, alpha: 1.0)

Wenderlich Green 作为整个 App 的主题色。然后修改 application(_:didFinishLaunchingWithOptions:) 方法:

func application(application: UIApplication!, didFinishLaunchingWithOptions
  launchOptions: [NSObject: AnyObject]?) -> Bool {

  window?.tintColor = themeColor
  return true
}

开始

打开 Main.storyboard ,拖一个 Collection View Controller。点击 Editor\Embed in\Navigation Controller 菜单,这将创建一个 navigation controller 并将 collection view controller 设置为 root View controller。

现在故事板应该是这个样子:

然后,选择 Navigation Controller ,将它设为 initial view controller:

接着,回到 collection view controller,选中 UICollectionView 背景色修改为白色:

注:知道 Scroll Direction 属性是干什么的吗?这个属性是 UICollectionViewFlowLayout 专有的,默认值是 Vertical。一个垂直的流式布局表示将 cell 按照从左到右排列如果排到最右边,则移到下一行。如果 cell 太多,用户可以通过垂直滚动来查看。
于此对应,水平流式布局将 cell 按照从上到下排列,直到排到最底部。用户可以用水平滚动的方式浏览。本文中使用垂直布局。

选择 collection view 中的 cell,将 Reuse Identifier 设为 FlickrCell。这和 Table View 中是类似的——就像数据源方法会用这个 ID 来重用或创建新的 cell。

拖一个 text field 到 collection view 上方的导航栏中心。这会用于给用户输入搜索关键字。将 Placeholder Text 设置为 Search ,Return Key 设为 Search:

然后,右键从 text field 拖一条线到 collection view controller 并选择 delegate outlet:

!{}(https://koenig-media.raywenderlich.com/uploads/2016/06/rw-cv7.png)

UICollectionViewController 是不错,但通常我们应该创建它的一个子类。点击 File\New\File…, 选择 iOS\Source\Cocoa Touch Class 并点 Next。类名命名为 FlickrPhotosViewController, 继承 UICollectionViewController。模板生成的文件中有许多模板代码,但理解这个类的最好方法还是从空白开始。打开 FlickrPhotosViewController.swift 修改内容为:

import UIKit

final class FlickrPhotosViewController: UICollectionViewController {

  // MARK: - Properties
  private let reuseIdentifier = "FlickrCell"
}

接着,定义一个常量用于 section 的 insets(后面我们会用到):

private let sectionInsets = UIEdgeInsets(top: 50.0, left: 20.0, bottom: 50.0, right: 20.0)

在本文接下来的内容中,你将填充剩余的代码。

回到 Main.storyboard, 选中 collection view controller ,在 Identity 面板中,将 Class 设置为 FlickrPhotosViewController:

从 Flickr 抓取照片

在这一节,你的第一个任务是快速念出本节标题十次。哈哈,开个玩笑。

Flickr 是一个非常好的图片分享网站,向开发者提供公开的、极其简单的 API。通过这个 API 你可以搜索照片,为照片发表评论,等等。

要使用 Flickr API,你需要一个 API key。在真实的项目中,我建议你从这里注册一个:http://www.flickr.com/services/api/keys/apply/

但是,如果是用于测试,Flickr 提供一个示例 key,你可以不用注册就能使用。你可以在这里找到: http://www.flickr.com/services/api/explore/?method=flickr.photos.search 一个并拷贝底部的 Url —— 将 “&api_key=” 和后面的 “&” 之间的内容拷贝到文本编辑器中待用。

例如,如果 URL 是:
http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=6593783 efea8e7f6dfc6b70bc03d2afb&format=rest&api_sig=f24f4e98063a9b8ecc8b522b238 d5e2f

则 API key 就是 6593783efea8e7f6dfc6b70bc03d2afb。

注:如果你使用了示例 API key,注意它每隔几天就会改变一次。因此如果你过几天来做这个例子,你会发现必须更新 API key。因此,如果你完成本文的例子需要几天时间的话,注册自己的 API key 会更轻松一点。

本文的目的是介绍 UICollectionView and 而非 Flickr API,所以有一些关于 Flickr 类已经为你创建好了,这样能减少你搜索 Flickr 时的代码。你可以从这里下载。

将 zip 文件解压缩并将解压缩后的文件拖到你的项目中,确保已勾选 Copy items into destination group’s folder(if needed),然后点击 Finish。

这些文件包含两个类和一个结构:

  • FlickrSearchResults: 一个用于封装关键字和搜索结果的结构。
  • FlickrPhoto: 用于保存从 Flickr 返回的照片数据——它的缩略图、完整图以及一些元数据比如 ID。还有一些方法,比如用于创建 Flick URL 和关于尺寸大小计算的方法。FlickrSearchResults 存储了一个包含这些对象的数组。
  • Flickr: 一个简单的闭包式的 API,用于执行搜索并返回一个 FlickrSearchResult 对象。

你可以看看它们的代码——代码都很简单,但可能会引起你在自己的 App 中使用 Flickr 的兴趣。

在你能够搜索 Flickr 之前,你需要一个 API key。打开 Flickr.swift ,将 apiKey 修改为你获取到的 API key。可能会是这样的:

let apiKey = "hh7ef5ce0a54b6f5b8fbc36865eb5b32"

接下来,进入下一节——在使用 Flickr 之前需要做一点小小准备。

准备数据结构

App 要实现这个效果,每次搜索完之后,都会将搜索结果显示在一个新的 section 里(而不是直接替换掉原来的 section)。也就是说,如果你先搜索了“忍者”,有搜索了“海盗”,则 Collection View 中会同时显示一个“忍者”的 section 和一个“海盗”的 section。来一个灾难的列表怎么样?

要实现这个效果,你需要用一个数据结构将每个 section 所需的数据单独存储。这个可以用一个 FlickrSearchResults 数组来代替。

打开 FlickrPhotosViewController.swift 在 sectonInsets 常量声明下加入:

private var searches = [FlickrSearchResults]()
private let flickr = Flickr()

searches 数组用于保存所有的搜索结果,flickr 用于引用我们要用来进行搜索的对象。然后在文件最后增加一个私有扩展:

// MARK: - Private
private extension FlickrPhotosViewController {
  func photoForIndexPath(indexPath: NSIndexPath) -> FlickrPhoto {
    return searches[indexPath.section].searchResults[indexPath.row]
  }
}

photoForIndexPath 是一个实用方法,根据 Collection View 中的某个的 Index Path 返回对应的 FlickrPhoto 对象。你用 Index Path 来访问照片的地方非常多,这样你就不用重复写同样的代码了。

获取结果

现在开始搜索 Flickr!当用户输入后点击 Search,我们就开始搜索。在 Collectoin View Controller 中,我们已经将 TextField 的 delegate 设置为 self,我们可以利用它来做些事情了。

打开 FlickrPhotosViewController.swift ,添加一个 text field delegate 的扩展:

extension FlickrPhotosViewController : UITextFieldDelegate {
  func textFieldShouldReturn(textField: UITextField) -> Bool {
    // 1
    let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
    textField.addSubview(activityIndicator)
    activityIndicator.frame = textField.bounds
    activityIndicator.startAnimating()

    flickr.searchFlickrForTerm(textField.text!) {
      results, error in


      activityIndicator.removeFromSuperview()


      if let error = error {
        // 2
        print("Error searching : \(error)")
        return
      }

      if let results = results {
        // 3
        print("Found \(results.searchResults.count) matching \(results.searchTerm)")
        self.searches.insert(results, atIndex: 0)

        // 4
        self.collectionView?.reloadData()
      }
    }

    textField.text = nil
    textField.resignFirstResponder()
    return true
  }
}

代码解释如下:

  1. 创建 Activity View ,然后调用 Flickr 类(我提供的用于搜索 Flickr 上的照片)异步搜索给定的关键字。当搜索完成,完成块被调用,并传入一个由 FlickrPhoto 对象构成的结果集,以及一个 NSError 对象(如果有的话)。
  2. 将错误打印到控制台。当然,在真正的 App 中你应当将错误显示给用户。
  3. 打印结果集并将其插入到 searches 数组的前列。
  4. 到这里,你收到新数据,需要刷新 UI。你可以调用 reloadData() 方法,这个方法的作用就如同 TableView 的同名方法。

运行 App。在文本框中进行一个搜索,你会在控制台看到输出的日志,描述结果集的行数:

Found 20 matching bananas

注意 Flickr 类将结果集限制在 20 条数据,以保证加载时间不会太长。

不幸的是,你在 Collectoin View 中看不到图片!正如 TableView,Collection View 需要你实现一系列数据源方法和委托方法。

填充 UICollectionView

你可能想到了,在使用 Table View 时必须设置它的 dataSource 和 delegate 才能为它提供显示数据和处理事件(选中行事件)。

类似地,在使用 Collection View 时也必须设置它的数据源和委托属性。它们的职责分别是:

  • 数据源(UICollectionViewDataSource)负责提供要显示的 item 的个数和视图。
  • 委托(UICollectionViewDelegate)负责处理事件,比如 cell 被选择、加亮或删除等。

UICollectionViewFlowLayout 也有一个委托协议—— UICollectionViewDelegateFlowLayout。它允许你对布局行为进行调整,对某些对象进行配置,比如 cell 间距、滚动方向等等。

在本文,你会实现必要的 UICollectionViewDataSource 和 UICollectionViewDelegateFlowLayout 方法,以使你的 Collection View 能够正常运行。UICollectionViewDelegate 方法在这里不需要,你将在下一教程中使用它。

UICollectionViewDataSource

打开 FlickrPhotosViewController.swift, 添加一个针对 UICollectionViewDataSource 协议的扩展:

// MARK: - UICollectionViewDataSource
extension FlickrPhotosViewController {
  //1
  override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    return searches.count
  }

  //2
  override func collectionView(collectionView: UICollectionView,
                               numberOfItemsInSection section: Int) -> Int {
    return searches[section].searchResults.count
  }

  //3
  override func collectionView(collectionView: UICollectionView,
                               cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
    cell.backgroundColor = .blackColor()
    // Configure the cell
    return cell
  }
}

这是较为简单的几个方法:

  1. 一个 section 代表一个搜索,因此 section 的数目就是 searches 数组中的元素个数。
  2. 每个 section 中的 item 个数,等于对应 FlickrSearch 对象中 searchResults 数组的元素个数。
  3. 一个暂时的实现,仅仅返回空白的 cell——后面我们会用数据填充 cell。注意 Collectoin View 需要你为 cell 注册一个重用 ID,否则运行时会出现错误。

运行 app,执行搜索。你会看到多出 20 个 cell,可能看起来会有点奇怪:

UICollectionViewFlowLayoutDelegate

前面说过,每个 Collection View 都有一个与之关联的 layout。你现在用在项目中的 layout 就是系统内置的流式布局,这种布局简单、易用,提供了一个网格式的布局。

回到 FlickrPhotosViewController.swift, 在 flickr 常量下面声明:

private let itemsPerRow: CGFloat = 3

然后,增加一个对 UICollectionViewDelegateFlowLayout 的扩展,以使你的 view controller 采用这个流式布局委托协议:

extension FlickrPhotosViewController : UICollectionViewDelegateFlowLayout {
  //1
  func collectionView(collectionView: UICollectionView,
                      layout collectionViewLayout: UICollectionViewLayout,
                             sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
    //2
    let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
    let availableWidth = view.frame.width - paddingSpace
    let widthPerItem = availableWidth / itemsPerRow

    return CGSize(width: widthPerItem, height: widthPerItem)
  }

  //3
  func collectionView(collectionView: UICollectionView,
                      layout collectionViewLayout: UICollectionViewLayout,
                             insetForSectionAtIndex section: Int) -> UIEdgeInsets {
    return sectionInsets
  }

  // 4
  func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
    return sectionInsets.left
  }
}
  1. collectionView(_:layout:sizeForItemAtIndexPath:) 方法负责告诉布局引擎每个 cell 应该是多大尺寸。
  2. 计算所有 padding 加起来有多少。即 n+1 乘以每个 cell 所留下的 padding,其中 n 为每行的 cell 个数。cell 间距采用 section inset 的左边边距。将视图的宽减去所有 padding 后再除以每行 cell 数即得到每个 cell 或 item 的宽。然后以这个宽和相等的高作为 item 的 size 返回(也就是一个正方形)。
  3. collectionView(_:layout:insetForSectionAtIndex:) 方法负责返回 cell 之间的间距,cell 和 header/footer 之间的间距。这个值保存在一个常量中。
  4. 这个方法布局中每行之间的间距,我们设为和左右 Padding 相同。

运行程序,进行一次搜索。看!比原来更大的黑色方块!

一切就绪,现在让我们来显示照片吧!

创建自定义 UICollectionViewCells

UICollectionView 中最精彩的一点就是,它和 Table View 一样,通过故事板能够用可视化的方式很方便地配置 CollectionView。你可以拖一个 UICollctionView 到你的 View Controller 中,在故事板编辑器中设计你的 cell。让我们来看看怎么做。

打开 Main.storyboard 选择 collection view。在 Size 面板中将 cell 大小修改为 200x200,让它的空间更大一点。

注:修改 cell 的 size 并不会影响 App 中实际 cell 的大小,因为我们会通过委托方法来配置 cell 的大小,它会覆盖你在故事板中的设置。

拖一个 Image View 到 cell,拖拉其大小占据整个 cell。在保持 Image View 被选中的状态下,打开 Pin 菜单,清除 Constrain to margins 选项,将 4 个方向的间距设置为 0:

选中状态下,将 Image View 的 Mode 设置为 Aspect Fit,这样图片无论哪个方向都不会被剪切和拉伸:

UICollectionViewCell 不允许进行任何定制,除了改变它的背景色外。你只能通过子类化进行定制,并方便地访问所有添加在上面的 subview。

打开 File\New\File… 菜单,选择 iOS\Source\Cocoa Touch Class 模板,点击 Next,类名命名为 FlickrPhotoCell, 并继承 UICollectionViewCell。

打开 Main.storyboard 选择 cell。在 Identity 面板,将 cell 的 Class 设为 FlickrPhotoCell:

打开助手编辑器,源代码窗口切换到 FlickrPhotoCell.swift ,右键从 Image View 拖一条线到源文件,创建一个 IBOutlet,命名为 imageView:

现在你创建了一个带有一个 ImageView 的自定义 cell。让我们来将照片显示在上面!打开 FlickrPhotosViewController.swift 将 collectionView(_:cellForItemAtIndexPath:) 方法替换为:

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  //1
  let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier,
               forIndexPath: indexPath) as! FlickrPhotoCell
  //2
  let flickrPhoto = photoForIndexPath(indexPath)
  //3
  cell.imageView.image = flickrPhoto.thumbnail

  return cell
}

这个方法和之前的占位方法有点不同了。

  1. cell 现在变成了一个 FlickrPhotoCell 对象。
  2. 你需要获得存储有要显示的照片的 FlickrPhoto 对象,你可以调用先前定义的实用方法来获得。
  3. 将缩略图传给 Image View。

运行程序,进行一次搜索,你可以看到你所找到的图片了!

耶!我们成功了!

注:如果你看到的结果与此不同,或者图片看起来有点奇怪,很可能是你的自动布局配置不正确。如果遇到坎,你可以对项目中的设置进行比较。

到此,你的所有工作就完成了(非常酷)—— 给自己一个表扬吧!这是最终完成的项目

结尾

但还不够!继续阅读第二部分的教程,你会学习到:

  • 如何添加自定义的 section header
  • 如何通过拖放来移动 cell
  • 如何在选中一个 cell 时打开详情页
  • 如何选择多个 cell

同时,如果在学习中有任何问题或建议,请留言!

作者:kmyhy 发表于2016/12/11 19:57:08 原文链接
阅读:24 评论:0 查看评论

Modbus协议简介与FreeMODBUS移植

$
0
0

1:Modbus协议简介

    Modbus协议主要描述的是应用层的信息封装格式,处于OSI模式的第七层(应用层)。Modbus的物理层可以是RS-485、Ethernet II /802.3。Modbus协议栈的层次图:
            

        本文主要介绍Modbus使用物理层是EIA/TIA-485的情况。Modbus主要内容为应用层协议,所以在现实使用中可以将EIA/TIA-485(485)和Ethernet II /802.3(以太网)不同的物理连接网络通过Modbus协议组成统一的系统。
使用EIA/TIA-485这种串行通讯方式的Modbus协议框图:


物理层的EIA/TIA-485大家比较熟悉,现场总线通讯很多设备都使用485通讯。下面重点介绍一下Figure 2 中的 Data Link 层和Application层。
Application层主要目的是定义应用层的功能和参数。应用层帧格式见图:

Modbus应用层的Function Code 就是我们熟悉的功能码,常见的有04(read input register) 03(Read Holding Registers)等。这些内容大家比较熟悉,就不一一说明了。
具体功能码含义可参见文章最后的相关资料及工具。

Data Link 层主要目的是确定总线上的具体从机以及检验数据帧传输是否正确。Data Link层的数据帧格式见图:

其中Address field域是地址域,现场使用485总线,总线上可能存在很多设备,这个地址主要用于从总线上选择一个具体的设备。CRC (or LRC是检验位)用于检验数据,确保传输正确。
在485总线上使用Modbus协议,有两种主要方式:RTU ASCII。RTU传输方式传输的数据帧为二进制数据,ASCII传输方式传输的是ASCII码。举例来说要发送 0x31(十进制数)使用RTU直接发送0x31就可以(占用一个byte),而ASCII码传输方式则需要发送0x33和0x31这两个字节(即十六进制数0x31的 高低位分别占用一个byte)。
两种传输方式各有利弊,使用RTU传输效率高,使用ACSII可读性好。
Modbus协议是一种主从式协议,即主站发起通讯,从站响应这种模式。
 最后,给大家一个看一个完整的Modbus协议帧格式:

关于Modbus的具体协议规定和Modbus在串行传输(485)中的使用,请大家查看4:相关资料及工具 里的

Modbus_Application_Protocol_V1_1b3.pdf
Modbus_over_serial_line_V1_02.pdf

其他Modbus相关资料请参考网站:http://www.modbus.org/

2:FreeMODBUS移植

    FreeMODBUS是一个开源的MODBUS协议栈,使用ANSI C语言编写,可在常见的处理器上移植(不需要嵌入式操作系统的支持,当然可以通过很简单的修改可以在嵌入式操作系统上运行)。FreeMODBUS支持从机,若想使用FreeMODBUS实现主机MODBUS,则需要根据实际需求进行二次开发MODBUS协议栈。
关于Modbus的软件结构,移植,配置,使用网上有很多资料和移植经验。我就不详细探讨移植和FreeMODBUS的软件结构。这里只是简单说一下软件的大致结构以及移植过程中需要注意的地方。我会根据MODBUS协议栈的协议规定,以FreeMODBUS为原型,说明从协议栈的规定到具体实现之间的关系。另外,也谈一下自己对通讯协议的一些个人想法。
前面说过,MODUBS串行口传输方式中存在RTU、ASCII 两种方式。这里只针对RTU方式进行分析,ASCII方式自行分析。
 FreeMODBUS的整体软件结构是:前后台系统。利用串行口接收、发送中断完成数据帧在物理层的传输:接收时,接到一帧完整无误的数据帧就通过事件的方式通知前台程序,前台程序根据收到的数据帧做相应的处理。发送时,先封装应用层数据内容,然后再封装链路层数据,最后通过串口中断的方式发送出去。
通过分析FreeMODBUS的整体软件结构我们很容易就知道,FreeMODBUS需要使用到串口(RS485),我们移植FreeMODBUS要实现初始化串口、读写串口、初始化并控制串口中断。
接下来一个重要的问题是RTU方式的MODBUS如何判别一帧数据包哪?有的协议里有开始标示、结束标示,通过判别开头标示和结束标示来表示一帧完整的数据帧。但根据前面的介绍,我们发现MODBUS RTU方式的数据帧并没有开始标示和结束标示,那MODBUS RTU怎么判别一帧数据帧的哪?我们还要看MDBUS协议栈的具体规定。先给大家看一个图:

MODBUS协议里面说一帧数据和下一帧数据之间的间隔至少是3.5个字符。按照9600的波特率来算的话(9600 N 1,一帧数据为10bit),1S大约能传输960字节,一个字符(1 Byte)的传输时间大约为1ms多点。那这个T3.5应该在4ms左右。也就是说一帧完整的数据帧和下一个数据帧之间的间隔应>=4ms。也就是图示中的 Start >= 3.5Char 、End >= 3.5 Char。
MODBUS RTU方式 还有一个时间要求:

根据图示,很明显MIDBUS协议要求一帧数据里,byte和byte之间的时间间隔应<= 1.5个字符,如果还是按照9600的波特率来计算的话,大约是1.56ms。一帧数据内超出这个数值认为数据包错误。

我们知道了这个,似乎明白了MODBUS协议栈是使用两个时间来确定一帧完整的数据包的。记住哦,T3.5 T1.5 很重要的MODBUS协议参数。然后我们也很容易想到FreeMODBUS要实现精确延时,有可能会使用到定时器,对了,你没猜错,FreeMODBUS除了占用一个串口硬件资源外,它还占用了一个定时器硬件资源。所以移植的时候也要处理定时器有关的初始化、启动定时、关闭定时、开关定时器中断等操作。
下面,我们来说一下FreeMODBUS是如何实现上述时间要求的。根据我看FreeMODBUS的源代码,它只利用了一个T3.5,并没有实现T1.5这个时间要求。我的理解是这样的:
FreeMODBUS一般作为从机,从机作为接收时,若主发送的数据不满足T1.5的要求,FreeMODBUS并没有检测,FreeMODBUS只检测是否满足T3.5这个时间,若byte和byte之间的间隔>=T3.5 这个时间,则认为一帧数据包接收完毕。它的设计可能是假定了主机发送的数据帧格式完全符合MODBUS协议。
而从机发送数据时,是使用串口中断发送,肯定满足T1.5的要求,因为MODBUS是主从模式,主机发送完命令,从机接收到后才给主机响应,这种通讯业肯定能满足T3.5这个要求。
好了,我们现在看看FreeMODBUS是怎么实现接收数据帧时根据这个T3.5来实现判断接收完一帧完整的数据帧的。大致流程是这样,初始化一个4ms的定时器,并打开定时器中断,在每次接收到1个byte时,都重置定时器计数值并启动定时器,然后若时间都过去4ms了都没有收到新的byte,那么就认为一帧数据接收完毕。这个也很好理解吧,可以好好琢磨一下这个实现方案。

最后,还有一个事件处理需要在移植FreeMODBUS的时候考虑,这个简单,其实就是在接受完一帧数据或者出错后,通知前台的查询程序,让它做相应的处理。

FreeMODBUS大致分了3层,底层的接收、发送策略(符合MODBUS协议规定)。中间MODBUS协议。然后就是上层应用层,每层的接口设计的不错,方便移植,应用层也方便扩展。


 FreeMODBUS 的官方网站:www.freemodbus.org

3:PLC modbus 地址

            MODBUS协议及FreeMODBUS的基本工作原理、移植都介绍完了。我们接下来看看MODBUS实际应用时的地址一般是怎么定义的。
先说一下标准MODBUS协议里的地址和设备定义的地址之间的关系。见下图:

也就是说,一般设备定义的应用层地址是MODBUS协议里地址数值+1。这么说可能不太容易理解,我举个例子吧,我们做了一个设备支持MODBUS协议,我们给用户的使用说明书里说 0x0001这个地址是一个模拟输入量(比如说是压力),根据上面的MODBUS要求,我们发送的MODBUS协议里的地址域里应该填写的内容是0x0001 - 0x0001 = 0x0000。即封装成标准MODBUS协议帧时的地址是0。其实,这里的Application specific 是指具体应用时的地址。这个地址只和具体使用一个设备时有关,这个地址和MODBUS协议里的地址域里的地址关系见上图。

MODBUS使用最多的是PLC,我们就说说这个MODBUS通讯协议里规定的地址和PLC定义的应用层地址之间是个什么关系?

通常PLC 定义的  Modbus 地址由 5 位数字组成,包括起始的数据类型代号,以及后面的偏移地址。Modbus Master 协议库把标准的 Modbus 地址映射为所谓 Modbus 功能号,读写从站的数据。Modbus Master 协议库支持如下地址: 
00001 - 09999:数字量输出( 线圈)          
10001 - 19999:数字量输入(触点) 
30001 - 39999:输入数据寄存器(通常为模拟量输入)
40001 - 49999:数据保持寄存器

也就是说,PLC编程时只需要设置读取、写入,还有5位PLC MODBUS地址即可,Modbus Master 协议库会根据你填写的地址内容自动转换为相应的标准MODBUS协议包内容,主要是根据5位地址中的第一位和设置的读取还是写入来生成MODBUS协议里的功能码和地址。

这里不好理解,我还是举例。比如有这样一个系统需求:PLC采集压力数据,根据压力数据用来控制电机。PLC作为主机,压力采集器作为从机,他们之间通过MODBUS协议通讯。压力采集器支持标准的modbus协议。
我们想通过PLC读取一路压力数据。我们在PLC端编程时,设置成   读取  地址写成 30001(十进制数)。其实通过Modbus Master 协议库会把这样的一个请求命令转换为标准的MODBUS协议。
具体为:根据30001这个数据我们知道这个是一个输入数据寄存器(通常为模拟量输入),在modbus协议栈里这个对应的是 Input Register ,而我们是读取。所以这个功能是Read Input Register (对应的标准MODBUS协议的功能码是0x04) ,而地址我们先去掉最高位的3,剩下0001(十进制),然后根据MODBUS协议规定,我们应该再减去1,生成标准MODBUS协议。即地址为0。转换完后是这样的一个结果,功能码是0x04(1 byte),地址是0x0000(2 byte)


4:相关资料及工具

           
     Modbus_Application_Protocol_V1_1b3.pdf
   Modbus_over_serial_line_V1_02.pdf
        freeModbus代码解读及移植笔记.doc
        ModBus协议--易理解.doc
        ModbusPOLL工具

作者:xiaoluoshan 发表于2016/12/11 19:57:40 原文链接
阅读:22 评论:0 查看评论

UICollectionView教程:重用、选择和排序

$
0
0

原文:UICollectionView Tutorial: Reusable Views, Selection, and Reordering
作者:Bradley Johnson
译者:kmyhy

注:本文由 Bardley Johnson 升级至 Swift 和 iOS 9,原文作者是 Brandon Trebitowski。

在第一部分教程中,你学习了如何用 UICollectionView 以网格的形式显示图片。
在第二部分,你将继续学习如何与 UICollectionView 交互以及定制它的 header。我们将继续利用第一部分教程中的项目。打开你的项目或者上节课中的 FlickrSearch 然后开始。 你还是需要使用上节课中用到新的 API key。如果你学习上节课的时候已经隔了一段时间了,那么你还需要重新获得一个 API key。

添加 header

这个 app 每搜索一次就添加一个 section。最好将每个搜索的结果集放在单独的 section 里展示。让用户更加清楚这些照片都是关于什么的。

我们将使用 UICollectionReusableView 来创建 header。这个类就像 Collection View Cell(事实上,cell 也继承了这个类),但它是用来表示别的东西比如 header 或 footer。

这个 View 可以通过故事板创建,然后连接到某个自定义类上。打开 File\New\File… 菜单,选择 iOS\Source\Cocoa Touch Class 模板,然后点 Next,类名命名为 FlickrPhotoHeaderView ,继承自 UICollectionReusableView。点击 Next、Create ,保存文件。
打开 MainStoryboard.storyboard ,从左边的 Scene 面板中点击 collection view ( 可能需要从根视图往下展开若干层级)。打开属性面板,勾选 Accessories 下面的 Section Header:

查看左边 Scene 面板,你会发现在 Collection View 下方增加了一个 “Collection Reusable View” 。点击这个 Collection Reusable View ,你就可以在里面添加 subview 了。

现在扩大一下它的空间,点击底部的白色拉柄向下拉,让它有 90 像素高。(或者在 Size 面板中修改大小)。

拖一个 Label 到 header上,将它居中对齐到导线。字体修改为 System 32.0,然后使用 Alignment 菜单,将它和 container view 水平和垂直对齐并 update frame:

选中 header,打开 Identity 面板,将 Class 设为 FlickrPhotoHeaderView。

打开属性面板,背景色设为 Group Table View Background Color,将 Identifier 设为 FlickrPhotoHeaderView。这个Identity 将在重用 Header View 时用到。

打开助手编辑器,源代码窗口中打开 FlickrPhotoHeaderView.swift ,右键从 label 拖一条线到源文件中,创建一个 IBOutlet,命名为 label:


class FlickrPhotoHeaderView: UICollectionReusableView {
  @IBOutlet weak var label: UILabel!
}

如果你这时运行程序,你还看不见 header(它仅仅是空白的一条,上面写着“Label”)。你还需要实现另外一个数据源方法。打开 FlickrPhotosViewController.swift 在 UICollectionViewDataSource 扩展的collectionView(_:numberOfItemsInSection:) 方法下加入这个方法:

override func collectionView(collectionView: UICollectionView,
                             viewForSupplementaryElementOfKind kind: String,
                             atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
  //1
  switch kind {
  //2
  case UICollectionElementKindSectionHeader:
    //3
    let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind,
                       withReuseIdentifier: "FlickrPhotoHeaderView",
                       forIndexPath: indexPath) as! FlickrPhotoHeaderView
    headerView.label.text = searches[indexPath.section].searchTerm
    return headerView
  default:
    //4
    assert(false, "Unexpected element kind")
  }
}

这个方法和 cellForItemAtIndexPath 方法相似,只不过是用在补充视图上。代码解释如下:

  • layout 对象 kind 参数表明这个方法是针对哪一种类型的补充视图。
  • UICollectionElementKindSectionHeader 是属于流式布局的一种补充视图。通过勾选故事版编辑器中的选项,我们增加了这种类型的补充视图——section header,同时流式布局就知道需要去请求哪一种类型的视图。还有一种补充视图是 UICollectionElementKindSectionFooter, 但是我们现在没有用到。如果你采用的不是流式布局,那你可以不用 header 和 footer。
  • header 视图通过在故事板中指定的 identifier 进行重用。这种机制类似于 cell。标签上的文本会被搜索关键字替代。
  • 用一个断言告诉其它开发者(包括以后的你)除了 header view 之外,你不会提供别的视图了。

运行程序。你会看到你的 UI 接近完成了。如果你进行多次搜索,你会发现每个结果集都有一个漂亮的 section header。另外当你旋转屏幕——注意看布局,包括 header,都会自适应,不需要进行额外的工作。

和 cell 进行交互

在本课的最后一部分,你将学习与 cell 的交互。有 3 种不同的方式。第一种是显示一个大图。第二种是通过多选进行分享。第三种是允许用户通过拖动重排图片。

单选

Collection View 可以动态修改它们的布局。你的第一个任务是当 item 被点击时显示大图。

首先,要加一个属性,保存当前被点击的 cell。打开 FlickrPhotosViewController.swift ,在 itemPerRow 之下添加属性:

//1
var largePhotoIndexPath: NSIndexPath? {
  didSet {
    //2
    var indexPaths = [NSIndexPath]()
    if let largePhotoIndexPath = largePhotoIndexPath {
      indexPaths.append(largePhotoIndexPath)
    }
    if let oldValue = oldValue {
      indexPaths.append(oldValue)
    }
    //3
    collectionView?.performBatchUpdates({
      self.collectionView?.reloadItemsAtIndexPaths(indexPaths)
    }) { completed in
      //4
      if let largePhotoIndexPath = self.largePhotoIndexPath {
        self.collectionView?.scrollToItemAtIndexPath(
          largePhotoIndexPath,
          atScrollPosition: .CenteredVertically,
          animated: true)
      }
    }
  }
}

代码解释如下:

  1. largePhotoIndexPath 是一个可空变量,保存了当前点击到的图片的 Index Path,如果它存在的话。
  2. 当这个属性改变,Collection View 需要刷新。利用 didSet 属性观察器能被很方便地做到这一点。这里有两个 cell 需要改变,用户当前点击的 cell 和之前点击过另一个 cell,或者用户是再次点击第一次点击的 cell,这些 cell 的大小需要改变。
  3. performBatchUpdates 能够以动画块的方式改变 Collection View。 在动画块中,你刷新受影响的 cell。
  4. 当动画结束,将变大的 cell 滚动到屏幕中央。

你也许会问,怎样让 cell 变大?请稍等一分钟!

点击 cell 时会让 Cellection View 去调用 cell 选中方法。你想知道是哪个 cell 被点击,可以修改 largeIndexPath 属性,当然你并不想真的选择这个 cell,因为之后我们要做多选,那会让我们混淆。UICollectionViewDelegate 为你想到了这一点。Collection View 会询问 delegate 某个 cell 是否能够被选中。打开 FlickrPhotosViewController.swift, 在 UICollectionViewDataSource 扩展中新增方法:

// MARK: - UICollectionViewDelegate
extension FlickrPhotosViewController {

  override func collectionView(collectionView: UICollectionView,
                               shouldSelectItemAtIndexPath indexPath: NSIndexPath) -> Bool {

    largePhotoIndexPath = largePhotoIndexPath == indexPath ? nil : indexPath  
    return false
  }
}

这个方法非常简单。如果点击的 cell 已经是一个大图,设置 largePhotoIndexPath 属性为 nil,否则设置为所点的 cell 的 IndexPath。这将调用这个属性的属性观察器,在这个方法中刷新受影响的 cell。

要让点击的 cell 变大,你需要修改 sizeForItemAtIndexPath 委托方法。在这个方法中使用如下代码:

// 新增代码
if indexPath == largePhotoIndexPath {
  let flickrPhoto = photoForIndexPath(indexPath)
  var size = collectionView.bounds.size
  size.height -= topLayoutGuide.length
  size.height -= (sectionInsets.top + sectionInsets.right)
  size.width -= (sectionInsets.left + sectionInsets.right)
  return flickrPhoto.sizeToFillWidthOfSize(size)
}

这里将 cell 的大小设置为尽可能占据整个 Collection View 大小,同时保持它的宽高比。除非你要显示更大的图片,否则没必要将 cell 弄得很大。

打开 Main.storyboard,拖入一个 Activity Indicator 到 cell 的 Image View 上。在属性面板中,将 Style 设置为 Large White 并勾选 Hides When Stopped 。将 Indicator 拖在 cell 的中央(这时导线出现),然后使用 Alignment 菜单,选择水平和垂直居中于 Container。

打开助手编辑器,右键,从 Activity Indicator 拖一条线到 FlickrPhotoCell.swift,创建一个 Outlet,命名为 activitIndicator:

@IBOutlet weak var activityIndicator: UIActivityIndicatorView!

同样在 FlickrPhotoCell.swift 中,添加下一个属性观察器,指定 cell 的背景色,然后调用属性观察器:

// MARK: - Properties
override var selected: Bool {
  didSet {
    imageView.layer.borderWidth = selected ? 10 : 0
  }
}

// MARK: - View Life Cycle
override func awakeFromNib() {
  super.awakeFromNib()
  imageView.layer.borderColor = themeColor.CGColor
  selected = false
}

然后,打开 FlickrPhotosViewController.swift 修改 collectionView(_:cellForItemAtIndexPath:) 方法为:

override func collectionView(collectionView: UICollectionView,
                             cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

  let cell = collectionView.dequeueReusableCellWithReuseIdentifier(
               reuseIdentifier, forIndexPath: indexPath) as! FlickrPhotoCell
  var flickrPhoto = photoForIndexPath(indexPath)

  //1
  cell.activityIndicator.stopAnimating()

  //2
  guard indexPath == largePhotoIndexPath else {
    cell.imageView.image = flickrPhoto.thumbnail
    return cell
  }

  //3
  guard flickrPhoto.largeImage == nil else {
    cell.imageView.image = flickrPhoto.largeImage
    return cell
  }

  //4
  cell.imageView.image = flickrPhoto.thumbnail
  cell.activityIndicator.startAnimating()

  //5
  flickrPhoto.loadLargeImage { loadedFlickrPhoto, error in

    //6
    cell.activityIndicator.stopAnimating()

    //7
    guard loadedFlickrPhoto.largeImage != nil && error == nil else {
      return
    }

    //8
    if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? FlickrPhotoCell
                  where indexPath == self.largePhotoIndexPath  {
      cell.imageView.image = loadedFlickrPhoto.largeImage
    }
  }

  return cell
}

代码有点长,我们分别解释如下:

  1. 停止 Activity 的旋转——以便重用先前正在加载图片的 cell。
  2. 这部分和之前的一样。如果没有找到当前 cell 的大图,那么将它设置为缩略图并返回。
  3. 如果已经加载到大图,设置它为大图并返回。
  4. 如果执行到这一步,说明你想要查看大图,但它还没下载。先使用缩略图,然后让 Activity 开始旋转。缩略图会被拉大显示,直到大图下载完成后。
  5. 从 Flickr 请求大图。通过异步方式加载大图,并提供一个完成块。
  6. 当下载完成,让小菊花停止旋转。
  7. 如果发生错误或者图片未能下载,什么也不做。
  8. 当下载完成时,根据 Index Path 来判断用户是否选择了另一个的 cell(如果视图发生过滚动,则很可能这个 cell 已经不是原来的 cell 了),如果还是原来那个 cell,设置它的 image 为下载到的大图。

运行程序,进行一次搜索,然后选一张图片——它会被拉伸到整个屏幕,其他 cell 会自动移开以挪出足够的空间。

再次点击 cell,或者开始滚动或点击其它 cell。你不需要写任何一句移动或让这些 cell 移动的代码,Collection View 和它的 layout 对象为你完成剩下的所有事情。

多选

你接下来的任务是允许用户选择多张图片,并和朋友分享。在 Collection View 中进行多选非常类似于 Table View。唯一的一点是告诉 Collection View 启用多选。

这个过程分为几个步骤进行:

  1. 用户点击 Share 按钮告诉 UICollectionView 开启多选模式——并将 sharing 属性标记为 YES。
  2. 用户选择多张图片,将它们添加到一个数组中。
  3. 用户再次点击分享按钮,打开分享界面。
  4. 用户或者完成分享,或者点击取消,选中的图片和 Collection View 又恢复单选模式。

首先打开 FlickrPhotosViewController.swift 在 itemsPerRow 属性下添加:

private var selectedPhotos = [FlickrPhoto]()
private let shareTextLabel = UILabel()

然后,在私有扩展的 photoForIndexPath(_:) 方法下添加方法:

func updateSharedPhotoCount() {
  shareTextLabel.textColor = themeColor
  shareTextLabel.text = "\(selectedPhotos.count) photos selected"
  shareTextLabel.sizeToFit()
}

selectedPhotos 数组会保存住用户所选的图片,shareTextLabel 则用于显示用户选择了几张图片。你会调用 updateSharedPhotoCount 来更新 shareTextLabel 的显示。

然后在 largePhotoIndexPath 属性下添加下列属性:

var sharing: Bool = false {
  didSet {
    collectionView?.allowsMultipleSelection = sharing
    collectionView?.selectItemAtIndexPath(nil, animated: true, scrollPosition: .None)
    selectedPhotos.removeAll(keepCapacity: false)

    guard let shareButton = self.navigationItem.rightBarButtonItems?.first else {
      return
    }

    guard sharing else {
      navigationItem.setRightBarButtonItems([shareButton], animated: true)
      return
    }

    if let _ = largePhotoIndexPath  {
      largePhotoIndexPath = nil
    }

    updateSharedPhotoCount()
    let sharingDetailItem = UIBarButtonItem(customView: shareTextLabel)
    navigationItem.setRightBarButtonItems([shareButton,sharingDetailItem], animated: true)
  }
}

sharing 是一个 Bool 属性,我们为它定义了另一个属性观察器,和前面的 largePhotoIndexPath 类似。在这个属性观察器中,你会切换 Collection View 的多选状态,清空所选的 cell 和照片数组。同时,你还要在导航栏中加入和刷新 shareTextLabel 和 UIBarButtonItem。

打开 Main.storyboard ,拖一个 UIBarButtonItem 到 Collectoin View 上面的导航栏的右边。 在属性面板中,将 System Item 设置为 Action,并选择一个常见的分享图标。打开助手编辑器,确认源代码窗口中打开的是 FlickrPhotosViewController.swift ,然后用右键从 bar button 拖一条线到源代码中,创建一个 IBAction 叫做 share:,sender 类型设置为 UIBarButtonItem。
实现这个 IBAction 方法:

@IBAction func share(sender: UIBarButtonItem) {
  guard !searches.isEmpty else {
    return
  }

  guard !selectedPhotos.isEmpty else {
    sharing = !sharing
    return
  }

  guard sharing else  {
    return
  }
  //TODO actually share photos!
}

这个方法仅仅判断用户是否搜索结果是否为空,如果不为空,分享所选照片。

现在开始允许用户选择 cell。在 collectionView(_:shouldSelectItemAtIndexPath:) method: 顶部加入:

guard !sharing else {
  return true
}

这段代码只允许用户在分享模式下选择 cell.

然后,在 UICollectionViewDelegate 扩展的 collectionView(_:shouldSelectItemAtIndexPath:) 方法后面加入:

override func collectionView(collectionView: UICollectionView,
                             didSelectItemAtIndexPath indexPath: NSIndexPath) {
  guard sharing else {
    return
  }

  let photo = photoForIndexPath(indexPath)
  selectedPhotos.append(photo)
  updateSharedPhotoCount()
}

这个方法将选中的图片添加到要分享的照片数组中,然后刷新 shareTextLabel 标签。

最后,在 collectionView(_:didSelectItemAtIndexPath:) 方法下面添加:

override func collectionView(collectionView: UICollectionView,
                             didDeselectItemAtIndexPath indexPath: NSIndexPath) {

  guard sharing else {
    return
  }

  let photo = photoForIndexPath(indexPath)

  if let index = selectedPhotos.indexOf(photo) {
    selectedPhotos.removeAtIndex(index)
    updateSharedPhotoCount()
  }
}

这个方法将照片从要分享的照片数组中删除,并刷新 shareTextLabel 标签。

允许程序,进行一个搜索。点击分享按钮,切换到分享模式,选择几张图片。标签会实时发生变化,选到的照片会有一个 Wenderlich 绿的加亮边框。

再次点击 Share 按钮,所有 cell 恢复未选中状态,你又会回到非分享模式,这时你点击 cell 会显示大图。

当然,现在分享按钮还没有任何意义,因为我们还没有真正做到图片的分享!
在 share(_:) 方法中,将 TODO 注释替换为如下代码:

var imageArray = [UIImage]()
for selectedPhoto in selectedPhotos {
  if let thumbnail = selectedPhoto.thumbnail {
    imageArray.append(thumbnail)
  }
}

if !imageArray.isEmpty {
  let shareScreen = UIActivityViewController(activityItems: imageArray, applicationActivities: nil)
  shareScreen.completionWithItemsHandler = { _ in
    self.sharing = false
  }
  let popoverPresentationController = shareScreen.popoverPresentationController
  popoverPresentationController?.barButtonItem = sender
  popoverPresentationController?.permittedArrowDirections = .Any
  presentViewController(shareScreen, animated: true, completion: nil)
}

首先,创建了一个 UIImage 数组,将缩略图放入。这个 UIImage 数组可以很方便地传给 UIActivityViewController。而 UIActivityViewController 会显示要手机上所有存在的分享程序或动作:iMessage、Mail、打印机扥等。你只需将 UIActivity以 Popover 方式呈现(因为这是一个 iPad App),剩下的就是用户的事情了。

运行程序,进入分享模式,选择几张图片,再次点击分享按钮。分享对话框会出现!

注:如果你用模拟器进行测试,你会发现模拟器中的分享选项要比设备中更少。如果你不能确定分享界面能否正常分享照片,请使用保存图片选项。无论设备还是模拟器,这都会将照片保存进照片程序,你可以通过这种方式确认分享功能是正常的。

渲染 cell

在 iOS 9 之前,Collection View 和它的近亲 Table View 相比缺少一个重要的功能:即很容易对 cell 进行重排。幸运的是,这种情况得到改变,在这个项目中你能够很容易地实现它,因为你使用的是 Collection View Controller。

现在, Collection View Controller 有一个 installsStandardGestureForInteractiveMovement 属性,它默认是 true。这个属性决定是否可以在 Collection View 上通过一个手势来重排 cell。你唯一需要做的事情就是重写 UICollectionViewDataSource 协议中的某个方法。打开 FlickrPhotosViewController.swift, 在 UICollectionViewDataSource 扩展的 collectionView(_:cellForItemAtIndexPath:) 方法后加入这个方法 :

override func collectionView(collectionView: UICollectionView,
                             moveItemAtIndexPath sourceIndexPath: NSIndexPath,
                             toIndexPath destinationIndexPath: NSIndexPath) {

  var sourceResults = searches[sourceIndexPath.section].searchResults
  let flickrPhoto = sourceResults.removeAtIndex(sourceIndexPath.row)

  var destinationResults = searches[destinationIndexPath.section].searchResults
  destinationResults.insert(flickrPhoto, atIndex: destinationIndexPath.row)  
}

实现这个方法很简单。你只需要将要移动的 cell 从结果集数组中删除,然后重新放到数组的新位置即可。

运行程序,选择一张照片,移动到另外一个位置:

太好了,只需要短短 4 行代码!

结束

完整项目在这里下载。

恭喜你,你创建了一个非常定制化的 Flickr 照片浏览程序,使用了一个非常酷网格式的 UICollectionView。

在本教程中,你学习了如何定制 UICollectionView,通过 UICollectionReusableView 创建 header,监听 cell 的点击,支持多选等等。

如果你有问题或建议,请留言。

作者:kmyhy 发表于2016/12/11 19:58:37 原文链接
阅读:29 评论:0 查看评论

Android开发艺术探索

$
0
0

ClipDrawabe对应于标签,他可以根据自己当前的等级(level)来裁剪一个Drawable,裁剪方向可以通过android:clipOrientationandroid:gravity两个属性共同控制。语法如下:

<?xml version="1.0" encoding="utf-8"?> 
<clip xmlns:android="http://schemas.android.com/apk/res/android" 
android:clipOrientation="vertical\horizontal" 
android:drawable="@drawable/bitmapdrawable" 
android:gravity="bottom|top|left|right|center|fill|center_vertical|center_horizontal|fill_vertical|fill_horizontal|clip_vertical|clip_horizontal" />

ClipDrawable的gravity属性

选项 含义

  • top 将内部的Drawable放在容器的顶部,不改变大小,如果为竖直裁剪,就从底部开始裁剪

  • bottom 将内部的Drawable放在容器的底部,不改变大小,如果为竖直裁剪,就从顶部开始裁剪

  • left 默认值。内部Drawable放在容器左边,不改变大小,如果为水平裁剪,就从右边开始裁剪。

  • right 内部Drawable放在容器右边,不改变大小,如果为水平裁剪,就从左边开始裁剪

  • center_vertical Drawable在容器中竖直居中,不改变大小,竖直裁剪的时候上下同时开始裁剪

  • fill_vertical Drawable在竖直方向填充容器,如果为竖直裁剪,仅当ClipDrawable的等级为0(level=0,完全不可见)时,才会有裁剪行为

  • center_horizontal Drawable水平居中,不改变大小,水平裁剪的时候从左右两边开始裁剪

  • fill_horizontal Drawable在水平方向填充,如果为水平裁剪,仅当ClipDrawable等级=0的时候,才能有裁剪行为。

  • center Drawable在水平和竖直方向居中,不改变大小,水平裁剪的时候从左右开始裁剪,竖直裁剪的时候从上下开始裁剪。

  • fill Drawable在竖直和水平方向填充容器,仅当level=0的时候才有裁剪行为

  • clip_vertical 附加选项,竖直方向的裁剪,少使用

  • clip_horizontal 附加选项,水平方向的裁剪,少使用

使用示例

1.定义ClipDrawable

<?xml version="1.0" encoding="utf-8"?> 
<clip xmlns:android="http://schemas.android.com/apk/res/android" 
android:clipOrientation="vertical" 
android:drawable="@drawable/bitmapdrawable" 
android:gravity="fill_vertical" /> 

2.布局文件引用

<ImageView 
android:id="@+id/image_clip" 
android:layout_width="100dp" 
android:layout_height="100dp" 
android:src="@drawable/clipdrawable" /> 

3.代码控制level

ImageView imageClip = (ImageView) findViewById(R.id.image_clip); 
ClipDrawable drawable = (ClipDrawable) imageClip.getDrawable(); 
drawable.setLevel(5000);

最后补充

对于ClipDrawable来说,level=0的时候,表示完全裁剪,level=10000的时候表示完全不裁剪,level=5000的时候表示裁剪了一半。即等级越大,裁剪的区域越小。

作者:player_android 发表于2016/12/11 20:00:09 原文链接
阅读:34 评论:0 查看评论

SSH进阶(5)——Struts2对异常支持

$
0
0
分为:全局异常和局部异常

      struts2支持声明式异常处理,可以再Action中直接抛出异常而交给struts2来处理,当然需要我们在xml文件中配置,由于抛出同样的异常的处理方法通常都一样,所以如果能在xml中配置全局异常,将会使得开发便捷性大大提高。在页面中可以使用el取得异常信息。

    ${exception.message }<br>
    ${exceptionStack}<br>

if (!("admin".equals(username) && "admin".equals(password))) {
            throw new ApplicationException("用户名称或密码错误");
        } 



    


<body>
    ${exception.message }<br>
    ${exceptionStack}<br>
</body> 

 
 
全局异常:

<global-results>
            <result name="global-error">/global_error.jsp</result>
        </global-results>
        <global-exception-mappings>
            <exception-mapping result="global-error" exception="com.bjpowernode.struts2.ApplicationException"/>
        </global-exception-mappings> 


 

局部异常:
<action name="login" class="com.bjpowernode.struts2.LoginAction">
            <!-- 局部异常 -->
            <!-- 
            <exception-mapping result="error" exception="com.bjpowernode.struts2.ApplicationException"/>
             -->
            <result>/login_success.jsp</result> 
            <!-- 
            <result name="error">/login_error.jsp</result>
             -->
        </action>  



 
<!-- 当struts.xml配置文件发生修改,会立刻加载,在生产环境下最好不要配置 -->
    <constant name="struts.configuration.xml.reload" value="true"/>
    <!-- 会提供更加友好的提示信息 -->
    <constant name="struts.devMode" value="true"/>
    <!-- 需要继承struts-default包,这样就拥有的最基本的功能 -->
    <package name="struts2" extends="struts-default">  


总结:

      Struts2的捕获异常的任务交给xml配置文件,配置文件还是比较容易理解的。



作者:u012904383 发表于2016/12/11 20:06:30 原文链接
阅读:29 评论:0 查看评论

RabbitMQ学习之延时队列

$
0
0

在实际的业务中我们会遇见生产者产生的消息,不立即消费,而是延时一段时间在消费。RabbitMQ本身没有直接支持延迟队列功能,但是我们可以根据其特性Per-Queue Message TTL和 Dead Letter Exchanges实现延时队列。也可以通过改特性设置消息的优先级。

1.Per-Queue Message TTL
RabbitMQ可以针对消息和队列设置TTL(过期时间)。队列中的消息过期时间(Time To Live, TTL)有两种方法可以设置。第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。第二种方法是对消息进行单独设置,每条消息TTL可以不同。如果上述两种方法同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就成为dead message,消费者将无法再收到该消息。
2.Dead Letter Exchanges
当消息在一个队列中变成死信后,它能被重新publish到另一个Exchange。消息变成Dead Letter一向有以下几种情况:
消息被拒绝(basic.reject or basic.nack)并且requeue=false
消息TTL过期
队列达到最大长度
实际上就是设置某个队列的属性,当这个队列中有Dead Letter时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange中去,进而被路由到另一个队列,publish可以监听这个队列中消息做相应的处理,这个特性可以弥补RabbitMQ 3.0.0以前支持的immediate参数中的向publish确认的功能。

一、在队列上设置TTL


1.建立delay.exchange


这里Internal设置为NO,否则将无法接受dead letter,YES表示这个exchange不可以被client用来推送消息,仅用来进行exchange和exchange之间的绑定。

2.建立延时队列(delay queue)


如上配置延时5min队列(x-message-ttl=300000)

x-max-length:最大积压的消息个数,可以根据自己的实际情况设置,超过限制消息不会丢失,会立即转向delay.exchange进行投递

x-dead-letter-exchange:设置为刚刚配置好的delay.exchange,消息过期后会通过delay.exchange进行投递

这里不需要配置"dead letter routing key"否则会覆盖掉消息发送时携带的routingkey,导致后面无法路由为刚才配置的delay.exchange

3.配置延时路由规则

需要延时的消息到exchange后先路由到指定的延时队列

1)创建delaysync.exchange通过Routing key将消息路由到延时队列


2.配置delay.exchange 将消息投递到正常的消费队列

配置完成。

下面使用代码测试一下:

生产者:

package cn.slimsmart.study.rabbitmq.delayqueue.queue;

import java.io.IOException;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {

	private static String queue_name = "test.queue";

	public static void main(String[] args) throws IOException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("10.1.199.169");
		factory.setUsername("admin");
		factory.setPassword("123456");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		// 声明队列
		channel.queueDeclare(queue_name, true, false, false, null);
		String message = "hello world!" + System.currentTimeMillis();
		channel.basicPublish("delaysync.exchange", "deal.message", null, message.getBytes());
		System.out.println("sent message: " + message + ",date:" + System.currentTimeMillis());
		// 关闭频道和连接
		channel.close();
		connection.close();
	}
}
消费者:

package cn.slimsmart.study.rabbitmq.delayqueue.queue;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;

public class Consumer {

	private static String queue_name = "test.queue";

	public static void main(String[] args) throws Exception {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("10.1.199.169");
		factory.setUsername("admin");
		factory.setPassword("123456");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		// 声明队列
		channel.queueDeclare(queue_name, true, false, false, null);
		QueueingConsumer consumer = new QueueingConsumer(channel);
		// 指定消费队列
		channel.basicConsume(queue_name, true, consumer);
		while (true) {
			// nextDelivery是一个阻塞方法(内部实现其实是阻塞队列的take方法)
			QueueingConsumer.Delivery delivery = consumer.nextDelivery();
			String message = new String(delivery.getBody());
			System.out.println("received message:" + message + ",date:" + System.currentTimeMillis());
		}
	}

}
二、在消息上设置TTL


实现代码:

生产者:

package cn.slimsmart.study.rabbitmq.delayqueue.message;

import java.io.IOException;
import java.util.HashMap;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {

	private static String queue_name = "message_ttl_queue";

	public static void main(String[] args) throws IOException {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("10.1.199.169");
		factory.setUsername("admin");
		factory.setPassword("123456");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		HashMap<String, Object> arguments = new HashMap<String, Object>();
		arguments.put("x-dead-letter-exchange", "amq.direct");
		arguments.put("x-dead-letter-routing-key", "message_ttl_routingKey");
		channel.queueDeclare("delay_queue", true, false, false, arguments);

		// 声明队列
		channel.queueDeclare(queue_name, true, false, false, null);
		// 绑定路由
		channel.queueBind(queue_name, "amq.direct", "message_ttl_routingKey");

		String message = "hello world!" + System.currentTimeMillis();
		// 设置延时属性
		AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder();
		// 持久性 non-persistent (1) or persistent (2)
		AMQP.BasicProperties properties = builder.expiration("300000").deliveryMode(2).build();
		// routingKey =delay_queue 进行转发
		channel.basicPublish("", "delay_queue", properties, message.getBytes());
		System.out.println("sent message: " + message + ",date:" + System.currentTimeMillis());
		// 关闭频道和连接
		channel.close();
		connection.close();
	}
}
消费者:

package cn.slimsmart.study.rabbitmq.delayqueue.message;

import java.util.HashMap;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;

public class Consumer {

	private static String queue_name = "message_ttl_queue";

	public static void main(String[] args) throws Exception {
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("10.1.199.169");
		factory.setUsername("admin");
		factory.setPassword("123456");
		Connection connection = factory.newConnection();
		Channel channel = connection.createChannel();
		HashMap<String, Object> arguments = new HashMap<String, Object>();
		arguments.put("x-dead-letter-exchange", "amq.direct");
		arguments.put("x-dead-letter-routing-key", "message_ttl_routingKey");
		channel.queueDeclare("delay_queue", true, false, false, arguments);

		// 声明队列
		channel.queueDeclare(queue_name, true, false, false, null);
		// 绑定路由
		channel.queueBind(queue_name, "amq.direct", "message_ttl_routingKey");

		QueueingConsumer consumer = new QueueingConsumer(channel);
		// 指定消费队列
		channel.basicConsume(queue_name, true, consumer);
		while (true) {
			// nextDelivery是一个阻塞方法(内部实现其实是阻塞队列的take方法)
			QueueingConsumer.Delivery delivery = consumer.nextDelivery();
			String message = new String(delivery.getBody());
			System.out.println("received message:" + message + ",date:" + System.currentTimeMillis());
		}
	}

}

查看资料:

http://www.rabbitmq.com/ttl.html 
http://www.rabbitmq.com/dlx.html 
http://www.rabbitmq.com/maxlength.html
https://www.cloudamqp.com/docs/delayed-messages.html 

作者:tianwei7518 发表于2016/12/11 20:13:04 原文链接
阅读:31 评论:0 查看评论

深度探索c++对象模型之member function的具现行为

$
0
0

      对于template 的支持,最困难的就是template function的instatiation【具现】。截至此书问世,大家的编译器提供了两种策略:一个是编译时期策略——模板程序代码必须在program text file【程序文本文件】中备好可用;另一个是编译时起策略,有一些meta-compilation【元编译】工具可以导引编译器的具现行为。

      对于编译器的设计者们,必须要能回答出下面三个问题:

1):编译器如何找出函数的定义?

      答案之一是包含template program text file【模板程序文本文件】,就好像它是个头文件一样,borland编译器就是遵循的这种策略。还有一个方法是要求一种文件命名规则,例如,我们可以规定:在Point.h中发现的函数声明,其相应的template program text file一定要放在文件Point.C或Point.cpp中,以此类推,cfront就是遵循的这种策略。edison design group编译器对这两种策略都支持。

2):编译器如何能够只具现出程序需要用到的函数?

      嗯,这个难度系数很高,所以目前的很多编译器是根本忽略这个问题:把一个已经具现出来的template class的所有member function都具现出来。borland就是这么做的——虽然它也提供#pragmas让用户可以压制一些特定的实体。但还有一种方法——仿真链接操作,检测看一下哪个member function是程序运行时真正需要的,然后只为它或它们产生实体,cfront就是这么做的。edison design group对这两种方法都支持。

3):编译器如何阻止member definitions在多个.o文件中都被具现出来呢?

      有一个办法是产生多个实体,然后从连接器中提供支持,只留下其中一个实体,其余的都忽略。另一个办法是由用户自己来导引“仿真链接阶段”的具现策略,决定哪些instances【实体】才是我们所需求的。

      但不管是编译时期还是链接时期的instantiation【具现】策略,都有一个共同的弱点:当template实体被产生出来时,有时候会大量增加程序的编译时间。显然,这是模板函数第一次具现时的必要条件。然而当这些模板函数被非必要的再次具现,或者当“决定那些模板函数是否需要被再次具现”所花费的代价太大时,编译器的表现令人失望!

      c++支持模板的原始意图是一个由用户导引的use-directed automatic instantiation mechanism【自动具现机制】:既不需要用户的介入,也不需要相同文件有多次的具现行为。但这已经被证明是一个很难的任务,随着cfront3.0版所附加的原始具现工具,提供了一个由用户执行的use-driven automatic instantiation mechanism【自动具现机制】,但它实在太复杂了,即使是一个久经世故的人也没法一下子了解。

      Edison Design Group开发出的一套第二代directed-instantiation【直接具现】机制,非常接近于template facility【模板工具】的原始含义。它的主要过程如下:

1):一个程序的代码被编译时,最初并不会产生任何模板具现体,相关信息被产生于object files之中。

2):当object files被链接到一块时,会有一个prelinker程序被执行,该程序会检查object files,寻找模板实体的相互参考以及对应的定义。

3):对于每一个参考到的模板实体而该实体却没有定义的情况,prelinker会将该文件看作于另一个文件【在这个文件中,实体已经定义】的同类。用这种方法,就可以把必要的程序具现操作指定给特定的文件。这些都会注册在prelinker所产生的.ii文件【放在磁盘目录ii_file】中。

4):prelinker重新执行编译器,重新编译每一个“.ii文件曾被改变过”的文件。这个过程不断重复,直到所有必要的具现操作都已经被完成。

5):所有的object files都被链接成一个可执行问价。

      上面这个directed-instantiation【直接具现】机制的主要成本在于,程序被第一次编译时的.ii文件设定时间;次要成本则是必须针对每一个compile afterwords【后续编译】执行prelinker,以确保所有被参考到的模板们都存在定义。在最初的设定以及成功的第一次链接后,重新编译操作包含以下程序:

1):对于每一个将被重新编译的program text file,编译器会检查其对应的.ii文件。

2):如果这些对应的.ii文件能列出一组要被具现的模板们,那么这些模板将在此次编译时被具现。

3):prelinker必须执行起来,确保所有参考到的模板们都已经被定义好。

      不幸的是,所有的机制都存在一定bugs。Edison Design Group的编译器使用了一个由cfront2.0引入的算法,在大部分情况下,针对程序的每一个class自动产生虚函数表的单一实体,例如下面的class声明:

class PrimitiveObject:public Geometry
{
public:
  virtual ~PrimitiveObject();
  virtual draw();
   ...
}
如果它被包含在几十个源码文件中,编译器如何确保只有一个虚函数表被产生出来呢?倒是产生几十个虚函数表比较容易。

      Andy Koenig【应该是一个c++编译器设计值】用下面的方法解决上面的问题:每一个虚函数的地址都被放置在active classes的虚函数表中,如果取得了函数地址,则意味着虚函数的定义肯定出现在程序的某个地点,否则程序就无法链接成功。此外,该函数只能有一个实体,否则也是链接不成功。那么,就把虚函数表放在定义了该class的第一个non-inline、nonpure virtual function的文件中吧。以我们上面的例子而言,编译器会把虚函数表放在存储着虚析构器的文件之中。

      然而在template之中,这种单一定义并不一定为真,在template所支持的将模块中的每一样东西都编译的模型下,不只是多个定义可能被产生,而且链接器也放任让多个定义同时出现,它只要选择其中一个忽略掉其它的就可以了。

      Edison Design Group机制会做什么事呢?考虑下面这个library函数:

void foo(const Point<float>* ptr)
{
  ptr->virtual_func();
}
其中的虚函数调用会被转换成类似这样的东西:

//c++伪码
//ptr->virtual_func();的转换后形式
(*ptr->_vtbl_Point<float>[2])(ptr);
这会具现出Point类的一个float实体以及它的虚函数func()。由于每一个虚函数的地址被放在虚函数表中,如果虚函数表被产生出来,那么里面的每一个虚函数也必须被具现出来,就像C++ standard所说:如果一个虚拟函数被具现出来,那么它的具现点应紧跟在它所属的class具现点之后。

      然而,如果编译器遵循cfront的虚函数表实现机制,那么在“Point的float实体有一个virtual destructor定义被具现出来”之前,这个虚函数表不会被产生,除非在这一点上,并没有明确使用virtual destructor以担保其具现行为。

      Edison Design Group的automatic template【自动模板】机制并不明确它自己的编译器对于一个non-inline、nonpure virtual function的隐晦使用,所以并没有把它标示于.ii文件中,所以,链接器反而回头抱怨下面这个符号并没有出现:

_vtbl_Point<float>
并拒绝产生一个可执行文件,automatic instantiation在此失效!程序员必须明确的强制将destructor具现出来,目前的编译系统是以(#pragmatic)指令来支持此要求。然而C++ standard也已经扩充了对template的支持,允许程序员明确地要求在一个文件中将整个模板类具现出来:

template class Point3d<float>;
或者针对一个模板类的个别成员函数:

template class Point3d<float>::X() const;
再或者是针对个别模板函数:

template class Point3d<float> operator+(const Point3d<float>&, const Point3d<float>&);
      在实现层面上,template instantiation【模板具现】似乎拒绝全面自动化。甚至虽然每一个工作都做对了,产生出来的object files的重新编译成本还是太高。所以用手动方式先在个别的object module【对象模块】中完成pre-instantiation【预先具现】,虽然沉闷,但也是唯一有效率的办法。





作者:wenpinglaoyao 发表于2016/12/11 20:15:06 原文链接
阅读:23 评论:0 查看评论

Android打包的那些事

$
0
0

转载来源http://www.cnblogs.com/qianxudetianxia/p/4948499.html

使用gradle打包apk已经成为当前主流趋势,我也在这个过程中经历了各种需求,并不断结合gradle新的支持,一一改进。在此,把这些相关的东西记录,做一总结。

1. 替换AndroidManifest中的占位符

我想把其中的${app_label}替换为@string/app_name

android{
    defaultConfig{
        manifestPlaceholders = [app_label:"@string/app_name"]
    }
}

如果只想替换debug版本:

android{
    buildTypes {
        debug {
              manifestPlaceholders = [app_label:"@string/app_name_debug"]
        }
        release {
        }
    }
}

更多的需求是替换渠道编号:

android{
    productFlavors {
        // 把dev产品型号的apk的AndroidManifest中的channel替换dev
        "dev"{
            manifestPlaceholders = [channel:"dev"]
        }
    }
}

2. 独立配置签名信息

对于签名相关的信息,直接写在gradle当然不好,特别是一些开源项目,可以添加到local.properties:

RELEASE_KEY_PASSWORD=xxxx
RELEASE_KEY_ALIAS=xxx
RELEASE_STORE_PASSWORD=xxx
RELEASE_STORE_FILE=../.keystore/xxx.jks

然后在build.gradle中引用即可:

android {
    signingConfigs {
        release {
            storeFile file(RELEASE_STORE_FILE)
            storePassword RELEASE_STORE_PASSWORD
            keyAlias RELEASE_KEY_ALIAS
            keyPassword RELEASE_KEY_PASSWORD
        }
    }
}

为了更省事,小规模团队可考虑添加到gradle.properties或者另外再建立一个单独的properties,然后提交到版本库中,方便大家共享。

3. 多渠道打包

多渠道打包的关键之处在于,定义不同的product flavor, 并把AndroiManifest中的channel渠道编号替换为对应的flavor标识:

android {
    productFlavors {
        dev{
            manifestPlaceholders = [channel:"dev"]
        }
        official{
            manifestPlaceholders = [channel:"official"]
        }
        // ... ...
        wandoujia{
            manifestPlaceholders = [channel:"wandoujia"]
        }
        xiaomi{
            manifestPlaceholders = [channel:"xiaomi"]
        }
        "360"{
            manifestPlaceholders = [channel:"360"]
        }
}

注意一点,这里的flavor名如果是数字开头,必须用引号引起来。
构建一下,就能生成一系列的Build Variant了:

devDebug
devRelease
officialDebug
officialRelease
wandoujiaDebug
wandoujiaRelease
xiaomiDebug
xiaomiRelease
360Debug
360Release

其中debug, release是gradle默认自带的两个build type, 下一节还会继续说明。
选择一个,就能编译出对应渠道的apk了。

4. 自定义Build Type

前面说到默认的build type有两种debug和release,区别如下:

// release版本生成的BuildConfig特性信息
public final class BuildConfig {
  public static final boolean DEBUG = false;
  public static final String BUILD_TYPE = "release";
}
// debug版本生成的BuildConfig特性信息
public final class BuildConfig {
  public static final boolean DEBUG = true;
  public static final String BUILD_TYPE = "debug";
}

现在有一种需求,增加一种build type,介于debug和release之间,就是和release版本一样,但是要保留debug状态(如果做过rom开发的话,类似于user debug版本),我们称为preview版本吧。
其实很简单:

android {
    signingConfigs {
        debug {
            storeFile file(RELEASE_STORE_FILE)
            storePassword RELEASE_STORE_PASSWORD
            keyAlias RELEASE_KEY_ALIAS
            keyPassword RELEASE_KEY_PASSWORD
        }
        preview {
            storeFile file(RELEASE_STORE_FILE)
            storePassword RELEASE_STORE_PASSWORD
            keyAlias RELEASE_KEY_ALIAS
            keyPassword RELEASE_KEY_PASSWORD
        }
        release {
            storeFile file(RELEASE_STORE_FILE)
            storePassword RELEASE_STORE_PASSWORD
            keyAlias RELEASE_KEY_ALIAS
            keyPassword RELEASE_KEY_PASSWORD
        }
    }

    buildTypes {
        debug {
            manifestPlaceholders = [app_label:"@string/app_name_debug"]
        }
        release {
            manifestPlaceholders = [app_label:"@string/app_name"]
        }
        preview{
            manifestPlaceholders = [app_label:"@string/app_name_preview"]
        }
    }
}

另外,build type还有一个好处,如果想要一次性生成所有的preview版本,执行assemblePreview即可,debug和releae版本同理。

5. build type中的定制参数

上面我们在不同的build type替换${app_label}为不同的字符串,这样安装到手机上就能明显的区分出不同build type的版本。
除此之外,可能还可以配置一些参数,我这里列几个我在工作中用到的:

android {
        debug {
            manifestPlaceholders = [app_label:"@string/app_name_debug"]
            applicationIdSuffix ".debug"
            minifyEnabled false
            signingConfig signingConfigs.debug
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        release {
            manifestPlaceholders = [app_label:"@string/app_name"]
            minifyEnabled true
            shrinkResources true
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        preview{
            manifestPlaceholders = [app_label:"@string/app_name_preview"]
            applicationIdSuffix ".preview"
            debuggable true // 保留debug信息
            minifyEnabled true
            shrinkResources true
            signingConfig signingConfigs.preview
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

这些都用的太多了,稍微解释一下:

// minifyEnabled 混淆处理
// shrinkResources 去除无用资源
// signingConfig 签名
// proguardFiles 混淆配置
// applicationIdSuffix 增加APP ID的后缀
// debuggable 是否保留调试信息
// ... ...

6. 多工程全局配置

随着产品渠道的铺开,往往一套代码需要支持多个产品形态,这就需要抽象出主要代码到一个Library,然后基于Library扩展几个App Module。
相信每个module的build.gradle都会有这个代码:

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.1"

    defaultConfig {
        minSdkVersion 10
        targetSdkVersion 22
        versionCode 34
        versionName "v2.6.1"
    }
}

当升级sdk、build tool、target sdk等,几个module都要更改,非常的麻烦。最重要的是,很容易忘记,最终导致app module之间的差异不统一,也不可控。
强大的gradle插件在1.1.0支持全局变量设定,一举解决了这个问题。
先在project的根目录下的build.gradle定义ext全局变量:

ext {
    compileSdkVersion = 22
    buildToolsVersion = "23.0.1"
    minSdkVersion = 10
    targetSdkVersion = 22
    versionCode = 34
    versionName = "v2.6.1"
}

然后在各module的build.gradle中引用如下:

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion

    defaultConfig {
        applicationId "com.xxx.xxx"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName
    }
}

然后每次修改project级别的build.gradle即可实现全局统一配置。

7. 自定义导出的APK名称

默认android studio生成的apk名称为app-debug.apk或者app-release.apk,当有多个渠道的时候,需要同时编出50个渠道包的时候,就麻烦了,不知道谁是谁了。
这个时候,就需要自定义导出的APK名称了,不同的渠道编出的APK的文件名应该是不一样的。

android {
    // rename the apk with the version name
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.outputFile = new File(
                    output.outputFile.parent,
                    "ganchai-${variant.buildType.name}-${variant.versionName}-${variant.productFlavors[0].name}.apk".toLowerCase())
        }
    }
}

当apk太多时,如果能把apk按debug,release,preview分一下类就更好了(事实上,对于我这样经常发版的人,一编往往就要编四五十个版本的人,debug和release版本全混在一起没法看,必须分类),简单:

android {
    // rename the apk with the version name
    // add output file sub folder by build type
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            output.outputFile = new File(
                    output.outputFile.parent + "/${variant.buildType.name}",
                    "ganchai-${variant.buildType.name}-${variant.versionName}-${variant.productFlavors[0].name}.apk".toLowerCase())
        }
    }
}

现在生成了类似于ganchai-dev-preview-v2.4.0.0.apk这样格式的包了,preview的包自然就放在preview的文件夹下,清晰明了。

8. 混淆技巧

混淆能让反编译的代码可读性变的很差,而且还能显著的减少APK包的大小。

1). 第一个技巧

相信很多朋友对混淆都觉得麻烦,甚至说,非常乱。因为添加混淆规则需要查询官方说明文档,甚至有的官方文档还没说明。当你引用了太多库后,添加混淆规则将使一场噩梦。
这里介绍一个技巧,不用查官方文档,不用逐个库考虑添加规则。
首先,除了默认的混淆配置(android-sdk/tools/proguard/proguard-android.txt), 自己的代码肯定是要自己配置的:

## 位于module下的proguard-rules.pro
#####################################
######### 主程序不能混淆的代码 #########
#####################################

-dontwarn xxx.model.**
-keep class xxx.model.** { *; }

## 等等,自己的代码自己清楚

#####################################
########### 不优化泛型和反射 ##########
#####################################

-keepattributes Signature

接下来是麻烦的第三方库,一般来说,如果是极光推的话,它的包名是cn.jpush, 添加如下代码即可:

-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }

其他的第三库也是如此,一个一个添加,太累!其实可以用第三方反编译工具(比如jadx:https://github.com/skylot/jadx ),打开apk后,一眼就能看到引用的所有第三方库的包名,把所有不想混淆或者不确定能不能混淆的,直接都添加又有何不可:

#####################################
######### 第三方库或者jar包 ###########
#####################################

-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }

-dontwarn com.squareup.**
-keep class com.squareup.** { *; }

-dontwarn com.octo.**
-keep class com.octo.** { *; }

-dontwarn de.**
-keep class de.** { *; }

-dontwarn javax.**
-keep class javax.** { *; }

-dontwarn org.**
-keep class org.** { *; }

-dontwarn u.aly.**
-keep class u.aly.** { *; }

-dontwarn uk.**
-keep class uk.** { *; }

-dontwarn com.baidu.**
-keep class com.baidu.** { *; }

-dontwarn com.facebook.**
-keep class com.facebook.** { *; }

-dontwarn com.google.**
-keep class com.google.** { *; }

## ... ...

2). 第二个技巧

一般release版本混淆之后,像友盟这样的统计系统如果有崩溃异常,会记录如下:

java.lang.NullPointerException: java.lang.NullPointerException
    at com.xxx.TabMessageFragment$7.run(Unknown Source)

这个Unknown Source是很要命的,排除错误无法定位到具体行了,大大降低调试效率。
当然,友盟支持上传Mapping文件,可帮助定位,mapping文件的位置在:

project > module
        > build > outputs > {flavor name} > {build type} > mapping.txt

如果版本一多,mapping.txt每次都要重新生成,还要上传,终归还是麻烦。
其实,在proguard-rules.pro中添加如下代码即可:

-keepattributes SourceFile,LineNumberTable

当然apk包会大那么一点点(我这里6M的包,大个200k吧),但是再也不用mapping.txt也能定位到行了,为了这种解脱,这个代价我个人觉得是值的,而且超值!

9. 动态设置一些额外信息

假如想把当前的编译时间、编译的机器、最新的commit版本添加到apk,而这些信息又不好写在代码里,强大的gradle给了我创造可能的自信:

android {
    defaultConfig {
        resValue "string", "build_time", buildTime()
        resValue "string", "build_host", hostName()
        resValue "string", "build_revision", revision()
    }
}

def buildTime() {
    return new Date().format("yyyy-MM-dd HH:mm:ss")
}
def hostName() {
    return System.getProperty("user.name") + "@" + InetAddress.localHost.hostName
}
def revision() {
    def code = new ByteArrayOutputStream()
    exec {
        commandLine 'git', 'rev-parse', '--short', 'HEAD'
        standardOutput = code
    }
    return code.toString()
}

上述代码实现了动态的添加了3个字符串资源: build_time、build_host、build_revision, 然后在其他地方可像如引用字符串一样使用如下:

// 在Activity里调用
getString(R.string.build_time)  // 输出2015-11-07 17:01
getString(R.string.build_host)  // 输出jay@deepin,这是我的电脑的用户名和PC名
getString(R.string.build_revision) // 输出3dd5823, 这是最后一次commit的sha值

这个地方,如何从命令行读取返回结果,很有意思。
其实这段代码来自我学习VLC源码时偶然看到,深受启发,不敢独享,特摘抄在此。
vlc源码及编译地址:https://wiki.videolan.org/AndroidCompile, 有兴趣可以过去一观。

10. 给自己留个"后门": 点七下

为了调试方便,我们往往会在debug版本留一个显示我们想看的界面(记得之前微博的一个iOS版本就泄露了一个调试界面),如何进入到一个界面,我们可以仿照android开发者选项的方式,点七下才显示,我们来实现一个:

private int clickCount = 0;
private long clickTime = 0;

sevenClickView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {

        if (clickTime == 0) {
            clickTime = System.currentTimeMillis();
        }
        if (System.currentTimeMillis() - clickTime > 500) {
            clickCount = 0;
        } else {
            clickCount++;
        }
        clickTime = System.currentTimeMillis();

        if (clickCount > 6) {
            // 点七下条件达到,跳到debug界面
        }
    }
});

release版本肯定是不能暴露这个界面的,也不能让人用am在命令行调起,如何防止呢,可以在release版本把这个debug界面的exported设为false。

11. 自动化构建

如何使用jenkins打包android和ios,并上传到蒲公英平台,这个可以参考我的另外一篇文章专门介绍: 《使用jenkins自动化构建android和ios应用》,不过,这篇文章还没写完,实际上在公司里已经一直在用了,哪天心情好了总会写完的,这里不再赘述。

12. 小结

android打包因为groovy语言的强大,变的强大的同时必然也变的复杂,今天把我经历的这些门道拿出来说道一下,做一个小小的总结,后续有更新我还会添加。

同步首发:http://www.jayfeng.com/2015/11/07/Android%E6%89%93%E5%8C%85%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B/


作者:shanshan_blog 发表于2016/12/12 19:55:56 原文链接
阅读:15 评论:0 查看评论

websocket 心跳连接

$
0
0

websocket连接时,如果长时间没有进行数据的通讯就会自动断开连接。为了不让其断开就在要断开的时候自动发送数据进行通讯,就产生了心跳连接的效果。

具体的操作就是在客户端建立连接的时候开启发送心跳信息的线程,之后再每次收到信息之后就线程重启。服务端在处理数据的时候多处理一下心跳信息,将其发给连接的用户,从而实现心跳通讯。

服务端代码

@ServerEndpoint("/video")
public class AcgistVideo {
    //连接超时
    public static final long MAX_TIME_OUT = 2 * 60 * 1000;
    //房间和房间对应的用户列表
    public static Map<String, ArrayList<String>> room_user = Collections.synchronizedMap(new HashMap<String, ArrayList<String>>());
    //用户的sid和用户的session一一对应
    public static Map<String, Session> user_session = Collections.synchronizedMap(new HashMap<String, Session>());
    //用户的sid和房间一一对应
    public static Map<String, String> userInRoom = Collections.synchronizedMap(new HashMap<String, String>());
    public static Logger log = LoggerFactory.getLogger(AcgistVideo.class);

    /**
     * 打开websocket
     *
     * @param session websocket的session
     * @param uid     打开用户的UID
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("uid") String uid) throws IOException {
        log.info("--------------------onOpen--------------------");
        session.setMaxIdleTimeout(MAX_TIME_OUT);
    }
    /**
     * 收到消息
     *
     * @param message 消息内容
     * @param session 发送消息的session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("接收到来自客户端--" + session.getId() + "--的信息:" + message);
        JSONObject json = JSONObject.fromObject(message);

        if (json.get("type").equals("create")) {
            ArrayList arrayList = new ArrayList();
            arrayList.add(session.getId());

            String roomId = createRoom();
            room_user.put(roomId, arrayList);
            userInRoom.put(session.getId(), roomId);
            user_session.put(session.getId(), session);

            JSONObject jsonstr = new JSONObject();
            jsonstr.put("type", "create");
            jsonstr.put("room", roomId);
            jsonstr.put("sid", session.getId());
            jsonstr.put("count", getUserNumberByRoom(roomId));
            sendMessage(jsonstr.toString(), session);

        } else if (json.get("type").equals("apply")) {
            String roomId = "";
            if (json.has("room")) {
                roomId = json.getString("room");
            }

            if (!("".equals(roomId)) && null != room_user.get(roomId)) {
                ArrayList<String> arrayList = room_user.get(roomId);
                arrayList.add(session.getId());
                room_user.put(roomId, arrayList);
                userInRoom.put(session.getId(), roomId);
                user_session.put(session.getId(), session);

                JSONObject jsonstr = new JSONObject();
                jsonstr.put("type", "apply");
                jsonstr.put("room", roomId);
                jsonstr.put("sid", session.getId());
                jsonstr.put("count", getUserNumberByRoom(roomId));

                sendMessageToUserInSameRoom(session.getId(), jsonstr, false);
            } else {
                ArrayList arrayList = new ArrayList();
                arrayList.add(session.getId());

                roomId = createRoom();
                room_user.put(roomId, arrayList);
                userInRoom.put(session.getId(), roomId);
                user_session.put(session.getId(), session);

                JSONObject jsonstr = new JSONObject();
                jsonstr.put("type", "create");
                jsonstr.put("room", roomId);
                jsonstr.put("sid", session.getId());
                jsonstr.put("count", getUserNumberByRoom(roomId));
                sendMessage(jsonstr.toString(), session);
            }
        } else if (json.get("type").equals("receipt")) {
            Session oession = user_session.get(json.get("sid"));
            JSONObject jsonstr = new JSONObject();
            jsonstr.put("type", "receipt");
            jsonstr.put("room", json.get("room"));
            jsonstr.put("sid", session.getId());
            jsonstr.put("count", getUserNumberByRoom(userInRoom.get(session.getId())));
            sendMessage(jsonstr.toString(), oession);
        } else if (json.get("type").equals("link")) {
            Session oession = user_session.get(json.get("sid"));
            JSONObject jsonstr = new JSONObject();
            jsonstr.put("type", "link");
            jsonstr.put("room", json.get("room"));
            jsonstr.put("sid", session.getId());
            jsonstr.put("count", getUserNumberByRoom(userInRoom.get(session.getId())));
            sendMessage(jsonstr.toString(), oession);
        } else if (json.get("type").equals("offer")) {
            Session oession = user_session.get(json.get("sid"));
            JSONObject jsonstr = new JSONObject();
            jsonstr.put("type", "offer");
            jsonstr.put("room", json.get("room"));
            jsonstr.put("sid", session.getId());
            jsonstr.put("count", getUserNumberByRoom(userInRoom.get(session.getId())));
            jsonstr.put("offer", json.get("offer"));
            sendMessage(jsonstr.toString(), oession);
        } else if (json.get("type").equals("answer")) {
            Session oession = user_session.get(json.get("sid"));
            JSONObject jsonstr = new JSONObject();
            jsonstr.put("type", "answer");
            jsonstr.put("room", json.get("room"));
            jsonstr.put("sid", session.getId());
            jsonstr.put("count", getUserNumberByRoom(userInRoom.get(session.getId())));
            jsonstr.put("answer", json.get("answer"));
            sendMessage(jsonstr.toString(), oession);
        } else if (json.get("type").equals("candidate")) {
            Session oession = user_session.get(json.get("sid"));
            JSONObject jsonstr = new JSONObject();
            jsonstr.put("type", "candidate");
            jsonstr.put("room", json.get("room"));
            jsonstr.put("sid", session.getId());
            jsonstr.put("count", getUserNumberByRoom(userInRoom.get(session.getId())));
            jsonstr.put("candidate", json.get("candidate"));
            sendMessage(jsonstr.toString(), oession);
        } else if (json.get("type").equals("bye")) {
            remove(session);
        } else if (json.get("type").equals("beat")) {
            JSONObject jsonstr = new JSONObject();
            jsonstr.put("type", "beat");
            jsonstr.put("msg", json.get("msg"));
            jsonstr.put("sid", session.getId());
            sendMessageToUserInSameRoom(session.getId(), jsonstr, false);
        }
    }
    /**
     * websocket错误
     *
     * @param e
     * @param session
     */
    @OnError
    public void onError(Throwable e, Session session) {
        log.info("--------------------onError--------------------");
        log.info("session ID: --" + session.getId() + " --离开了");
        log.error("--", e);
        log.info("----------------------------------------");
    }
    /**
     * websocket关闭
     *
     * @param session 关闭的session
     */
    @OnClose
    public void onClose(Session session) {
        log.info("--------------------onClose--------------------");
        remove(session);
    }
    /**
     *    * 随机生成UUID
     *    * @return
     *    
     */
    public static synchronized String getUUID() {
        log.info("--------------------getUUID--------------------");
        UUID uuid = UUID.randomUUID();
        String str = uuid.toString();
        String uuidStr = str.replace("-", "");
        return uuidStr;
    }
    /**
     * 通过房间号获取房间中的用户数量
     *
     * @param roomId 房间号
     * @return 房间中的用户数量 如果只为-1则房间不存在
     */
    public int getUserNumberByRoom(String roomId) {
        log.info("--------------------getUserNumberByRoom--------------------");
        if (room_user.containsKey("roomId")) {
            ArrayList<String> userList = room_user.get(roomId);
            return userList.size();
        } else {
            return -1;
        }
    }
    /**
     * 发送信息
     *
     * @param message 发送内容
     * @param session 用户session
     */
    public void sendMessage(String message, Session session) {
        log.info("--------------------sendMessage--------------------");
        try {
            log.info("发送消息到客户端--" + session.getId() + "--信息为:" + message);
            synchronized (session) {
                if (session.isOpen()) {
                    session.getBasicRemote().sendText(message);
                }
            }
        } catch (Exception e) {
            log.error("send message exception", e);
        }
    }
    /**
     * 创建房间
     *
     * @return 房间ID
     */
    public String createRoom() {
        log.info("--------------------createRoom--------------------");
        String roomId = getUUID();
        return roomId;
    }
    /**
     * 移除聊天用户
     *
     * @param session 移除的session
     */
    private void remove(Session session) {
        log.info("--------------------remove--------------------");

        String roomId = userInRoom.get(session.getId());
        ArrayList<String> arrayList = room_user.get(roomId);

        if (arrayList.contains(session.getId())) {
            log.info("移除SID:" + session.getId());
            arrayList.remove(session.getId());
            if (arrayList.size() < 1) {
                log.info("移除房间:" + roomId);
                room_user.remove(roomId);
            } else {
                room_user.put(roomId, arrayList);

                //用户退出的时候给房间中的每一位用户发一条信息
                JSONObject jsonstr = new JSONObject();
                jsonstr.put("type", "bye");
                jsonstr.put("sid", session.getId());
                sendMessageToUserInSameRoom(session.getId(), jsonstr, false);
            }
            userInRoom.remove(session.getId());
            user_session.remove(session.getId());
        }
    }
    /**
     * 给用户所在房间中的每一位用户发一条信息
     *
     * @param selfId     用户Id
     * @param jsonstr    需要发的信息
     * @param isSendSelf 是否发给自己
     */
    private void sendMessageToUserInSameRoom(String selfId, JSONObject jsonstr, boolean isSendSelf) {
        String roomId = userInRoom.get(selfId);
        ArrayList<String> arrayList = room_user.get(roomId);

        for (int i = 0; i < arrayList.size(); i++) {
            if (arrayList.get(i).equals(selfId)) {
                if (isSendSelf) {
                    sendMessage(jsonstr.toString(), user_session.get(arrayList.get(i)));
                }
            } else {
                sendMessage(jsonstr.toString(), user_session.get(arrayList.get(i)));
            }
        }
    }
}
客户端代码

var MyWebSocket = function (obj) {
    var _this = this;
    this.timeout = 30 * 1000;
    this.timeoutObj = null;
    this.socket = null;
    this.initSocket = function () {
        this.socket = new WebSocket(GLOBAL.getConstant("WEB_SOCKET_URL"));

        this.socket.onopen = function () {
            console.log("websocket-----> Opened");
            _this.start();
        };

        this.socket.onmessage = this.socketMessage;

        this.socket.onclose = function () {
            console.log("websocket-----> Closed");
            obj.closeWebSocket();
        };

        this.socket.onerror = function () {
            console.log("websocket-----> Error:" + JSON.stringify(event));
        };

        console.log("-------websocket初始化完成执行回调函数-------");
        obj.initWebRtc(obj.entitys);
    }
    //接收到服务器的回复
    this.socketMessage = function (message) {
        console.log("websocket-----> Message:" + message.data);
        _this.reset();
        obj.SocketCallBack(message.data);
    }
    //向服务器发送信息
    this.sendMessage = function (message, callback) {
        this.waitForConnection(function () {
            var msgJson = JSON.stringify(message);
            this.socket.send(msgJson);
            console.log("websocket-----> SendMessage:" + msgJson);
            if (typeof callback !== 'undefined') {
                callback();
            }
        }, 1000);
    };
    this.waitForConnection = function (callback, interval) {
        if (this.socket.readyState === 1) {
            callback();
        } else {
            var that = this;
            setTimeout(function () {
                that.waitForConnection(callback, interval);
            }, interval);
        }
    };
    this.onClose = function () {
        this.socket.close();
    }
    this.start = function () {
        _this.timeoutObj = setTimeout(function () {
            var heartbeat = {
                type: "beat",
                msg: "HeartBeat"
            };
            _this.socket.send(JSON.stringify(heartbeat));
        }, _this.timeout)
    };
    this.reset = function () {
        clearTimeout(this.timeoutObj);
        _this.start();
    };

    this.initSocket();

    return this;
}



作者:jiangyou4 发表于2016/12/12 19:58:37 原文链接
阅读:33 评论:0 查看评论

SpringMVC-基本配置

$
0
0

本文主要讲解Spring的基本知识以及基本的配置步骤。

环境:

Eclipse neon

Tomcat7.0

jdk7

Spring3.2

相关源码下载:点击下载

SpringMVC架构


SpringMVC主要流程以及步骤如下所示:

1、  用户发起request请求至控制器(Controller)

控制接收用户请求的数据,委托给模型进行处理

2、  控制器通过模型(Model)处理数据并得到处理结果

模型通常是指业务逻辑

3、  模型处理结果返回给控制器

4、  控制器将模型数据在视图(View)中展示

web中模型无法将数据直接在视图上显示,需要通过控制器完成。如果在C/S应用中模型是可以将数据在视图中展示的。

5、  控制器将视图response响应给用户

通过视图展示给用户要的数据或处理结果。


架构图


流程

根据上图,分析执行流程如下:

1、  用户发送请求至前端控制器DispatcherServlet

2、  DispatcherServlet收到请求调用HandlerMapping处理器映射器。

3、  处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。

4、  DispatcherServlet通过HandlerAdapter处理器适配器调用处理器

5、  执行处理器(Controller,也叫后端控制器)。

6、  Controller执行完成返回ModelAndView

7、  HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet

8、  DispatcherServlet将ModelAndView传给ViewReslover视图解析器

9、  ViewReslover解析后返回具体View

10、DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。

11、DispatcherServlet响应用户


非注解方式配置

通过上面的分析,接下来我们通过实例进行配置说明。

需求:在Jsp页面显示数据(通过List装载传送到页面进行显示)

1、新建工程

新建web工程,并且导入相应jar包(具体见源码包lib目录下)

2、配置前端控制器

在web.xml中做相应配置,如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>SpringMVC-1</display-name>
 
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <!-- 指定springmvc配置的加载位置,如果不指定则默认加
		载WEB-INF/[DispatcherServlet 的Servlet 名字]-servlet.xml
     -->
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!-- tomcat随服务启动 -->
    <load-on-startup>1</load-on-startup>
  </servlet>
  
  <!-- <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/classes/spring/applicationContext-*.xml</param-value>
  </context-param>
  
  <listener>
  	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>	
  </listener> -->
  
  
  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>*.action</url-pattern>
  </servlet-mapping>
  
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

load-on-startup:表示servlet随服务启动;

url-pattern:*.action的请交给DispatcherServlet处理。

contextConfigLocation:指定springmvc配置的加载位置,如果不指定则默认加

WEB-INF/[DispatcherServletServlet 名字]-servlet.xml


Servlet拦截方式

(1)拦截固定后缀的url,比如设置为 *.do、*.action, 例如:/user/add.action

此方法最简单,不会导致静态资源(jpg,js,css)被拦截。

(2)拦截所有,设置为/,例如:/user/add  /user/add.action

此方法可以实现REST风格的url,很多互联网类型的应用使用这种方式。

但是此方法会导致静态文件(jpg,js,css)被拦截后不能正常显示。需要特殊处理。

(3)拦截所有,设置为/*,此设置方法错误,因为请求到Action,当action转到jsp时再次被拦截,提示不能根据jsp路径mapping成功。

3、加载SpringMVC配置文件

<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>

完成上述配置后,在文件夹下建立Springmvc.xml


4、配置处理器适配器

在Springmvc.xml文件下做出如下配置:

<bean		class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/> 

SimpleControllerHandlerAdapter:即简单控制器处理适配器,所有实现了org.springframework.web.servlet.mvc.Controller 接口的Bean作为

Springmvc的后端控制器。


5、开发处理器

创建java文件,实现controller接口如下:

package com.sw.ssm.controller;

import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import com.sw.ssm.po.Items;

/*
 *@Author swxctx
 *@time 2016年12月9日
 *@Explain:实现controller接口的处理器
 */
public class ItemsController1 implements Controller{

	@Override
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		//调用servlce查找数据库,查询商品列表
		List<Items> list = new ArrayList<Items>();
		//填充订单数据
		Items items_1 = new Items();
		items_1.setName("Lenovo");
		items_1.setPrice(6000f);
		items_1.setDetail("ThinkPad T430");
		
		Items items_2 = new Items();
		items_2.setName("iphone");
		items_2.setPrice(5000f);
		items_2.setDetail("iphone6");
		
		list.add(items_1);
		list.add(items_2);
		
		//返回ModelAndView
		ModelAndView modelAndView = new ModelAndView();
		//作用类似于request的setAttribute(在jsp页面中通过itemsList取出相关数据)
		modelAndView.addObject("itemsList", list);
		
		//指定视图
		modelAndView.setViewName("/items/itemsList");
		return modelAndView;
	}

}

org.springframework.web.servlet.mvc.Controller:处理器必须实现Controller 接口。

ModelAndView:包含了模型数据及逻辑视图名

6、配置处理器映射器

<!-- 处理器映射器 -->
	<!-- 根据bean的name进行查找Handler 将action的url配置在bean的name中 -->
	<bean
		class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />

BeanNameUrlHandlerMapping:表示将定义的Bean名字作为请求的url,需要将编写的controller在spring容器中进行配置,且指定bean的name为请求的url,且必须以.action结尾。

7、处理器配置

<!-- 2 配置加载Handler(实现controller接口) -->
	<bean id="itemsController1" name="/queryItems_text.action" class="com.sw.ssm.controller.ItemsController1"/>

如上所示,url即为/queryItems.action


8、配置视图解析器

<!-- 4 视图解析器 -->
	<!-- 解析jsp视图,默认使用jstl -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<!-- 配置jsp路径的前缀 -->
		<property name="prefix" value="/WEB-INF/jsp/"/>
		<!-- 配置jsp路径的后缀 -->
		<property name="suffix" value=".jsp"/>
	</bean>

InternalResourceViewResolver:支持JSP视图解析

viewClass:JstlView表示JSP模板页面需要使用JSTL标签库,所以classpath中必须包含jstl的相关jar 包;

prefix 和suffix:查找视图页面的前缀和后缀,最终视图的址为:

前缀+逻辑视图名+后缀,逻辑视图名需要在controller中返回ModelAndView指定,比如逻辑视图名为swxctx,则最终返回的jsp视图地址 “WEB-INF/jsp/swxctx.jsp”


9、视图开发(Jsp、Html)

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt"  prefix="fmt"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>查询商品列表</title>
</head>
<body> 
<form action="${pageContext.request.contextPath }/item/queryItem.action" method="post">
查询条件:
<table width="100%" border=1>
<tr>
<td><input type="submit" value="查询"/></td>
</tr>
</table>
商品列表:
<table width="100%" border=1>
<tr>
	<td>商品名称</td>
	<td>商品价格</td>
	<td>生产日期</td>
	<td>商品描述</td>
	<td>操作</td>
</tr>
<c:forEach items="${itemsList }" var="item">
<tr>
	<td>${item.name }</td>
	<td>${item.price }</td>
	<td><fmt:formatDate value="${item.createtime}" pattern="yyyy-MM-dd HH:mm:ss"/></td>
	<td>${item.detail }</td>
	
	<td><a href="${pageContext.request.contextPath }/item/editItem.action?id=${item.id}">修改</a></td>

</tr>
</c:forEach>

</table>
</form>
</body>

</html>

至此,我们已经完成了SpringMVC非注解的配置。

但是在开发中,我们还可能会使用其他适配器。例如HttpRequestHandler,接下来,我们使用HttpRequestHandler适配器进行配置。

1、配置适配器

<!-- http请求处理器适配器,实现HttpRequestHandler接口 -->
	<bean
		 class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"/>

2、开发处理器

package com.sw.ssm.controller;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.HttpRequestHandler;

import com.sw.ssm.po.Items;

/*
 *@Author swxctx
 *@time 2016年12月9日
 *@Explain:实现HttpRequestHandler接口的处理器
 */
public class ItemsController2 implements HttpRequestHandler{

	@Override
	public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		//调用servlce查找数据库,查询商品列表
		List<Items> list = new ArrayList<Items>();
		//填充订单数据
		Items items_1 = new Items();
		items_1.setName("Lenovo");
		items_1.setPrice(6000f);
		items_1.setDetail("ThinkPad T430 ");
		
		Items items_2 = new Items();
		items_2.setName("iphone");
		items_2.setPrice(5000f);
		items_2.setDetail("iphone6");
		
		list.add(items_1);
		list.add(items_2);
		
		//填充数据
		//设置模型数据
		request.setAttribute("itemsList", list);
		//设置转发的视图
		request.getRequestDispatcher("/WEB-INF/jsp/items/itemsList.jsp").forward(request, response);
	}

}

3、处理器配置

<!-- 配置handler(实现HttpRequestHandler接口) -->
	<bean id="itemsController2" class="com.sw.ssm.controller.ItemsController2"/>

在url的映射中,我们可以使用url简单映射,如下所示:

<!-- 2 配置加载Handler(实现controller接口) -->
	<bean id="itemsController1" name="/queryItems_text.action" class="com.sw.ssm.controller.ItemsController1"/>
	<!-- 配置handler(实现HttpRequestHandler接口) -->
	<bean id="itemsController2" class="com.sw.ssm.controller.ItemsController2"/>
	
	<!-- 简单url映射 -->
	<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
		<property name="mappings">
			<props>
				<!-- 对itemsController1进行url映射,url为:/queryItems1.action -->
				<prop key="/queryItems1.action">itemsController1</prop>
				<prop key="/queryItems2.action">itemsController1</prop>
				<!-- 实现HttpRequestHandler的url映射 -->
				<prop key="/queryItems3.action">itemsController2</prop>
			</props>
		</property>
	</bean>

如上所示,即通过配置处理器时指定id,通过url简单映射指定其他的url。

至此,非注解的开发方式已经完成,接下来我们进行注解的开发讲解。


注解方式的配置

使用注解的方式进行配置,其他地方均与上面相同,我们只需要更改处理器映射器与适配器以及更改处理器的开发。

1、处理器适配器

<!--注解适配器 -->
	<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

2、注解映射器

<!--注解映射器(与非注解可并存) -->
	<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

3、处理器开发

package com.sw.ssm.controller;

import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.sw.ssm.po.Items;

/*
 *@Author swxctx
 *@time 2016年12月9日
 *@Explain:实现注解Handler处理器
 */
//使用注解表示该类为一个控制器
@Controller
public class ItemsController3{
	//商品查询列表
	//实现对queryItems方法进行url映射(一个方法对应一个url)
	@RequestMapping("/queryItems.action")
	public ModelAndView queryItems()throws Exception{
		//调用servlce查找数据库,查询商品列表
		List<Items> list = new ArrayList<Items>();
		//填充订单数据
		Items items_1 = new Items();
		items_1.setName("联想笔记本");
		items_1.setPrice(6000f);
		items_1.setDetail("ThinkPad T430 联想笔记本电脑!");
		
		Items items_2 = new Items();
		items_2.setName("苹果手机");
		items_2.setPrice(5000f);
		items_2.setDetail("iphone6苹果手机!");
		
		list.add(items_1);
		list.add(items_2);
		
		//返回ModelAndView
		ModelAndView modelAndView = new ModelAndView();
		//作用类似于request的setAttribute(在jsp页面中通过itemsList取出相关数据)
		modelAndView.addObject("itemsList", list);
		
		//指定视图
		modelAndView.setViewName("/items/itemsList");
		return modelAndView;
	}
}


4、处理器配置

<!-- 配置加载注解的Handler(url已通过注解的方式配置在url中) -->
	<!-- 单个配置(在实际开发中不常用) -->
	<bean class="com.sw.ssm.controller.ItemsController3"/>
	<!-- 通过组件扫描的方式加载 -->
	<!-- <context:component-scan base-package="com.sw.ssm.controller"></context:component-scan> -->


以上则为注解的配置方式,在实际开发中,较为常用的即为注解方式。


作者:qq_28796345 发表于2016/12/12 20:05:14 原文链接
阅读:20 评论:0 查看评论

未正常毕业*Bristol布里斯托大学毕业`等证件`材料证书

$
0
0
*布里斯托大学毕业证】QQ/微信:549585563  联系人:Kimi 代办国外(海外)澳洲英国 加拿大 韩国 美国 新西兰 等各大学毕业证,修改成绩单分数,学历认证,文凭,diploma,degree
 
 
 
欢迎咨询!QQ/微信:549585563,专业为留学生提供毕业证、学历认证、文凭、学位证、学位证书、使馆公证、国外学历学位教育部认证、使馆留学回国人员证明、录取通知书、Offer、在读证明、雅思托福成绩单、网上存档永久可查!【实体公司,寰宇教育,值得信赖】
  
   一、【主营项目】:
1.毕业证、成绩单、使馆认证、教育部认证、留信、学生卡-- OFFER等.等!
 
2.办理真实使馆公证(即留学回国人员证明,不成功不收费)
 
3.教育部国外学历学位认证咨询。(网上可查、永久存档、快速稳妥)
 
4.办理各国各大学文凭(一对一专业服务,可全程监控跟踪进度)








咨询顾问:Kimi QQ/微信:549585563 
 
1、挂科了,不想读了,成绩不理想怎么办??
2、找工作没有文凭怎么办?有本科却要求硕士又怎么办?
3、打算回国了,找工作的时候,需要提供认证,有文凭却得不到认证。又该怎么办???
 
如果您是以下情况,我们都能竭诚为您解决实际问题:
1、在校期间,因各种原因未能顺利毕业,拿不到官方毕业证; 
2、面对父母的压力,希望尽快拿到;   
3、不清楚流程以及材料该如何准备;   
4、回国时间很长,忘记办;   
5、回国马上就要找工作,办给用人单位看;  
6、企事业单位必须要求办的; 
德国大学:慕尼黑工业大学,哥廷根大学,慕尼黑大学,开姆尼茨工业大学,卡尔斯鲁厄大学,达姆斯塔特工业大学,明斯特大学,弗赖堡大学,多特蒙德工业大学,马堡大学,杜塞尔多夫大学,波鸿鲁尔大学,布伦瑞克工业大学,奥格斯堡大学,杜伊斯堡埃森大学,凯撒斯劳滕工业大学,法兰克福大学,亚琛工业大学,斯图加特大学,汉诺威大学,基尔大学,柏林自由大学,柏林工业大学,吉森大学,纽伦堡大学,莱比锡大学,美因茨大学,乌尔兹堡大学,萨尔大学,科隆大学,不来梅大学,奥登堡大学,安哈尔特应用技术大学,波恩大学,勃兰登堡工业大学,德累斯顿工业大学,汉堡大学,柏林洪堡大学,卡塞尔大学,克劳斯塔尔工业大学,罗斯托克大学,耶拿应用技术大学,汉堡音乐和戏剧学院,鲁昂大学,克莱蒙费朗一大,克莱蒙费朗第二大学,萨瓦大学,佩皮尼昂大学,南布列塔尼大学,巴黎第一大学,第戎大学,国立里昂第二大学,格勒诺布尔第三大学,凡尔赛大学,巴黎第九大学,马赛大学,昂热大学,贝桑松大学,波城大学,滨海大学,科西嘉大学,尼斯大学,巴黎第八大学,南锡一大,雷恩一大,巴黎第四大学,卡昂大学,蒙彼利埃三大,蒙彼利埃第一大学,图尔大学,INSEEC,图卢兹第一大学,图卢兹第三大学,巴黎第四大学索邦大学,斯特拉斯堡大学,图卢兹三大,波尔多一大,里尔第三大学,里昂三大,奥尔良大学,亚眠大学,罗马二大,米兰大学,马兰欧尼,办理德国毕业证文凭学历认证成绩单留学回国证明
 
 
英国大学:
纽卡斯尔大学,帝国理工学院,巴斯大学,埃克塞特大学,伦敦大学学院UCL,华威大学,约克大学,兰卡斯特大学,萨里大学,莱斯特大学,布里斯托大学,伯明翰大学,格鲁斯特大学,谢菲尔德大学,南安普顿大学,拉夫堡大学,爱丁堡大学,诺丁汉大学,伦敦大学亚非学院SOAS,格拉斯哥大学,曼彻斯特大学,伦敦国王学院KCL,皇家霍洛威大学RHUL,阿斯顿大学,利兹大学,萨塞克斯大学,卡迪夫大学,伦敦艺术大学,雷丁大学,肯特大学,利物浦大学,伦敦玛丽女王学院QMUL,赫瑞瓦特大学,埃塞克斯大学,阿伯丁大学,伦敦城市大学,斯特拉思克莱德大学,基尔大学,考文垂大学,斯旺西大学,邓迪大学,阿伯泰大学,切斯特大学,朴茨茅斯大学,威尔士班戈大学,林肯大学,布拉德福德大学,北安普顿大学,诺丁汉特伦特大学,诺森比亚大学,赫尔大学,约克圣约翰大学,哈德斯菲尔德大学,伯恩茅斯大学,伦敦商学院大学,罗汉普顿大学,爱丁堡玛格丽特皇后学院,格林威治大学,赫特福德大学,布鲁内尔大学,德蒙福特大学,罗伯特戈登大学RGU,索尔福德大学,桑德兰大学,威斯敏斯特大学,南岸大学,圣安德鲁斯大学,普利茅斯大学,牛津布鲁克斯大学,伯明翰城市大学BCU,办理英国毕业证文凭学历认证成绩单留学回国证明
 
 
澳洲大学:
梅西大学,林肯大学,奥塔哥大学,奥克兰理工大学AUT,怀卡托大学,基督城理工学院CPIT,马努卡理工学院,坎特伯雷大学,奥克兰大学,奥克兰商学院AIS,悉尼大学USYD,新南威尔士大学UNSW,查尔斯达尔文大学CDU,澳大利亚联邦大学,斯威本科技大学Swinburne,巴拉瑞特大学ballarat,RMIT,墨尔本大学,阿德莱德大学Adelaide,莫纳什大学Monash,昆士兰大学UQ,西澳大学UWA,澳大利亚国立大学ANU,麦考瑞大学Macquarie,纽卡斯尔大学,卧龙岗大学
Wollongong,格里菲斯大学Griffith,弗林德斯大学Flinders,塔斯马尼亚大学UTAS,堪培拉大学,邦德大学Bond,迪肯大学Deakin,悉尼科技大学UTS,科廷大学Curtin,墨尔本皇家理工学院RMIT,昆士兰科技大学QUT,拉筹伯大学La Trobe,莫道克大学Murdoch,澳洲TAFE,南澳大学UniSA,中央昆士兰大学CQU,詹姆斯库克大学JCU,新英格兰大学UNE,南昆士兰大学USQ,埃迪斯科文大学ECU,南十字星大学SCU,阳光海岸大学,维多利亚大学Victoria,办理澳洲毕业证文凭学历认证成绩单留学回国证明
 
 
美国大学:
俄亥俄州立大学OSU,加州大学洛杉矶分校UCLA,华盛顿州立大学WSU,普渡大学Purdue,俄勒冈大学Oregon,纽约大学NYU,西雅图大学Seattle,南加州大学USC,宾州州立大学PSU,匹兹堡大学PITT,加州理工学院CIT,西北大学NWU,
宾夕法尼亚大学Pennsylvania,密歇根州立大学MSU,密歇根大学UMICH,波士顿大学BU,德克萨斯大学奥斯汀分校utexas,哈佛大学Harvard,麻省理工学院MIT,田纳西大学UTK,帕森斯设计学院Parsons,佩斯大学Pace,普林斯顿大学Princeton,托莱多大学Toledo,杜克大学Duke,芝加哥大学Chicago,达特茅斯学院Dartmouth,拉文大学La Verne,康奈尔大学Cornell,约翰霍普金斯大学JHU,布朗大学Brown,莱斯大学Rice,埃默里大学Emory,圣母大学,范德堡大学Vandy,加州大学伯克利分校UCB,卡内基梅隆大学CMU,乔治城大学Georgetown,弗吉尼亚大学UVa,塔夫斯大学Tufts,维克森林大学WFU,布兰迪斯大学Brandeis,威廉玛丽学院wm,波士顿学院BC,佐治亚理工学院Gatech,利哈伊大学Lehigh,罗切斯特大学Rochester,威斯康星大学WISC,内布拉斯加大学UNL,伦斯勒理工学院RPI,华盛顿大学UW,加州大学戴维斯分校UCD,加州大学欧文分校UCI,加州大学UCSB,天普大学Temple,叶史瓦大学Yeshiva,肯塔基大学Kentucky,乔治华盛顿大学GWU,雪城大学SU,马里兰大学UMD,佩珀代因大学Pepperdine,乔治亚大学UGA,克莱姆森大学Clemson,福特汉姆大学Fordham,明尼苏达大学UMN,迈阿密大学UM,密西西比大学Mississippi,南卫理公会大学SMU,康涅狄格大学Connecticut,爱荷华大学Iowa,印地安那大学伯明顿分校IUB,特拉华大学UD,伍斯特理工学院WPI,贝勒大学baylor,马凯特大学Marquette,纽约宾汉姆顿大学binghamton,纽约奥尔巴尼大学Albany,克拉克大学Clark,科罗拉多大学CU-Boulder,密苏里大学Missouri,堪萨斯大学KU,北卡罗来纳州立大学NCSU,俄亥俄大学ohio,俄克拉荷马大学Oklahoma,德州A&M大学TAMU,佛罗里达州立大学FSU,斯坦福大学Stanford,加州大学圣地亚哥分校UCSD,凤凰城大学UPX,旧金山大学USF,休斯敦大学UH,斯蒂文斯理工学院SIT,阿拉巴马大学Alabama,塔尔萨大学TU,德雷塞尔大学Drexel,爱荷华州立大学ISU,加州大学河滨分校UCR,丹佛大学DU,堪萨斯州立大学KSU,罗格斯大学Rutgers,佛蒙特大学UVM,奥本大学Auburn,美国东北大学NEU,纽约州立大学石溪分校SBU,亚利桑那州立大学ASU,亚利桑那大学UA,加州大学圣克鲁兹分校UCSC,纽约州立大学布法罗分校Buffalo,纽约理工学院NYIT,北卡罗莱纳大学UNC,罗德岛设计学院RISD,加州旧金山分校UCSF,伊利诺伊大学香槟分校UIUC,杜兰大学Tulane,社区大学College,阿肯色大学,犹他大学,杨百翰大学BYU,辛辛那提大学,霍夫斯特拉大学,肯特州立大学,旧金山艺术大学AAU,莱特州立大学,欧道明大学,罗德岛大学URI,南达科他州立大学,阿肯色大学小石城分校,长岛大学布鲁克林校区,海斯堡州立大学FHSU,圣莫尼卡社区学院SMC,办理美国毕业证文凭学历认证成绩单留学回国证明
 
 
加拿大大学:
多伦多大学,约克大学York,渥太华大学,西安大略大学UWO,滑铁卢大学,麦克马斯特大学McMaster,卡尔顿大学,皇后大学,布鲁克大学,湖首大学,BCIT,温莎大学,瑞尔森大学,百年理工学院,特伦特大学,英属哥伦比亚大学UBC,西蒙菲莎大学SFU,昆特兰理工大学KPU,汤普森河大学TRU,西三一大学,阿尔伯塔大学,卡尔加里大学,里贾纳大学,阿卡迪亚大学,达尔豪斯大学,圣玛丽大学,麦吉尔大学,康考迪亚大学,菲沙河谷大学UFV,蒙特利尔大学,温尼伯大学,曼尼托巴大学,毕业证加拿大文凭学历认证成绩单留学回国证明
作者:qiangpfeg 发表于2016/12/12 20:07:18 原文链接
阅读:18 评论:0 查看评论

未正常毕业*Brunel布鲁内尔大学毕业`等证件`材料证书

$
0
0
*布鲁内尔大学毕业证】QQ/微信:549585563  联系人:Kimi 代办国外(海外)澳洲英国 加拿大 韩国 美国 新西兰 等各大学毕业证,修改成绩单分数,学历认证,文凭,diploma,degree
 
 
 
欢迎咨询!QQ/微信:549585563,专业为留学生提供毕业证、学历认证、文凭、学位证、学位证书、使馆公证、国外学历学位教育部认证、使馆留学回国人员证明、录取通知书、Offer、在读证明、雅思托福成绩单、网上存档永久可查!【实体公司,寰宇教育,值得信赖】
  
   一、【主营项目】:
1.毕业证、成绩单、使馆认证、教育部认证、留信、学生卡-- OFFER等.等!
 
2.办理真实使馆公证(即留学回国人员证明,不成功不收费)
 
3.教育部国外学历学位认证咨询。(网上可查、永久存档、快速稳妥)
 
4.办理各国各大学文凭(一对一专业服务,可全程监控跟踪进度)








咨询顾问:Kimi QQ/微信:549585563 
 
1、挂科了,不想读了,成绩不理想怎么办??
2、找工作没有文凭怎么办?有本科却要求硕士又怎么办?
3、打算回国了,找工作的时候,需要提供认证,有文凭却得不到认证。又该怎么办???
 
如果您是以下情况,我们都能竭诚为您解决实际问题:
1、在校期间,因各种原因未能顺利毕业,拿不到官方毕业证; 
2、面对父母的压力,希望尽快拿到;   
3、不清楚流程以及材料该如何准备;   
4、回国时间很长,忘记办;   
5、回国马上就要找工作,办给用人单位看;  
6、企事业单位必须要求办的; 
德国大学:慕尼黑工业大学,哥廷根大学,慕尼黑大学,开姆尼茨工业大学,卡尔斯鲁厄大学,达姆斯塔特工业大学,明斯特大学,弗赖堡大学,多特蒙德工业大学,马堡大学,杜塞尔多夫大学,波鸿鲁尔大学,布伦瑞克工业大学,奥格斯堡大学,杜伊斯堡埃森大学,凯撒斯劳滕工业大学,法兰克福大学,亚琛工业大学,斯图加特大学,汉诺威大学,基尔大学,柏林自由大学,柏林工业大学,吉森大学,纽伦堡大学,莱比锡大学,美因茨大学,乌尔兹堡大学,萨尔大学,科隆大学,不来梅大学,奥登堡大学,安哈尔特应用技术大学,波恩大学,勃兰登堡工业大学,德累斯顿工业大学,汉堡大学,柏林洪堡大学,卡塞尔大学,克劳斯塔尔工业大学,罗斯托克大学,耶拿应用技术大学,汉堡音乐和戏剧学院,鲁昂大学,克莱蒙费朗一大,克莱蒙费朗第二大学,萨瓦大学,佩皮尼昂大学,南布列塔尼大学,巴黎第一大学,第戎大学,国立里昂第二大学,格勒诺布尔第三大学,凡尔赛大学,巴黎第九大学,马赛大学,昂热大学,贝桑松大学,波城大学,滨海大学,科西嘉大学,尼斯大学,巴黎第八大学,南锡一大,雷恩一大,巴黎第四大学,卡昂大学,蒙彼利埃三大,蒙彼利埃第一大学,图尔大学,INSEEC,图卢兹第一大学,图卢兹第三大学,巴黎第四大学索邦大学,斯特拉斯堡大学,图卢兹三大,波尔多一大,里尔第三大学,里昂三大,奥尔良大学,亚眠大学,罗马二大,米兰大学,马兰欧尼,办理德国毕业证文凭学历认证成绩单留学回国证明
 
 
英国大学:
纽卡斯尔大学,帝国理工学院,巴斯大学,埃克塞特大学,伦敦大学学院UCL,华威大学,约克大学,兰卡斯特大学,萨里大学,莱斯特大学,布里斯托大学,伯明翰大学,格鲁斯特大学,谢菲尔德大学,南安普顿大学,拉夫堡大学,爱丁堡大学,诺丁汉大学,伦敦大学亚非学院SOAS,格拉斯哥大学,曼彻斯特大学,伦敦国王学院KCL,皇家霍洛威大学RHUL,阿斯顿大学,利兹大学,萨塞克斯大学,卡迪夫大学,伦敦艺术大学,雷丁大学,肯特大学,利物浦大学,伦敦玛丽女王学院QMUL,赫瑞瓦特大学,埃塞克斯大学,阿伯丁大学,伦敦城市大学,斯特拉思克莱德大学,基尔大学,考文垂大学,斯旺西大学,邓迪大学,阿伯泰大学,切斯特大学,朴茨茅斯大学,威尔士班戈大学,林肯大学,布拉德福德大学,北安普顿大学,诺丁汉特伦特大学,诺森比亚大学,赫尔大学,约克圣约翰大学,哈德斯菲尔德大学,伯恩茅斯大学,伦敦商学院大学,罗汉普顿大学,爱丁堡玛格丽特皇后学院,格林威治大学,赫特福德大学,布鲁内尔大学,德蒙福特大学,罗伯特戈登大学RGU,索尔福德大学,桑德兰大学,威斯敏斯特大学,南岸大学,圣安德鲁斯大学,普利茅斯大学,牛津布鲁克斯大学,伯明翰城市大学BCU,办理英国毕业证文凭学历认证成绩单留学回国证明
 
 
澳洲大学:
梅西大学,林肯大学,奥塔哥大学,奥克兰理工大学AUT,怀卡托大学,基督城理工学院CPIT,马努卡理工学院,坎特伯雷大学,奥克兰大学,奥克兰商学院AIS,悉尼大学USYD,新南威尔士大学UNSW,查尔斯达尔文大学CDU,澳大利亚联邦大学,斯威本科技大学Swinburne,巴拉瑞特大学ballarat,RMIT,墨尔本大学,阿德莱德大学Adelaide,莫纳什大学Monash,昆士兰大学UQ,西澳大学UWA,澳大利亚国立大学ANU,麦考瑞大学Macquarie,纽卡斯尔大学,卧龙岗大学
Wollongong,格里菲斯大学Griffith,弗林德斯大学Flinders,塔斯马尼亚大学UTAS,堪培拉大学,邦德大学Bond,迪肯大学Deakin,悉尼科技大学UTS,科廷大学Curtin,墨尔本皇家理工学院RMIT,昆士兰科技大学QUT,拉筹伯大学La Trobe,莫道克大学Murdoch,澳洲TAFE,南澳大学UniSA,中央昆士兰大学CQU,詹姆斯库克大学JCU,新英格兰大学UNE,南昆士兰大学USQ,埃迪斯科文大学ECU,南十字星大学SCU,阳光海岸大学,维多利亚大学Victoria,办理澳洲毕业证文凭学历认证成绩单留学回国证明
 
 
美国大学:
俄亥俄州立大学OSU,加州大学洛杉矶分校UCLA,华盛顿州立大学WSU,普渡大学Purdue,俄勒冈大学Oregon,纽约大学NYU,西雅图大学Seattle,南加州大学USC,宾州州立大学PSU,匹兹堡大学PITT,加州理工学院CIT,西北大学NWU,
宾夕法尼亚大学Pennsylvania,密歇根州立大学MSU,密歇根大学UMICH,波士顿大学BU,德克萨斯大学奥斯汀分校utexas,哈佛大学Harvard,麻省理工学院MIT,田纳西大学UTK,帕森斯设计学院Parsons,佩斯大学Pace,普林斯顿大学Princeton,托莱多大学Toledo,杜克大学Duke,芝加哥大学Chicago,达特茅斯学院Dartmouth,拉文大学La Verne,康奈尔大学Cornell,约翰霍普金斯大学JHU,布朗大学Brown,莱斯大学Rice,埃默里大学Emory,圣母大学,范德堡大学Vandy,加州大学伯克利分校UCB,卡内基梅隆大学CMU,乔治城大学Georgetown,弗吉尼亚大学UVa,塔夫斯大学Tufts,维克森林大学WFU,布兰迪斯大学Brandeis,威廉玛丽学院wm,波士顿学院BC,佐治亚理工学院Gatech,利哈伊大学Lehigh,罗切斯特大学Rochester,威斯康星大学WISC,内布拉斯加大学UNL,伦斯勒理工学院RPI,华盛顿大学UW,加州大学戴维斯分校UCD,加州大学欧文分校UCI,加州大学UCSB,天普大学Temple,叶史瓦大学Yeshiva,肯塔基大学Kentucky,乔治华盛顿大学GWU,雪城大学SU,马里兰大学UMD,佩珀代因大学Pepperdine,乔治亚大学UGA,克莱姆森大学Clemson,福特汉姆大学Fordham,明尼苏达大学UMN,迈阿密大学UM,密西西比大学Mississippi,南卫理公会大学SMU,康涅狄格大学Connecticut,爱荷华大学Iowa,印地安那大学伯明顿分校IUB,特拉华大学UD,伍斯特理工学院WPI,贝勒大学baylor,马凯特大学Marquette,纽约宾汉姆顿大学binghamton,纽约奥尔巴尼大学Albany,克拉克大学Clark,科罗拉多大学CU-Boulder,密苏里大学Missouri,堪萨斯大学KU,北卡罗来纳州立大学NCSU,俄亥俄大学ohio,俄克拉荷马大学Oklahoma,德州A&M大学TAMU,佛罗里达州立大学FSU,斯坦福大学Stanford,加州大学圣地亚哥分校UCSD,凤凰城大学UPX,旧金山大学USF,休斯敦大学UH,斯蒂文斯理工学院SIT,阿拉巴马大学Alabama,塔尔萨大学TU,德雷塞尔大学Drexel,爱荷华州立大学ISU,加州大学河滨分校UCR,丹佛大学DU,堪萨斯州立大学KSU,罗格斯大学Rutgers,佛蒙特大学UVM,奥本大学Auburn,美国东北大学NEU,纽约州立大学石溪分校SBU,亚利桑那州立大学ASU,亚利桑那大学UA,加州大学圣克鲁兹分校UCSC,纽约州立大学布法罗分校Buffalo,纽约理工学院NYIT,北卡罗莱纳大学UNC,罗德岛设计学院RISD,加州旧金山分校UCSF,伊利诺伊大学香槟分校UIUC,杜兰大学Tulane,社区大学College,阿肯色大学,犹他大学,杨百翰大学BYU,辛辛那提大学,霍夫斯特拉大学,肯特州立大学,旧金山艺术大学AAU,莱特州立大学,欧道明大学,罗德岛大学URI,南达科他州立大学,阿肯色大学小石城分校,长岛大学布鲁克林校区,海斯堡州立大学FHSU,圣莫尼卡社区学院SMC,办理美国毕业证文凭学历认证成绩单留学回国证明
 
 
加拿大大学:
多伦多大学,约克大学York,渥太华大学,西安大略大学UWO,滑铁卢大学,麦克马斯特大学McMaster,卡尔顿大学,皇后大学,布鲁克大学,湖首大学,BCIT,温莎大学,瑞尔森大学,百年理工学院,特伦特大学,英属哥伦比亚大学UBC,西蒙菲莎大学SFU,昆特兰理工大学KPU,汤普森河大学TRU,西三一大学,阿尔伯塔大学,卡尔加里大学,里贾纳大学,阿卡迪亚大学,达尔豪斯大学,圣玛丽大学,麦吉尔大学,康考迪亚大学,菲沙河谷大学UFV,蒙特利尔大学,温尼伯大学,曼尼托巴大学,毕业证加拿大文凭学历认证成绩单留学回国证明
作者:qiangpfeg 发表于2016/12/12 20:07:32 原文链接
阅读:14 评论:0 查看评论

未正常毕业*UCB白金汉大学毕业`等证件`材料证书

$
0
0
*白金汉大学毕业证】QQ/微信:549585563  联系人:Kimi 代办国外(海外)澳洲英国 加拿大 韩国 美国 新西兰 等各大学毕业证,修改成绩单分数,学历认证,文凭,diploma,degree
 
 
 
欢迎咨询!QQ/微信:549585563,专业为留学生提供毕业证、学历认证、文凭、学位证、学位证书、使馆公证、国外学历学位教育部认证、使馆留学回国人员证明、录取通知书、Offer、在读证明、雅思托福成绩单、网上存档永久可查!【实体公司,寰宇教育,值得信赖】
  
   一、【主营项目】:
1.毕业证、成绩单、使馆认证、教育部认证、留信、学生卡-- OFFER等.等!
 
2.办理真实使馆公证(即留学回国人员证明,不成功不收费)
 
3.教育部国外学历学位认证咨询。(网上可查、永久存档、快速稳妥)
 
4.办理各国各大学文凭(一对一专业服务,可全程监控跟踪进度)








咨询顾问:Kimi QQ/微信:549585563 
 
1、挂科了,不想读了,成绩不理想怎么办??
2、找工作没有文凭怎么办?有本科却要求硕士又怎么办?
3、打算回国了,找工作的时候,需要提供认证,有文凭却得不到认证。又该怎么办???
 
如果您是以下情况,我们都能竭诚为您解决实际问题:
1、在校期间,因各种原因未能顺利毕业,拿不到官方毕业证; 
2、面对父母的压力,希望尽快拿到;   
3、不清楚流程以及材料该如何准备;   
4、回国时间很长,忘记办;   
5、回国马上就要找工作,办给用人单位看;  
6、企事业单位必须要求办的; 
德国大学:慕尼黑工业大学,哥廷根大学,慕尼黑大学,开姆尼茨工业大学,卡尔斯鲁厄大学,达姆斯塔特工业大学,明斯特大学,弗赖堡大学,多特蒙德工业大学,马堡大学,杜塞尔多夫大学,波鸿鲁尔大学,布伦瑞克工业大学,奥格斯堡大学,杜伊斯堡埃森大学,凯撒斯劳滕工业大学,法兰克福大学,亚琛工业大学,斯图加特大学,汉诺威大学,基尔大学,柏林自由大学,柏林工业大学,吉森大学,纽伦堡大学,莱比锡大学,美因茨大学,乌尔兹堡大学,萨尔大学,科隆大学,不来梅大学,奥登堡大学,安哈尔特应用技术大学,波恩大学,勃兰登堡工业大学,德累斯顿工业大学,汉堡大学,柏林洪堡大学,卡塞尔大学,克劳斯塔尔工业大学,罗斯托克大学,耶拿应用技术大学,汉堡音乐和戏剧学院,鲁昂大学,克莱蒙费朗一大,克莱蒙费朗第二大学,萨瓦大学,佩皮尼昂大学,南布列塔尼大学,巴黎第一大学,第戎大学,国立里昂第二大学,格勒诺布尔第三大学,凡尔赛大学,巴黎第九大学,马赛大学,昂热大学,贝桑松大学,波城大学,滨海大学,科西嘉大学,尼斯大学,巴黎第八大学,南锡一大,雷恩一大,巴黎第四大学,卡昂大学,蒙彼利埃三大,蒙彼利埃第一大学,图尔大学,INSEEC,图卢兹第一大学,图卢兹第三大学,巴黎第四大学索邦大学,斯特拉斯堡大学,图卢兹三大,波尔多一大,里尔第三大学,里昂三大,奥尔良大学,亚眠大学,罗马二大,米兰大学,马兰欧尼,办理德国毕业证文凭学历认证成绩单留学回国证明
 
 
英国大学:
纽卡斯尔大学,帝国理工学院,巴斯大学,埃克塞特大学,伦敦大学学院UCL,华威大学,约克大学,兰卡斯特大学,萨里大学,莱斯特大学,布里斯托大学,伯明翰大学,格鲁斯特大学,谢菲尔德大学,南安普顿大学,拉夫堡大学,爱丁堡大学,诺丁汉大学,伦敦大学亚非学院SOAS,格拉斯哥大学,曼彻斯特大学,伦敦国王学院KCL,皇家霍洛威大学RHUL,阿斯顿大学,利兹大学,萨塞克斯大学,卡迪夫大学,伦敦艺术大学,雷丁大学,肯特大学,利物浦大学,伦敦玛丽女王学院QMUL,赫瑞瓦特大学,埃塞克斯大学,阿伯丁大学,伦敦城市大学,斯特拉思克莱德大学,基尔大学,考文垂大学,斯旺西大学,邓迪大学,阿伯泰大学,切斯特大学,朴茨茅斯大学,威尔士班戈大学,林肯大学,布拉德福德大学,北安普顿大学,诺丁汉特伦特大学,诺森比亚大学,赫尔大学,约克圣约翰大学,哈德斯菲尔德大学,伯恩茅斯大学,伦敦商学院大学,罗汉普顿大学,爱丁堡玛格丽特皇后学院,格林威治大学,赫特福德大学,布鲁内尔大学,德蒙福特大学,罗伯特戈登大学RGU,索尔福德大学,桑德兰大学,威斯敏斯特大学,南岸大学,圣安德鲁斯大学,普利茅斯大学,牛津布鲁克斯大学,伯明翰城市大学BCU,办理英国毕业证文凭学历认证成绩单留学回国证明
 
 
澳洲大学:
梅西大学,林肯大学,奥塔哥大学,奥克兰理工大学AUT,怀卡托大学,基督城理工学院CPIT,马努卡理工学院,坎特伯雷大学,奥克兰大学,奥克兰商学院AIS,悉尼大学USYD,新南威尔士大学UNSW,查尔斯达尔文大学CDU,澳大利亚联邦大学,斯威本科技大学Swinburne,巴拉瑞特大学ballarat,RMIT,墨尔本大学,阿德莱德大学Adelaide,莫纳什大学Monash,昆士兰大学UQ,西澳大学UWA,澳大利亚国立大学ANU,麦考瑞大学Macquarie,纽卡斯尔大学,卧龙岗大学
Wollongong,格里菲斯大学Griffith,弗林德斯大学Flinders,塔斯马尼亚大学UTAS,堪培拉大学,邦德大学Bond,迪肯大学Deakin,悉尼科技大学UTS,科廷大学Curtin,墨尔本皇家理工学院RMIT,昆士兰科技大学QUT,拉筹伯大学La Trobe,莫道克大学Murdoch,澳洲TAFE,南澳大学UniSA,中央昆士兰大学CQU,詹姆斯库克大学JCU,新英格兰大学UNE,南昆士兰大学USQ,埃迪斯科文大学ECU,南十字星大学SCU,阳光海岸大学,维多利亚大学Victoria,办理澳洲毕业证文凭学历认证成绩单留学回国证明
 
 
美国大学:
俄亥俄州立大学OSU,加州大学洛杉矶分校UCLA,华盛顿州立大学WSU,普渡大学Purdue,俄勒冈大学Oregon,纽约大学NYU,西雅图大学Seattle,南加州大学USC,宾州州立大学PSU,匹兹堡大学PITT,加州理工学院CIT,西北大学NWU,
宾夕法尼亚大学Pennsylvania,密歇根州立大学MSU,密歇根大学UMICH,波士顿大学BU,德克萨斯大学奥斯汀分校utexas,哈佛大学Harvard,麻省理工学院MIT,田纳西大学UTK,帕森斯设计学院Parsons,佩斯大学Pace,普林斯顿大学Princeton,托莱多大学Toledo,杜克大学Duke,芝加哥大学Chicago,达特茅斯学院Dartmouth,拉文大学La Verne,康奈尔大学Cornell,约翰霍普金斯大学JHU,布朗大学Brown,莱斯大学Rice,埃默里大学Emory,圣母大学,范德堡大学Vandy,加州大学伯克利分校UCB,卡内基梅隆大学CMU,乔治城大学Georgetown,弗吉尼亚大学UVa,塔夫斯大学Tufts,维克森林大学WFU,布兰迪斯大学Brandeis,威廉玛丽学院wm,波士顿学院BC,佐治亚理工学院Gatech,利哈伊大学Lehigh,罗切斯特大学Rochester,威斯康星大学WISC,内布拉斯加大学UNL,伦斯勒理工学院RPI,华盛顿大学UW,加州大学戴维斯分校UCD,加州大学欧文分校UCI,加州大学UCSB,天普大学Temple,叶史瓦大学Yeshiva,肯塔基大学Kentucky,乔治华盛顿大学GWU,雪城大学SU,马里兰大学UMD,佩珀代因大学Pepperdine,乔治亚大学UGA,克莱姆森大学Clemson,福特汉姆大学Fordham,明尼苏达大学UMN,迈阿密大学UM,密西西比大学Mississippi,南卫理公会大学SMU,康涅狄格大学Connecticut,爱荷华大学Iowa,印地安那大学伯明顿分校IUB,特拉华大学UD,伍斯特理工学院WPI,贝勒大学baylor,马凯特大学Marquette,纽约宾汉姆顿大学binghamton,纽约奥尔巴尼大学Albany,克拉克大学Clark,科罗拉多大学CU-Boulder,密苏里大学Missouri,堪萨斯大学KU,北卡罗来纳州立大学NCSU,俄亥俄大学ohio,俄克拉荷马大学Oklahoma,德州A&M大学TAMU,佛罗里达州立大学FSU,斯坦福大学Stanford,加州大学圣地亚哥分校UCSD,凤凰城大学UPX,旧金山大学USF,休斯敦大学UH,斯蒂文斯理工学院SIT,阿拉巴马大学Alabama,塔尔萨大学TU,德雷塞尔大学Drexel,爱荷华州立大学ISU,加州大学河滨分校UCR,丹佛大学DU,堪萨斯州立大学KSU,罗格斯大学Rutgers,佛蒙特大学UVM,奥本大学Auburn,美国东北大学NEU,纽约州立大学石溪分校SBU,亚利桑那州立大学ASU,亚利桑那大学UA,加州大学圣克鲁兹分校UCSC,纽约州立大学布法罗分校Buffalo,纽约理工学院NYIT,北卡罗莱纳大学UNC,罗德岛设计学院RISD,加州旧金山分校UCSF,伊利诺伊大学香槟分校UIUC,杜兰大学Tulane,社区大学College,阿肯色大学,犹他大学,杨百翰大学BYU,辛辛那提大学,霍夫斯特拉大学,肯特州立大学,旧金山艺术大学AAU,莱特州立大学,欧道明大学,罗德岛大学URI,南达科他州立大学,阿肯色大学小石城分校,长岛大学布鲁克林校区,海斯堡州立大学FHSU,圣莫尼卡社区学院SMC,办理美国毕业证文凭学历认证成绩单留学回国证明
 
 
加拿大大学:
多伦多大学,约克大学York,渥太华大学,西安大略大学UWO,滑铁卢大学,麦克马斯特大学McMaster,卡尔顿大学,皇后大学,布鲁克大学,湖首大学,BCIT,温莎大学,瑞尔森大学,百年理工学院,特伦特大学,英属哥伦比亚大学UBC,西蒙菲莎大学SFU,昆特兰理工大学KPU,汤普森河大学TRU,西三一大学,阿尔伯塔大学,卡尔加里大学,里贾纳大学,阿卡迪亚大学,达尔豪斯大学,圣玛丽大学,麦吉尔大学,康考迪亚大学,菲沙河谷大学UFV,蒙特利尔大学,温尼伯大学,曼尼托巴大学,毕业证加拿大文凭学历认证成绩单留学回国证明
未正常毕业*UCB白金汉大学毕业`等证件`材料证书
作者:qiangpfeg 发表于2016/12/12 20:07:46 原文链接
阅读:15 评论:0 查看评论

未正常毕业*CITY剑桥大学毕业`等证件`材料证书

$
0
0
*剑桥大学毕业证】QQ/微信:549585563  联系人:Kimi 代办国外(海外)澳洲英国 加拿大 韩国 美国 新西兰 等各大学毕业证,修改成绩单分数,学历认证,文凭,diploma,degree
 
 
 
欢迎咨询!QQ/微信:549585563,专业为留学生提供毕业证、学历认证、文凭、学位证、学位证书、使馆公证、国外学历学位教育部认证、使馆留学回国人员证明、录取通知书、Offer、在读证明、雅思托福成绩单、网上存档永久可查!【实体公司,寰宇教育,值得信赖】
  
   一、【主营项目】:
1.毕业证、成绩单、使馆认证、教育部认证、留信、学生卡-- OFFER等.等!
 
2.办理真实使馆公证(即留学回国人员证明,不成功不收费)
 
3.教育部国外学历学位认证咨询。(网上可查、永久存档、快速稳妥)
 
4.办理各国各大学文凭(一对一专业服务,可全程监控跟踪进度)








咨询顾问:Kimi QQ/微信:549585563 
 
1、挂科了,不想读了,成绩不理想怎么办??
2、找工作没有文凭怎么办?有本科却要求硕士又怎么办?
3、打算回国了,找工作的时候,需要提供认证,有文凭却得不到认证。又该怎么办???
 
如果您是以下情况,我们都能竭诚为您解决实际问题:
1、在校期间,因各种原因未能顺利毕业,拿不到官方毕业证; 
2、面对父母的压力,希望尽快拿到;   
3、不清楚流程以及材料该如何准备;   
4、回国时间很长,忘记办;   
5、回国马上就要找工作,办给用人单位看;  
6、企事业单位必须要求办的; 
德国大学:慕尼黑工业大学,哥廷根大学,慕尼黑大学,开姆尼茨工业大学,卡尔斯鲁厄大学,达姆斯塔特工业大学,明斯特大学,弗赖堡大学,多特蒙德工业大学,马堡大学,杜塞尔多夫大学,波鸿鲁尔大学,布伦瑞克工业大学,奥格斯堡大学,杜伊斯堡埃森大学,凯撒斯劳滕工业大学,法兰克福大学,亚琛工业大学,斯图加特大学,汉诺威大学,基尔大学,柏林自由大学,柏林工业大学,吉森大学,纽伦堡大学,莱比锡大学,美因茨大学,乌尔兹堡大学,萨尔大学,科隆大学,不来梅大学,奥登堡大学,安哈尔特应用技术大学,波恩大学,勃兰登堡工业大学,德累斯顿工业大学,汉堡大学,柏林洪堡大学,卡塞尔大学,克劳斯塔尔工业大学,罗斯托克大学,耶拿应用技术大学,汉堡音乐和戏剧学院,鲁昂大学,克莱蒙费朗一大,克莱蒙费朗第二大学,萨瓦大学,佩皮尼昂大学,南布列塔尼大学,巴黎第一大学,第戎大学,国立里昂第二大学,格勒诺布尔第三大学,凡尔赛大学,巴黎第九大学,马赛大学,昂热大学,贝桑松大学,波城大学,滨海大学,科西嘉大学,尼斯大学,巴黎第八大学,南锡一大,雷恩一大,巴黎第四大学,卡昂大学,蒙彼利埃三大,蒙彼利埃第一大学,图尔大学,INSEEC,图卢兹第一大学,图卢兹第三大学,巴黎第四大学索邦大学,斯特拉斯堡大学,图卢兹三大,波尔多一大,里尔第三大学,里昂三大,奥尔良大学,亚眠大学,罗马二大,米兰大学,马兰欧尼,办理德国毕业证文凭学历认证成绩单留学回国证明
 
 
英国大学:
纽卡斯尔大学,帝国理工学院,巴斯大学,埃克塞特大学,伦敦大学学院UCL,华威大学,约克大学,兰卡斯特大学,萨里大学,莱斯特大学,布里斯托大学,伯明翰大学,格鲁斯特大学,谢菲尔德大学,南安普顿大学,拉夫堡大学,爱丁堡大学,诺丁汉大学,伦敦大学亚非学院SOAS,格拉斯哥大学,曼彻斯特大学,伦敦国王学院KCL,皇家霍洛威大学RHUL,阿斯顿大学,利兹大学,萨塞克斯大学,卡迪夫大学,伦敦艺术大学,雷丁大学,肯特大学,利物浦大学,伦敦玛丽女王学院QMUL,赫瑞瓦特大学,埃塞克斯大学,阿伯丁大学,伦敦城市大学,斯特拉思克莱德大学,基尔大学,考文垂大学,斯旺西大学,邓迪大学,阿伯泰大学,切斯特大学,朴茨茅斯大学,威尔士班戈大学,林肯大学,布拉德福德大学,北安普顿大学,诺丁汉特伦特大学,诺森比亚大学,赫尔大学,约克圣约翰大学,哈德斯菲尔德大学,伯恩茅斯大学,伦敦商学院大学,罗汉普顿大学,爱丁堡玛格丽特皇后学院,格林威治大学,赫特福德大学,布鲁内尔大学,德蒙福特大学,罗伯特戈登大学RGU,索尔福德大学,桑德兰大学,威斯敏斯特大学,南岸大学,圣安德鲁斯大学,普利茅斯大学,牛津布鲁克斯大学,伯明翰城市大学BCU,办理英国毕业证文凭学历认证成绩单留学回国证明
 
 
澳洲大学:
梅西大学,林肯大学,奥塔哥大学,奥克兰理工大学AUT,怀卡托大学,基督城理工学院CPIT,马努卡理工学院,坎特伯雷大学,奥克兰大学,奥克兰商学院AIS,悉尼大学USYD,新南威尔士大学UNSW,查尔斯达尔文大学CDU,澳大利亚联邦大学,斯威本科技大学Swinburne,巴拉瑞特大学ballarat,RMIT,墨尔本大学,阿德莱德大学Adelaide,莫纳什大学Monash,昆士兰大学UQ,西澳大学UWA,澳大利亚国立大学ANU,麦考瑞大学Macquarie,纽卡斯尔大学,卧龙岗大学
Wollongong,格里菲斯大学Griffith,弗林德斯大学Flinders,塔斯马尼亚大学UTAS,堪培拉大学,邦德大学Bond,迪肯大学Deakin,悉尼科技大学UTS,科廷大学Curtin,墨尔本皇家理工学院RMIT,昆士兰科技大学QUT,拉筹伯大学La Trobe,莫道克大学Murdoch,澳洲TAFE,南澳大学UniSA,中央昆士兰大学CQU,詹姆斯库克大学JCU,新英格兰大学UNE,南昆士兰大学USQ,埃迪斯科文大学ECU,南十字星大学SCU,阳光海岸大学,维多利亚大学Victoria,办理澳洲毕业证文凭学历认证成绩单留学回国证明
 
 
美国大学:
俄亥俄州立大学OSU,加州大学洛杉矶分校UCLA,华盛顿州立大学WSU,普渡大学Purdue,俄勒冈大学Oregon,纽约大学NYU,西雅图大学Seattle,南加州大学USC,宾州州立大学PSU,匹兹堡大学PITT,加州理工学院CIT,西北大学NWU,
宾夕法尼亚大学Pennsylvania,密歇根州立大学MSU,密歇根大学UMICH,波士顿大学BU,德克萨斯大学奥斯汀分校utexas,哈佛大学Harvard,麻省理工学院MIT,田纳西大学UTK,帕森斯设计学院Parsons,佩斯大学Pace,普林斯顿大学Princeton,托莱多大学Toledo,杜克大学Duke,芝加哥大学Chicago,达特茅斯学院Dartmouth,拉文大学La Verne,康奈尔大学Cornell,约翰霍普金斯大学JHU,布朗大学Brown,莱斯大学Rice,埃默里大学Emory,圣母大学,范德堡大学Vandy,加州大学伯克利分校UCB,卡内基梅隆大学CMU,乔治城大学Georgetown,弗吉尼亚大学UVa,塔夫斯大学Tufts,维克森林大学WFU,布兰迪斯大学Brandeis,威廉玛丽学院wm,波士顿学院BC,佐治亚理工学院Gatech,利哈伊大学Lehigh,罗切斯特大学Rochester,威斯康星大学WISC,内布拉斯加大学UNL,伦斯勒理工学院RPI,华盛顿大学UW,加州大学戴维斯分校UCD,加州大学欧文分校UCI,加州大学UCSB,天普大学Temple,叶史瓦大学Yeshiva,肯塔基大学Kentucky,乔治华盛顿大学GWU,雪城大学SU,马里兰大学UMD,佩珀代因大学Pepperdine,乔治亚大学UGA,克莱姆森大学Clemson,福特汉姆大学Fordham,明尼苏达大学UMN,迈阿密大学UM,密西西比大学Mississippi,南卫理公会大学SMU,康涅狄格大学Connecticut,爱荷华大学Iowa,印地安那大学伯明顿分校IUB,特拉华大学UD,伍斯特理工学院WPI,贝勒大学baylor,马凯特大学Marquette,纽约宾汉姆顿大学binghamton,纽约奥尔巴尼大学Albany,克拉克大学Clark,科罗拉多大学CU-Boulder,密苏里大学Missouri,堪萨斯大学KU,北卡罗来纳州立大学NCSU,俄亥俄大学ohio,俄克拉荷马大学Oklahoma,德州A&M大学TAMU,佛罗里达州立大学FSU,斯坦福大学Stanford,加州大学圣地亚哥分校UCSD,凤凰城大学UPX,旧金山大学USF,休斯敦大学UH,斯蒂文斯理工学院SIT,阿拉巴马大学Alabama,塔尔萨大学TU,德雷塞尔大学Drexel,爱荷华州立大学ISU,加州大学河滨分校UCR,丹佛大学DU,堪萨斯州立大学KSU,罗格斯大学Rutgers,佛蒙特大学UVM,奥本大学Auburn,美国东北大学NEU,纽约州立大学石溪分校SBU,亚利桑那州立大学ASU,亚利桑那大学UA,加州大学圣克鲁兹分校UCSC,纽约州立大学布法罗分校Buffalo,纽约理工学院NYIT,北卡罗莱纳大学UNC,罗德岛设计学院RISD,加州旧金山分校UCSF,伊利诺伊大学香槟分校UIUC,杜兰大学Tulane,社区大学College,阿肯色大学,犹他大学,杨百翰大学BYU,辛辛那提大学,霍夫斯特拉大学,肯特州立大学,旧金山艺术大学AAU,莱特州立大学,欧道明大学,罗德岛大学URI,南达科他州立大学,阿肯色大学小石城分校,长岛大学布鲁克林校区,海斯堡州立大学FHSU,圣莫尼卡社区学院SMC,办理美国毕业证文凭学历认证成绩单留学回国证明
 
 
加拿大大学:
多伦多大学,约克大学York,渥太华大学,西安大略大学UWO,滑铁卢大学,麦克马斯特大学McMaster,卡尔顿大学,皇后大学,布鲁克大学,湖首大学,BCIT,温莎大学,瑞尔森大学,百年理工学院,特伦特大学,英属哥伦比亚大学UBC,西蒙菲莎大学SFU,昆特兰理工大学KPU,汤普森河大学TRU,西三一大学,阿尔伯塔大学,卡尔加里大学,里贾纳大学,阿卡迪亚大学,达尔豪斯大学,圣玛丽大学,麦吉尔大学,康考迪亚大学,菲沙河谷大学UFV,蒙特利尔大学,温尼伯大学,曼尼托巴大学,毕业证加拿大文凭学历认证成绩单留学回国证明
未正常毕业*CITY剑桥大学毕业`等证件`材料证书
作者:qiangpfeg 发表于2016/12/12 20:08:11 原文链接
阅读:17 评论:0 查看评论

未正常毕业*UC坎特伯雷大学毕业`等证件`材料证书

$
0
0
*坎特伯雷大学毕业证】QQ/微信:549585563  联系人:Kimi 代办国外(海外)澳洲英国 加拿大 韩国 美国 新西兰 等各大学毕业证,修改成绩单分数,学历认证,文凭,diploma,degree
 
 
 
欢迎咨询!QQ/微信:549585563,专业为留学生提供毕业证、学历认证、文凭、学位证、学位证书、使馆公证、国外学历学位教育部认证、使馆留学回国人员证明、录取通知书、Offer、在读证明、雅思托福成绩单、网上存档永久可查!【实体公司,寰宇教育,值得信赖】
  
   一、【主营项目】:
1.毕业证、成绩单、使馆认证、教育部认证、留信、学生卡-- OFFER等.等!
 
2.办理真实使馆公证(即留学回国人员证明,不成功不收费)
 
3.教育部国外学历学位认证咨询。(网上可查、永久存档、快速稳妥)
 
4.办理各国各大学文凭(一对一专业服务,可全程监控跟踪进度)








咨询顾问:Kimi QQ/微信:549585563 
 
1、挂科了,不想读了,成绩不理想怎么办??
2、找工作没有文凭怎么办?有本科却要求硕士又怎么办?
3、打算回国了,找工作的时候,需要提供认证,有文凭却得不到认证。又该怎么办???
 
如果您是以下情况,我们都能竭诚为您解决实际问题:
1、在校期间,因各种原因未能顺利毕业,拿不到官方毕业证; 
2、面对父母的压力,希望尽快拿到;   
3、不清楚流程以及材料该如何准备;   
4、回国时间很长,忘记办;   
5、回国马上就要找工作,办给用人单位看;  
6、企事业单位必须要求办的; 
德国大学:慕尼黑工业大学,哥廷根大学,慕尼黑大学,开姆尼茨工业大学,卡尔斯鲁厄大学,达姆斯塔特工业大学,明斯特大学,弗赖堡大学,多特蒙德工业大学,马堡大学,杜塞尔多夫大学,波鸿鲁尔大学,布伦瑞克工业大学,奥格斯堡大学,杜伊斯堡埃森大学,凯撒斯劳滕工业大学,法兰克福大学,亚琛工业大学,斯图加特大学,汉诺威大学,基尔大学,柏林自由大学,柏林工业大学,吉森大学,纽伦堡大学,莱比锡大学,美因茨大学,乌尔兹堡大学,萨尔大学,科隆大学,不来梅大学,奥登堡大学,安哈尔特应用技术大学,波恩大学,勃兰登堡工业大学,德累斯顿工业大学,汉堡大学,柏林洪堡大学,卡塞尔大学,克劳斯塔尔工业大学,罗斯托克大学,耶拿应用技术大学,汉堡音乐和戏剧学院,鲁昂大学,克莱蒙费朗一大,克莱蒙费朗第二大学,萨瓦大学,佩皮尼昂大学,南布列塔尼大学,巴黎第一大学,第戎大学,国立里昂第二大学,格勒诺布尔第三大学,凡尔赛大学,巴黎第九大学,马赛大学,昂热大学,贝桑松大学,波城大学,滨海大学,科西嘉大学,尼斯大学,巴黎第八大学,南锡一大,雷恩一大,巴黎第四大学,卡昂大学,蒙彼利埃三大,蒙彼利埃第一大学,图尔大学,INSEEC,图卢兹第一大学,图卢兹第三大学,巴黎第四大学索邦大学,斯特拉斯堡大学,图卢兹三大,波尔多一大,里尔第三大学,里昂三大,奥尔良大学,亚眠大学,罗马二大,米兰大学,马兰欧尼,办理德国毕业证文凭学历认证成绩单留学回国证明
 
 
英国大学:
纽卡斯尔大学,帝国理工学院,巴斯大学,埃克塞特大学,伦敦大学学院UCL,华威大学,约克大学,兰卡斯特大学,萨里大学,莱斯特大学,布里斯托大学,伯明翰大学,格鲁斯特大学,谢菲尔德大学,南安普顿大学,拉夫堡大学,爱丁堡大学,诺丁汉大学,伦敦大学亚非学院SOAS,格拉斯哥大学,曼彻斯特大学,伦敦国王学院KCL,皇家霍洛威大学RHUL,阿斯顿大学,利兹大学,萨塞克斯大学,卡迪夫大学,伦敦艺术大学,雷丁大学,肯特大学,利物浦大学,伦敦玛丽女王学院QMUL,赫瑞瓦特大学,埃塞克斯大学,阿伯丁大学,伦敦城市大学,斯特拉思克莱德大学,基尔大学,考文垂大学,斯旺西大学,邓迪大学,阿伯泰大学,切斯特大学,朴茨茅斯大学,威尔士班戈大学,林肯大学,布拉德福德大学,北安普顿大学,诺丁汉特伦特大学,诺森比亚大学,赫尔大学,约克圣约翰大学,哈德斯菲尔德大学,伯恩茅斯大学,伦敦商学院大学,罗汉普顿大学,爱丁堡玛格丽特皇后学院,格林威治大学,赫特福德大学,布鲁内尔大学,德蒙福特大学,罗伯特戈登大学RGU,索尔福德大学,桑德兰大学,威斯敏斯特大学,南岸大学,圣安德鲁斯大学,普利茅斯大学,牛津布鲁克斯大学,伯明翰城市大学BCU,办理英国毕业证文凭学历认证成绩单留学回国证明
 
 
澳洲大学:
梅西大学,林肯大学,奥塔哥大学,奥克兰理工大学AUT,怀卡托大学,基督城理工学院CPIT,马努卡理工学院,坎特伯雷大学,奥克兰大学,奥克兰商学院AIS,悉尼大学USYD,新南威尔士大学UNSW,查尔斯达尔文大学CDU,澳大利亚联邦大学,斯威本科技大学Swinburne,巴拉瑞特大学ballarat,RMIT,墨尔本大学,阿德莱德大学Adelaide,莫纳什大学Monash,昆士兰大学UQ,西澳大学UWA,澳大利亚国立大学ANU,麦考瑞大学Macquarie,纽卡斯尔大学,卧龙岗大学
Wollongong,格里菲斯大学Griffith,弗林德斯大学Flinders,塔斯马尼亚大学UTAS,堪培拉大学,邦德大学Bond,迪肯大学Deakin,悉尼科技大学UTS,科廷大学Curtin,墨尔本皇家理工学院RMIT,昆士兰科技大学QUT,拉筹伯大学La Trobe,莫道克大学Murdoch,澳洲TAFE,南澳大学UniSA,中央昆士兰大学CQU,詹姆斯库克大学JCU,新英格兰大学UNE,南昆士兰大学USQ,埃迪斯科文大学ECU,南十字星大学SCU,阳光海岸大学,维多利亚大学Victoria,办理澳洲毕业证文凭学历认证成绩单留学回国证明
 
 
美国大学:
俄亥俄州立大学OSU,加州大学洛杉矶分校UCLA,华盛顿州立大学WSU,普渡大学Purdue,俄勒冈大学Oregon,纽约大学NYU,西雅图大学Seattle,南加州大学USC,宾州州立大学PSU,匹兹堡大学PITT,加州理工学院CIT,西北大学NWU,
宾夕法尼亚大学Pennsylvania,密歇根州立大学MSU,密歇根大学UMICH,波士顿大学BU,德克萨斯大学奥斯汀分校utexas,哈佛大学Harvard,麻省理工学院MIT,田纳西大学UTK,帕森斯设计学院Parsons,佩斯大学Pace,普林斯顿大学Princeton,托莱多大学Toledo,杜克大学Duke,芝加哥大学Chicago,达特茅斯学院Dartmouth,拉文大学La Verne,康奈尔大学Cornell,约翰霍普金斯大学JHU,布朗大学Brown,莱斯大学Rice,埃默里大学Emory,圣母大学,范德堡大学Vandy,加州大学伯克利分校UCB,卡内基梅隆大学CMU,乔治城大学Georgetown,弗吉尼亚大学UVa,塔夫斯大学Tufts,维克森林大学WFU,布兰迪斯大学Brandeis,威廉玛丽学院wm,波士顿学院BC,佐治亚理工学院Gatech,利哈伊大学Lehigh,罗切斯特大学Rochester,威斯康星大学WISC,内布拉斯加大学UNL,伦斯勒理工学院RPI,华盛顿大学UW,加州大学戴维斯分校UCD,加州大学欧文分校UCI,加州大学UCSB,天普大学Temple,叶史瓦大学Yeshiva,肯塔基大学Kentucky,乔治华盛顿大学GWU,雪城大学SU,马里兰大学UMD,佩珀代因大学Pepperdine,乔治亚大学UGA,克莱姆森大学Clemson,福特汉姆大学Fordham,明尼苏达大学UMN,迈阿密大学UM,密西西比大学Mississippi,南卫理公会大学SMU,康涅狄格大学Connecticut,爱荷华大学Iowa,印地安那大学伯明顿分校IUB,特拉华大学UD,伍斯特理工学院WPI,贝勒大学baylor,马凯特大学Marquette,纽约宾汉姆顿大学binghamton,纽约奥尔巴尼大学Albany,克拉克大学Clark,科罗拉多大学CU-Boulder,密苏里大学Missouri,堪萨斯大学KU,北卡罗来纳州立大学NCSU,俄亥俄大学ohio,俄克拉荷马大学Oklahoma,德州A&M大学TAMU,佛罗里达州立大学FSU,斯坦福大学Stanford,加州大学圣地亚哥分校UCSD,凤凰城大学UPX,旧金山大学USF,休斯敦大学UH,斯蒂文斯理工学院SIT,阿拉巴马大学Alabama,塔尔萨大学TU,德雷塞尔大学Drexel,爱荷华州立大学ISU,加州大学河滨分校UCR,丹佛大学DU,堪萨斯州立大学KSU,罗格斯大学Rutgers,佛蒙特大学UVM,奥本大学Auburn,美国东北大学NEU,纽约州立大学石溪分校SBU,亚利桑那州立大学ASU,亚利桑那大学UA,加州大学圣克鲁兹分校UCSC,纽约州立大学布法罗分校Buffalo,纽约理工学院NYIT,北卡罗莱纳大学UNC,罗德岛设计学院RISD,加州旧金山分校UCSF,伊利诺伊大学香槟分校UIUC,杜兰大学Tulane,社区大学College,阿肯色大学,犹他大学,杨百翰大学BYU,辛辛那提大学,霍夫斯特拉大学,肯特州立大学,旧金山艺术大学AAU,莱特州立大学,欧道明大学,罗德岛大学URI,南达科他州立大学,阿肯色大学小石城分校,长岛大学布鲁克林校区,海斯堡州立大学FHSU,圣莫尼卡社区学院SMC,办理美国毕业证文凭学历认证成绩单留学回国证明
 
 
加拿大大学:
多伦多大学,约克大学York,渥太华大学,西安大略大学UWO,滑铁卢大学,麦克马斯特大学McMaster,卡尔顿大学,皇后大学,布鲁克大学,湖首大学,BCIT,温莎大学,瑞尔森大学,百年理工学院,特伦特大学,英属哥伦比亚大学UBC,西蒙菲莎大学SFU,昆特兰理工大学KPU,汤普森河大学TRU,西三一大学,阿尔伯塔大学,卡尔加里大学,里贾纳大学,阿卡迪亚大学,达尔豪斯大学,圣玛丽大学,麦吉尔大学,康考迪亚大学,菲沙河谷大学UFV,蒙特利尔大学,温尼伯大学,曼尼托巴大学,毕业证加拿大文凭学历认证成绩单留学回国证明
未正常毕业*UC坎特伯雷大学毕业`等证件`材料证书
作者:qiangpfeg 发表于2016/12/12 20:08:33 原文链接
阅读:14 评论:0 查看评论

未正常毕业*UWICc毕业`等证件`材料证书

$
0
0
*卡迪夫城市大学毕业证】QQ/微信:549585563  联系人:Kimi 代办国外(海外)澳洲英国 加拿大 韩国 美国 新西兰 等各大学毕业证,修改成绩单分数,学历认证,文凭,diploma,degree
 
 
 
欢迎咨询!QQ/微信:549585563,专业为留学生提供毕业证、学历认证、文凭、学位证、学位证书、使馆公证、国外学历学位教育部认证、使馆留学回国人员证明、录取通知书、Offer、在读证明、雅思托福成绩单、网上存档永久可查!【实体公司,寰宇教育,值得信赖】
  
   一、【主营项目】:
1.毕业证、成绩单、使馆认证、教育部认证、留信、学生卡-- OFFER等.等!
 
2.办理真实使馆公证(即留学回国人员证明,不成功不收费)
 
3.教育部国外学历学位认证咨询。(网上可查、永久存档、快速稳妥)
 
4.办理各国各大学文凭(一对一专业服务,可全程监控跟踪进度)








咨询顾问:Kimi QQ/微信:549585563 
 
1、挂科了,不想读了,成绩不理想怎么办??
2、找工作没有文凭怎么办?有本科却要求硕士又怎么办?
3、打算回国了,找工作的时候,需要提供认证,有文凭却得不到认证。又该怎么办???
 
如果您是以下情况,我们都能竭诚为您解决实际问题:
1、在校期间,因各种原因未能顺利毕业,拿不到官方毕业证; 
2、面对父母的压力,希望尽快拿到;   
3、不清楚流程以及材料该如何准备;   
4、回国时间很长,忘记办;   
5、回国马上就要找工作,办给用人单位看;  
6、企事业单位必须要求办的; 
德国大学:慕尼黑工业大学,哥廷根大学,慕尼黑大学,开姆尼茨工业大学,卡尔斯鲁厄大学,达姆斯塔特工业大学,明斯特大学,弗赖堡大学,多特蒙德工业大学,马堡大学,杜塞尔多夫大学,波鸿鲁尔大学,布伦瑞克工业大学,奥格斯堡大学,杜伊斯堡埃森大学,凯撒斯劳滕工业大学,法兰克福大学,亚琛工业大学,斯图加特大学,汉诺威大学,基尔大学,柏林自由大学,柏林工业大学,吉森大学,纽伦堡大学,莱比锡大学,美因茨大学,乌尔兹堡大学,萨尔大学,科隆大学,不来梅大学,奥登堡大学,安哈尔特应用技术大学,波恩大学,勃兰登堡工业大学,德累斯顿工业大学,汉堡大学,柏林洪堡大学,卡塞尔大学,克劳斯塔尔工业大学,罗斯托克大学,耶拿应用技术大学,汉堡音乐和戏剧学院,鲁昂大学,克莱蒙费朗一大,克莱蒙费朗第二大学,萨瓦大学,佩皮尼昂大学,南布列塔尼大学,巴黎第一大学,第戎大学,国立里昂第二大学,格勒诺布尔第三大学,凡尔赛大学,巴黎第九大学,马赛大学,昂热大学,贝桑松大学,波城大学,滨海大学,科西嘉大学,尼斯大学,巴黎第八大学,南锡一大,雷恩一大,巴黎第四大学,卡昂大学,蒙彼利埃三大,蒙彼利埃第一大学,图尔大学,INSEEC,图卢兹第一大学,图卢兹第三大学,巴黎第四大学索邦大学,斯特拉斯堡大学,图卢兹三大,波尔多一大,里尔第三大学,里昂三大,奥尔良大学,亚眠大学,罗马二大,米兰大学,马兰欧尼,办理德国毕业证文凭学历认证成绩单留学回国证明
 
 
英国大学:
纽卡斯尔大学,帝国理工学院,巴斯大学,埃克塞特大学,伦敦大学学院UCL,华威大学,约克大学,兰卡斯特大学,萨里大学,莱斯特大学,布里斯托大学,伯明翰大学,格鲁斯特大学,谢菲尔德大学,南安普顿大学,拉夫堡大学,爱丁堡大学,诺丁汉大学,伦敦大学亚非学院SOAS,格拉斯哥大学,曼彻斯特大学,伦敦国王学院KCL,皇家霍洛威大学RHUL,阿斯顿大学,利兹大学,萨塞克斯大学,卡迪夫大学,伦敦艺术大学,雷丁大学,肯特大学,利物浦大学,伦敦玛丽女王学院QMUL,赫瑞瓦特大学,埃塞克斯大学,阿伯丁大学,伦敦城市大学,斯特拉思克莱德大学,基尔大学,考文垂大学,斯旺西大学,邓迪大学,阿伯泰大学,切斯特大学,朴茨茅斯大学,威尔士班戈大学,林肯大学,布拉德福德大学,北安普顿大学,诺丁汉特伦特大学,诺森比亚大学,赫尔大学,约克圣约翰大学,哈德斯菲尔德大学,伯恩茅斯大学,伦敦商学院大学,罗汉普顿大学,爱丁堡玛格丽特皇后学院,格林威治大学,赫特福德大学,布鲁内尔大学,德蒙福特大学,罗伯特戈登大学RGU,索尔福德大学,桑德兰大学,威斯敏斯特大学,南岸大学,圣安德鲁斯大学,普利茅斯大学,牛津布鲁克斯大学,伯明翰城市大学BCU,办理英国毕业证文凭学历认证成绩单留学回国证明
 
 
澳洲大学:
梅西大学,林肯大学,奥塔哥大学,奥克兰理工大学AUT,怀卡托大学,基督城理工学院CPIT,马努卡理工学院,坎特伯雷大学,奥克兰大学,奥克兰商学院AIS,悉尼大学USYD,新南威尔士大学UNSW,查尔斯达尔文大学CDU,澳大利亚联邦大学,斯威本科技大学Swinburne,巴拉瑞特大学ballarat,RMIT,墨尔本大学,阿德莱德大学Adelaide,莫纳什大学Monash,昆士兰大学UQ,西澳大学UWA,澳大利亚国立大学ANU,麦考瑞大学Macquarie,纽卡斯尔大学,卧龙岗大学
Wollongong,格里菲斯大学Griffith,弗林德斯大学Flinders,塔斯马尼亚大学UTAS,堪培拉大学,邦德大学Bond,迪肯大学Deakin,悉尼科技大学UTS,科廷大学Curtin,墨尔本皇家理工学院RMIT,昆士兰科技大学QUT,拉筹伯大学La Trobe,莫道克大学Murdoch,澳洲TAFE,南澳大学UniSA,中央昆士兰大学CQU,詹姆斯库克大学JCU,新英格兰大学UNE,南昆士兰大学USQ,埃迪斯科文大学ECU,南十字星大学SCU,阳光海岸大学,维多利亚大学Victoria,办理澳洲毕业证文凭学历认证成绩单留学回国证明
 
 
美国大学:
俄亥俄州立大学OSU,加州大学洛杉矶分校UCLA,华盛顿州立大学WSU,普渡大学Purdue,俄勒冈大学Oregon,纽约大学NYU,西雅图大学Seattle,南加州大学USC,宾州州立大学PSU,匹兹堡大学PITT,加州理工学院CIT,西北大学NWU,
宾夕法尼亚大学Pennsylvania,密歇根州立大学MSU,密歇根大学UMICH,波士顿大学BU,德克萨斯大学奥斯汀分校utexas,哈佛大学Harvard,麻省理工学院MIT,田纳西大学UTK,帕森斯设计学院Parsons,佩斯大学Pace,普林斯顿大学Princeton,托莱多大学Toledo,杜克大学Duke,芝加哥大学Chicago,达特茅斯学院Dartmouth,拉文大学La Verne,康奈尔大学Cornell,约翰霍普金斯大学JHU,布朗大学Brown,莱斯大学Rice,埃默里大学Emory,圣母大学,范德堡大学Vandy,加州大学伯克利分校UCB,卡内基梅隆大学CMU,乔治城大学Georgetown,弗吉尼亚大学UVa,塔夫斯大学Tufts,维克森林大学WFU,布兰迪斯大学Brandeis,威廉玛丽学院wm,波士顿学院BC,佐治亚理工学院Gatech,利哈伊大学Lehigh,罗切斯特大学Rochester,威斯康星大学WISC,内布拉斯加大学UNL,伦斯勒理工学院RPI,华盛顿大学UW,加州大学戴维斯分校UCD,加州大学欧文分校UCI,加州大学UCSB,天普大学Temple,叶史瓦大学Yeshiva,肯塔基大学Kentucky,乔治华盛顿大学GWU,雪城大学SU,马里兰大学UMD,佩珀代因大学Pepperdine,乔治亚大学UGA,克莱姆森大学Clemson,福特汉姆大学Fordham,明尼苏达大学UMN,迈阿密大学UM,密西西比大学Mississippi,南卫理公会大学SMU,康涅狄格大学Connecticut,爱荷华大学Iowa,印地安那大学伯明顿分校IUB,特拉华大学UD,伍斯特理工学院WPI,贝勒大学baylor,马凯特大学Marquette,纽约宾汉姆顿大学binghamton,纽约奥尔巴尼大学Albany,克拉克大学Clark,科罗拉多大学CU-Boulder,密苏里大学Missouri,堪萨斯大学KU,北卡罗来纳州立大学NCSU,俄亥俄大学ohio,俄克拉荷马大学Oklahoma,德州A&M大学TAMU,佛罗里达州立大学FSU,斯坦福大学Stanford,加州大学圣地亚哥分校UCSD,凤凰城大学UPX,旧金山大学USF,休斯敦大学UH,斯蒂文斯理工学院SIT,阿拉巴马大学Alabama,塔尔萨大学TU,德雷塞尔大学Drexel,爱荷华州立大学ISU,加州大学河滨分校UCR,丹佛大学DU,堪萨斯州立大学KSU,罗格斯大学Rutgers,佛蒙特大学UVM,奥本大学Auburn,美国东北大学NEU,纽约州立大学石溪分校SBU,亚利桑那州立大学ASU,亚利桑那大学UA,加州大学圣克鲁兹分校UCSC,纽约州立大学布法罗分校Buffalo,纽约理工学院NYIT,北卡罗莱纳大学UNC,罗德岛设计学院RISD,加州旧金山分校UCSF,伊利诺伊大学香槟分校UIUC,杜兰大学Tulane,社区大学College,阿肯色大学,犹他大学,杨百翰大学BYU,辛辛那提大学,霍夫斯特拉大学,肯特州立大学,旧金山艺术大学AAU,莱特州立大学,欧道明大学,罗德岛大学URI,南达科他州立大学,阿肯色大学小石城分校,长岛大学布鲁克林校区,海斯堡州立大学FHSU,圣莫尼卡社区学院SMC,办理美国毕业证文凭学历认证成绩单留学回国证明
 
 
加拿大大学:
多伦多大学,约克大学York,渥太华大学,西安大略大学UWO,滑铁卢大学,麦克马斯特大学McMaster,卡尔顿大学,皇后大学,布鲁克大学,湖首大学,BCIT,温莎大学,瑞尔森大学,百年理工学院,特伦特大学,英属哥伦比亚大学UBC,西蒙菲莎大学SFU,昆特兰理工大学KPU,汤普森河大学TRU,西三一大学,阿尔伯塔大学,卡尔加里大学,里贾纳大学,阿卡迪亚大学,达尔豪斯大学,圣玛丽大学,麦吉尔大学,康考迪亚大学,菲沙河谷大学UFV,蒙特利尔大学,温尼伯大学,曼尼托巴大学,毕业证加拿大文凭学历认证成绩单留学回国证明
未正常毕业*UWIC卡迪夫城市大学毕业`等证件`材料证书
作者:qiangpfeg 发表于2016/12/12 20:08:48 原文链接
阅读:13 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>