原帖看:
http://www.eoeandroid.com/thread-70194-1-1.html
或者看这个:
android中的左右滑动
http://www.iteye.com/topic/1102128
之所以说其不完美是因为FlingGallery使用到了Adapter,这样会造成各界面的一致性,比较单一.如果能随便什么界面都能拖动就完美了.(什么?想要完美?我的博客里找!)
不过它的循环显示效果不错.
下面是它的源码:
import android.content.Context;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.Transformation;
import android.widget.Adapter;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
// TODO:
// 1. In order to improve performance Cache screen bitmap and use for animation
// 2. Establish superfluous memory allocations and delay or replace with reused objects
// Probably need to make sure we are not allocating objects (strings, etc.) in loops
public class FlingGallery extends FrameLayout
{
// Constants
private final int swipe_min_distance = 120;
private final int swipe_max_off_path = 250;
private final int swipe_threshold_veloicty = 400;
// Properties
private int mViewPaddingWidth = 0;
private int mAnimationDuration = 250;
private float mSnapBorderRatio = 0.5f;
private boolean mIsGalleryCircular = true;
// Members
private int mGalleryWidth = 0;
private boolean mIsTouched = false;
private boolean mIsDragging = false;
private float mCurrentOffset = 0.0f;
private long mScrollTimestamp = 0;
private int mFlingDirection = 0;
private int mCurrentPosition = 0;
private int mCurrentViewNumber = 0;
private Context mContext;
private Adapter mAdapter;
private FlingGalleryView[] mViews;
private FlingGalleryAnimation mAnimation;
private GestureDetector mGestureDetector;
private Interpolator mDecelerateInterpolater;
public FlingGallery(Context context)
{
super(context);
mContext = context;
mAdapter = null;
mViews = new FlingGalleryView[3];
mViews[0] = new FlingGalleryView(0, this);
mViews[1] = new FlingGalleryView(1, this);
mViews[2] = new FlingGalleryView(2, this);
mAnimation = new FlingGalleryAnimation();
mGestureDetector = new GestureDetector(new FlingGestureDetector());
mDecelerateInterpolater = AnimationUtils.loadInterpolator(mContext, android.R.anim.decelerate_interpolator);
}
public void setPaddingWidth(int viewPaddingWidth)
{
mViewPaddingWidth = viewPaddingWidth;
}
public void setAnimationDuration(int animationDuration)
{
mAnimationDuration = animationDuration;
}
public void setSnapBorderRatio(float snapBorderRatio)
{
mSnapBorderRatio = snapBorderRatio;
}
public void setIsGalleryCircular(boolean isGalleryCircular)
{
if (mIsGalleryCircular != isGalleryCircular)
{
mIsGalleryCircular = isGalleryCircular;
if (mCurrentPosition == getFirstPosition())
{
// We need to reload the view immediately to the left to change it to circular view or blank
mViews[getPrevViewNumber(mCurrentViewNumber)].recycleView(getPrevPosition(mCurrentPosition));
}
if (mCurrentPosition == getLastPosition())
{
// We need to reload the view immediately to the right to change it to circular view or blank
mViews[getNextViewNumber(mCurrentViewNumber)].recycleView(getNextPosition(mCurrentPosition));
}
}
}
public int getGalleryCount()
{
return (mAdapter == null) ? 0 : mAdapter.getCount();
}
public int getFirstPosition()
{
return 0;
}
public int getLastPosition()
{
return (getGalleryCount() == 0) ? 0 : getGalleryCount() - 1;
}
private int getPrevPosition(int relativePosition)
{
int prevPosition = relativePosition - 1;
if (prevPosition < getFirstPosition())
{
prevPosition = getFirstPosition() - 1;
if (mIsGalleryCircular == true)
{
prevPosition = getLastPosition();
}
}
return prevPosition;
}
private int getNextPosition(int relativePosition)
{
int nextPosition = relativePosition + 1;
if (nextPosition > getLastPosition())
{
nextPosition = getLastPosition() + 1;
if (mIsGalleryCircular == true)
{
nextPosition = getFirstPosition();
}
}
return nextPosition;
}
private int getPrevViewNumber(int relativeViewNumber)
{
return (relativeViewNumber == 0) ? 2 : relativeViewNumber - 1;
}
private int getNextViewNumber(int relativeViewNumber)
{
return (relativeViewNumber == 2) ? 0 : relativeViewNumber + 1;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom)
{
super.onLayout(changed, left, top, right, bottom);
// Calculate our view width
mGalleryWidth = right - left;
if (changed == true)
{
// Position views at correct starting offsets
mViews[0].setOffset(0, 0, mCurrentViewNumber);
mViews[1].setOffset(0, 0, mCurrentViewNumber);
mViews[2].setOffset(0, 0, mCurrentViewNumber);
}
}
public void setAdapter(Adapter adapter)
{
mAdapter = adapter;
mCurrentPosition = 0;
mCurrentViewNumber = 0;
// Load the initial views from adapter
mViews[0].recycleView(mCurrentPosition);
mViews[1].recycleView(getNextPosition(mCurrentPosition));
mViews[2].recycleView(getPrevPosition(mCurrentPosition));
// Position views at correct starting offsets
mViews[0].setOffset(0, 0, mCurrentViewNumber);
mViews[1].setOffset(0, 0, mCurrentViewNumber);
mViews[2].setOffset(0, 0, mCurrentViewNumber);
}
private int getViewOffset(int viewNumber, int relativeViewNumber)
{
// Determine width including configured padding width
int offsetWidth = mGalleryWidth + mViewPaddingWidth;
// Position the previous view one measured width to left
if (viewNumber == getPrevViewNumber(relativeViewNumber))
{
return offsetWidth;
}
// Position the next view one measured width to the right
if (viewNumber == getNextViewNumber(relativeViewNumber))
{
return offsetWidth * -1;
}
return 0;
}
void movePrevious()
{
// Slide to previous view
mFlingDirection = 1;
processGesture();
}
void moveNext()
{
// Slide to next view
mFlingDirection = -1;
processGesture();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
switch (keyCode)
{
case KeyEvent.KEYCODE_DPAD_LEFT:
movePrevious();
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
moveNext();
return true;
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
}
return super.onKeyDown(keyCode, event);
}
public boolean onGalleryTouchEvent(MotionEvent event)
{
boolean consumed = mGestureDetector.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_UP)
{
if (mIsTouched || mIsDragging)
{
processScrollSnap();
processGesture();
}
}
return consumed;
}
void processGesture()
{
int newViewNumber = mCurrentViewNumber;
int reloadViewNumber = 0;
int reloadPosition = 0;
mIsTouched = false;
mIsDragging = false;
if (mFlingDirection > 0)
{
if (mCurrentPosition > getFirstPosition() || mIsGalleryCircular == true)
{
// Determine previous view and outgoing view to recycle
newViewNumber = getPrevViewNumber(mCurrentViewNumber);
mCurrentPosition = getPrevPosition(mCurrentPosition);
reloadViewNumber = getNextViewNumber(mCurrentViewNumber);
reloadPosition = getPrevPosition(mCurrentPosition);
}
}
if (mFlingDirection < 0)
{
if (mCurrentPosition < getLastPosition() || mIsGalleryCircular == true)
{
// Determine the next view and outgoing view to recycle
newViewNumber = getNextViewNumber(mCurrentViewNumber);
mCurrentPosition = getNextPosition(mCurrentPosition);
reloadViewNumber = getPrevViewNumber(mCurrentViewNumber);
reloadPosition = getNextPosition(mCurrentPosition);
}
}
if (newViewNumber != mCurrentViewNumber)
{
mCurrentViewNumber = newViewNumber;
// Reload outgoing view from adapter in new position
mViews[reloadViewNumber].recycleView(reloadPosition);
}
// Ensure input focus on the current view
mViews[mCurrentViewNumber].requestFocus();
// Run the slide animations for view transitions
mAnimation.prepareAnimation(mCurrentViewNumber);
this.startAnimation(mAnimation);
// Reset fling state
mFlingDirection = 0;
}
void processScrollSnap()
{
// Snap to next view if scrolled passed snap position
float rollEdgeWidth = mGalleryWidth * mSnapBorderRatio;
int rollOffset = mGalleryWidth - (int) rollEdgeWidth;
int currentOffset = mViews[mCurrentViewNumber].getCurrentOffset();
if (currentOffset <= rollOffset * -1)
{
// Snap to previous view
mFlingDirection = 1;
}
if (currentOffset >= rollOffset)
{
// Snap to next view
mFlingDirection = -1;
}
}
private class FlingGalleryView
{
private int mViewNumber;
private FrameLayout mParentLayout;
private FrameLayout mInvalidLayout = null;
private LinearLayout mInternalLayout = null;
private View mExternalView = null;
public FlingGalleryView(int viewNumber, FrameLayout parentLayout)
{
mViewNumber = viewNumber;
mParentLayout = parentLayout;
// Invalid layout is used when outside gallery
mInvalidLayout = new FrameLayout(mContext);
mInvalidLayout.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
// Internal layout is permanent for duration
mInternalLayout = new LinearLayout(mContext);
mInternalLayout.setLayoutParams(new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mParentLayout.addView(mInternalLayout);
}
public void recycleView(int newPosition)
{
if (mExternalView != null)
{
mInternalLayout.removeView(mExternalView);
}
if (mAdapter != null)
{
if (newPosition >= getFirstPosition() && newPosition <= getLastPosition())
{
mExternalView = mAdapter.getView(newPosition, mExternalView, mInternalLayout);
}
else
{
mExternalView = mInvalidLayout;
}
}
if (mExternalView != null)
{
mInternalLayout.addView(mExternalView, new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
}
public void setOffset(int xOffset, int yOffset, int relativeViewNumber)
{
// Scroll the target view relative to its own position relative to currently displayed view
mInternalLayout.scrollTo(getViewOffset(mViewNumber, relativeViewNumber) + xOffset, yOffset);
}
public int getCurrentOffset()
{
// Return the current scroll position
return mInternalLayout.getScrollX();
}
public void requestFocus()
{
mInternalLayout.requestFocus();
}
}
private class FlingGalleryAnimation extends Animation
{
private boolean mIsAnimationInProgres;
private int mRelativeViewNumber;
private int mInitialOffset;
private int mTargetOffset;
private int mTargetDistance;
public FlingGalleryAnimation()
{
mIsAnimationInProgres = false;
mRelativeViewNumber = 0;
mInitialOffset = 0;
mTargetOffset = 0;
mTargetDistance = 0;
}
public void prepareAnimation(int relativeViewNumber)
{
// If we are animating relative to a new view
if (mRelativeViewNumber != relativeViewNumber)
{
if (mIsAnimationInProgres == true)
{
// We only have three views so if requested again to animate in same direction we must snap
int newDirection = (relativeViewNumber == getPrevViewNumber(mRelativeViewNumber)) ? 1 : -1;
int animDirection = (mTargetDistance < 0) ? 1 : -1;
// If animation in same direction
if (animDirection == newDirection)
{
// Ran out of time to animate so snap to the target offset
mViews[0].setOffset(mTargetOffset, 0, mRelativeViewNumber);
mViews[1].setOffset(mTargetOffset, 0, mRelativeViewNumber);
mViews[2].setOffset(mTargetOffset, 0, mRelativeViewNumber);
}
}
// Set relative view number for animation
mRelativeViewNumber = relativeViewNumber;
}
// Note: In this implementation the targetOffset will always be zero
// as we are centering the view; but we include the calculations of
// targetOffset and targetDistance for use in future implementations
mInitialOffset = mViews[mRelativeViewNumber].getCurrentOffset();
mTargetOffset = getViewOffset(mRelativeViewNumber, mRelativeViewNumber);
mTargetDistance = mTargetOffset - mInitialOffset;
// Configure base animation properties
this.setDuration(mAnimationDuration);
this.setInterpolator(mDecelerateInterpolater);
// Start/continued animation
mIsAnimationInProgres = true;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation transformation)
{
// Ensure interpolatedTime does not over-shoot then calculate new offset
interpolatedTime = (interpolatedTime > 1.0f) ? 1.0f : interpolatedTime;
int offset = mInitialOffset + (int) (mTargetDistance * interpolatedTime);
for (int viewNumber = 0; viewNumber < 3; viewNumber++)
{
// Only need to animate the visible views as the other view will always be off-screen
if ((mTargetDistance > 0 && viewNumber != getNextViewNumber(mRelativeViewNumber)) ||
(mTargetDistance < 0 && viewNumber != getPrevViewNumber(mRelativeViewNumber)))
{
mViews[viewNumber].setOffset(offset, 0, mRelativeViewNumber);
}
}
}
@Override
public boolean getTransformation(long currentTime, Transformation outTransformation)
{
if (super.getTransformation(currentTime, outTransformation) == false)
{
// Perform final adjustment to offsets to cleanup animation
mViews[0].setOffset(mTargetOffset, 0, mRelativeViewNumber);
mViews[1].setOffset(mTargetOffset, 0, mRelativeViewNumber);
mViews[2].setOffset(mTargetOffset, 0, mRelativeViewNumber);
// Reached the animation target
mIsAnimationInProgres = false;
return false;
}
// Cancel if the screen touched
if (mIsTouched || mIsDragging)
{
// Note that at this point we still consider ourselves to be animating
// because we have not yet reached the target offset; its just that the
// user has temporarily interrupted the animation with a touch gesture
return false;
}
return true;
}
}
private class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener
{
@Override
public boolean onDown(MotionEvent e)
{
// Stop animation
mIsTouched = true;
// Reset fling state
mFlingDirection = 0;
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
{
if (e2.getAction() == MotionEvent.ACTION_MOVE)
{
if (mIsDragging == false)
{
// Stop animation
mIsTouched = true;
// Reconfigure scroll
mIsDragging = true;
mFlingDirection = 0;
mScrollTimestamp = System.currentTimeMillis();
mCurrentOffset = mViews[mCurrentViewNumber].getCurrentOffset();
}
float maxVelocity = mGalleryWidth / (mAnimationDuration / 1000.0f);
long timestampDelta = System.currentTimeMillis() - mScrollTimestamp;
float maxScrollDelta = maxVelocity * (timestampDelta / 1000.0f);
float currentScrollDelta = e1.getX() - e2.getX();
if (currentScrollDelta < maxScrollDelta * -1) currentScrollDelta = maxScrollDelta * -1;
if (currentScrollDelta > maxScrollDelta) currentScrollDelta = maxScrollDelta;
int scrollOffset = Math.round(mCurrentOffset + currentScrollDelta);
// We can't scroll more than the width of our own frame layout
if (scrollOffset >= mGalleryWidth) scrollOffset = mGalleryWidth;
if (scrollOffset <= mGalleryWidth * -1) scrollOffset = mGalleryWidth * -1;
mViews[0].setOffset(scrollOffset, 0, mCurrentViewNumber);
mViews[1].setOffset(scrollOffset, 0, mCurrentViewNumber);
mViews[2].setOffset(scrollOffset, 0, mCurrentViewNumber);
}
return false;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
if (Math.abs(e1.getY() - e2.getY()) <= swipe_max_off_path)
{
if (e2.getX() - e1.getX() > swipe_min_distance && Math.abs(velocityX) > swipe_threshold_veloicty)
{
movePrevious();
}
if(e1.getX() - e2.getX() > swipe_min_distance && Math.abs(velocityX) > swipe_threshold_veloicty)
{
moveNext();
}
}
return false;
}
@Override
public void onLongPress(MotionEvent e)
{
// Finalise scrolling
mFlingDirection = 0;
processGesture();
}
@Override
public void onShowPress(MotionEvent e)
{
}
@Override
public boolean onSingleTapUp(MotionEvent e)
{
// Reset fling state
mFlingDirection = 0;
return false;
}
}
}
测试代码:
import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.graphics.Color;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TextView;
public class FlingGalleryActivity extends Activity {
private final String[] mLabelArray = { "View1", "View2", "View3", "View4",
"View5" };
private final int[] mColorArray = { Color.argb(100, 200, 0, 0),
Color.argb(100, 0, 200, 0), Color.argb(100, 0, 0, 200),
Color.argb(100, 200, 200, 0), Color.argb(100, 200, 0, 200) };
private FlingGallery mGallery;
private CheckBox mCheckBox;
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGallery.onGalleryTouchEvent(event);
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGallery = new FlingGallery(this);
mGallery.setPaddingWidth(5);
mGallery.setAnimationDuration(500);
mGallery.setAdapter(new ArrayAdapter<String>(getApplicationContext(),
android.R.layout.simple_list_item_1, mLabelArray) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.d("111", "count=" + position);
return new GalleryViewItem(getApplicationContext(), position);
}
});
LinearLayout layout = new LinearLayout(getApplicationContext());
layout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
layoutParams.setMargins(10, 10, 10, 10);
layoutParams.weight = 1.0f;
layout.addView(mGallery, layoutParams);
mCheckBox = new CheckBox(getApplicationContext());
mCheckBox.setText("Gallery is Circular");
mCheckBox.setPadding(50, 10, 0, 10);
mCheckBox.setTextSize(30);
mCheckBox.setChecked(true);
mCheckBox.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mGallery.setIsGalleryCircular(mCheckBox.isChecked());
}
});
layout.addView(mCheckBox, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
setContentView(layout);
}
private class GalleryViewItem extends TableLayout {
private EditText mEdit1;
private TextView mText1;
private TextView mText2;
private Button mButton1;
private Button mButton2;
public GalleryViewItem(Context context, int position) {
super(context);
this.setOrientation(LinearLayout.VERTICAL);
this.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));
mEdit1 = new EditText(context);
this.addView(mEdit1, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
mText1 = new TextView(context);
mText1.setText(mLabelArray[position]);
mText1.setTextSize(30);
mText1.setGravity(Gravity.LEFT);
mText1.setBackgroundColor(mColorArray[position]);
this.addView(mText1, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
mButton1 = new Button(context);
mButton1.setText("<<");
mButton1.setGravity(Gravity.LEFT);
mButton1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mGallery.movePrevious();
}
});
this.addView(mButton1, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
mButton2 = new Button(context);
mButton2.setText(">>");
mButton2.setGravity(Gravity.RIGHT);
mButton2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
mGallery.moveNext();
}
});
this.addView(mButton2, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
mText2 = new TextView(context);
mText2.setText(mLabelArray[position]);
mText2.setTextSize(30);
mText2.setGravity(Gravity.RIGHT);
mText2.setBackgroundColor(mColorArray[position]);
this.addView(mText2, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT, 1));
}
}
}
还有一个问题想请教下,我用了FlingGallery 这个类,每个子View 都是一张图片,我用了回调先加载模糊图片然后再加载清晰图片,但是加载未完成发生滚动时,子View就无法更新了?项目卡在这里了,请博主指点一下!
呵呵 你自己跑跑看不就知道了么 看起来是滑动的效果 就是真的滑动么 不能用动画么
编写:
1.编写一个类,继承android.content.ContentProvider
2.定义一个uri字符串,例如:
private static final String MY_URI = "com.test.dataContentProvider";
3.如果你的应用程序可以提供多个ContentProvider,需要写几个path,例如
private static final String MY_PATH1 = "path1"; private static final String MY_PATH2 = "path1";
4.定义与你的path对应的代码,当匹配成功后,会返回这个代码,我们这里使用
public static final int SUCCESS1 = 1; public static final int SUCCESS2 = 2;
5.将uri添加到UriMatcher,例
static
{
UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(MY_URI, MY_PATH1, SUCCESS1);
uriMatcher.addURI(MY_URI, MY_PATH2, SUCCESS2);
}
如果你没用到path,则addURI的第二个参数设置为null
6.编写ContentProvider的方法,这里举例query方法
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case SUCCESS1: {//这个对应content://com.test.dataContentProvider/path1
return null;
}
case SUCCESS2:{//这个对应content://com.test.dataContentProvider/path2 {
return null;
}
}
return null;
}
7.在AndroidManifest.xml中配置ContentProvider
<provider android:name="com.test.provider.DataContentProvider" android:authorities="com.test.dataContentProvider" />
com.test.provider.DataContentProvider是你的自定义的ContentProvider的路径,包括包名和类名
com.test.dataContentProvider是你在上面定义的MY_URI的值
使用:
1.定义要使用的ContentProvider的URI字符串,注意:与编写ContentProvider时不同,这时定义的URI是完整的URI地址
private static final String MY_URI = "content://com.test.dataContentProvider";
如果你使用path了,则这么写
private static final String MY_URI1 = "content://com.test.dataContentProvider/path1"; private static final String MY_URI2 = "content://com.test.dataContentProvider/path2";
2.得到URI对象
Uri uri = Uri.parse(MY_URI);
3.得到Cursor
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
在编写push notification之获取device token中拿到device token以后,需要把token字符串发送给应用的服务器端,即provider。
provider将token号、通知内容、通知形式(比如是否弹出提示窗口、是否发声等)发送给苹果的服务器(apns)。
最简单的provider实现,其实就是通过证书,和苹果服务器建立安全连接(tsl或ssl),通过认证建立连接后,向苹果服务器发送符合苹果要求的数据流。
获得证书
苹果提供两种接入方式的证书:
developer,用于测试
production,用于产品
如果是内部测试,使用developer方式即可。
下载证书,通过ios provisioning portal:
这要求:
登录的apple developer program帐号必须是级别最高的agent(这是针对企业帐号来说的,如果是个人帐号就无所谓了),agent帐号即创始帐号,否则看不到configure链接;
必须经过configure操作,已经enable了developer和product。
然后进入configure链接,点击download按钮即可:
处理证书
如果是编写在mac下跑的objc程序,无需对证书做处理,可跳过这一步。
如果是在java下使用,需要把打证书用的私有专用密钥和上述的支持通知的证书(注意,不是iphone developer证书)合并导出。
生成证书:
点击存储的时候,会提示生成一个文件密码:
当然可以密码为空。
之后会提示:
这里需要输入mac登录用户的密码。
文件生成。
编写发送通知的实例
如果是编写mac代码,有一个现成的项目可用:http://stefan.hafeneger.name/download/PushMeBabySource.zip
导入到xcode中,只需将:
deviceToken填写成设备的token字符串,另外,pathForResource改为上面图中的:
aps_developer_identity
另外,要把刚才获得证书步骤中下载的证书复制到xcode项目Resources目录下:
可以看到文件名和上面的pathForResource的参数一致。
之后运行程序就可以在设备上收到推送通知。
如果是用java编写,可以用第三方库,见:
http://code.google.com/p/javapns/
编写简单的发送通知代码:
import org.json.JSONException;
import javapns.back.PushNotificationManager;
import javapns.back.SSLConnectionHelper;
import javapns.data.Device;
import javapns.data.PayLoad;
public class Main {
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
PayLoad simplePayLoad = new PayLoad();
// Get PushNotification Instance
PushNotificationManager pushManager = PushNotificationManager.getInstance();
// Link iPhone’s UDID (64-char device token) to a stringName
pushManager.addDevice("iPhone", "00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 ");
simplePayLoad.addAlert("My alert message测试");
simplePayLoad.addBadge(1);
simplePayLoad.addSound("default");
Device client = PushNotificationManager.getInstance().getDevice("iPhone");
PushNotificationManager.getInstance().initializeConnection("gateway.sandbox.push.apple.com", 2195, "/home/ubuntu/mypush.p12", "password", SSLConnectionHelper.KEYSTORE_TYPE_PKCS12);
PushNotificationManager.getInstance().sendNotification(client, simplePayLoad);
测试中文没有乱码问题。
编写比较复杂的使用示例(可以控制通知是否有提示窗口、是否有提醒声音):
aPayload.addBadge( 2),显示在手机应用图标上的数字
aPayload.addAlert("软件版本有更新"),显示提示窗口文字
aPayload.addSound("default.wav"),指定提示声音
另外,也可以使用php的第三方实现,比如:
http://code.google.com/p/php-apns
基本原理是启动一个php服务,监控memcacheq队列,如果有消息就发送给苹果服务器