Android中主线程与子线程之间相互通信教程

 更新时间:2016年9月20日 19:54  点击:1913
Android开发语言是Java,所以也分主线程、子线程,那么我们如何要主线程来向子线程发送消息,希望子线程来处理,该如何实现呢?

有时候,我们也可能碰到这样子的一种需求:需要主线程来向子线程发送消息,希望子线程来完成什么任务。如果这样子应该怎么做呢?这就是这篇文章将要讨论的内容。

一、HandlerThread类

主线程发送消息给子线程,通常思维逻辑就是:其实很简单,在主线程中实例化一个Handler,然后让他与子线程相关联(只要它与子线程的Looper相关联即可),这样子它处理的消息就是该子线程中的消息队列,而处理的逻辑都是在该子线程中执行的,不会占用主线程的时间。那么我们就来实现一下,看看这样子到底行得通还是行不通。新建项目,修改它的MainActivity的代码,如下即可:

package com.example.handldertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.TextView;
public class ThreadHandlerActivity extends Activity{
    
    //创建子线程
    class MyThread extends Thread{
        private Looper looper;//取出该子线程的Looper
        public void run() {
         
            Looper.prepare();//创建该子线程的Looper
            looper = Looper.myLooper();//取出该子线程的Looper
            Looper.loop();//只要调用了该方法才能不断循环取出消息
        }
    }
    
    private TextView tv;
    private MyThread thread;
    
    
    private Handler mHandler;//将mHandler指定轮询的Looper
    
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            tv = new TextView(this);
            tv.setText("Handler实验");
            setContentView(tv);
            thread = new MyThread();
            thread.start();//千万别忘记开启这个线程
            //下面是主线程发送消息
            mHandler = new Handler(thread.looper){
                public void handleMessage(android.os.Message msg) {
                    Log.d("当前子线程是----->", Thread.currentThread()+"");
                };
            };
            mHandler.sendEmptyMessage(1);
    }
}


好了,现在运行该程序。有没有得到预期的结果呢?显然没有,因为报错误了,如下:


这是一个空指针错误。这是为什么呢?仔细思考,也不难发现原因。因为当主线程走到第38行时,此时子线程的Looper对象还没有被创建出来,那么此时thread.looper肯定为空了。其实这个时间是很不好控制的,当然了,你可以让主线程休眠2秒后再执行第38行以后的代码。但是如果有很多个子线程都需要主线程类给其分配任务怎么办??那简直要乱套了。所以我们就更好的解决方式。就是android显然也考虑到了这个问题,于是它我们提供了一个HandlerThread类。这个类是专门处理这个问题的。

当主线程中有耗时的操作时,需要在子线程中完成,通常我们就把这个逻辑放在HandlerThread的对象中执行(该对象就是一个子线程),然后在需要开始执行逻辑的地方发送一个Message来通知一下就可以了。下面我们就修改上面的代码,看一看如何使用HandlerThread这个类。修改MainActivity中的代码如下:

package com.example.handldertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.widget.TextView;
public class ThreadHandlerActivity extends Activity{
    
    
    private TextView tv;
    private Handler mHandler;//将mHandler指定轮询的Looper
    
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            tv = new TextView(this);
            tv.setText("Handler实验");
            setContentView(tv);
        
            //实例化一个特殊的线程HandlerThread,必须给其指定一个名字
            HandlerThread thread = new HandlerThread("handler thread");
            thread.start();//千万不要忘记开启这个线程
            //将mHandler与thread相关联
            mHandler = new Handler(thread.getLooper()){
                public void handleMessage(android.os.Message msg) {
                    Log.d("当前子线程是----->", Thread.currentThread()+"");
                };
            };
            mHandler.sendEmptyMessage(1);//发送消息
    }
}


运行程序,打印的结果如下:


从打印结果来看,当前子线程的名字正是我们所起的那个名字“handler thread"。

你会有疑问,表面上看HandlerThread并没有创建自己的Looper啊?而且既然是一个线程,那么我们肯定也能重写它的run方法吧。在解答你的疑问之前,我们不妨重写它的run方法来看一看会有什么结果。将代码修改如下:

package com.example.handldertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.widget.TextView;
public class ThreadHandlerActivity extends Activity{
    
    
    private TextView tv;
    private Handler mHandler;//将mHandler指定轮询的Looper
    
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            tv = new TextView(this);
            tv.setText("Handler实验");
            setContentView(tv);
        
            //实例化一个特殊的线程HandlerThread,必须给其指定一个名字
            HandlerThread thread = new HandlerThread("handler thread"){
                @Override
                public void run() {
                    for(int i=0;i<3;i++){
                        Log.d("handler thread run ",i+"");
                    }
                }
            };
//            HandlerThread thread = new HandlerThread("handler thread");
            thread.start();//千万不要忘记开启这个线程
            //将mHandler与thread相关联
            mHandler = new Handler(thread.getLooper()){
                public void handleMessage(android.os.Message msg) {
                    Log.d("当前子线程是----->", Thread.currentThread()+"");
                };
            };
            mHandler.sendEmptyMessage(1);//发送消息
    }
}


红色部分就是我们重写了它的run方法。再云运行程序,打印的结果如下:

for循环的打印结果正常,但是为什么没有打印出”当前子线程“呢。其实这正是我们要解释的地方。还记得上一篇文章中实现与子线程相关联的的Handler,我们是怎么做的吗?没读过的朋友看以点击链接(http://www.cnblogs.com/fuly550871915/p/4889838.html)。其实我们实现Handlei与线程的关联正是写在run方法中的。而对于HandlerThread这样的线程,也是如此。我们翻看这个类的源代码,找到它的run方法,如下:

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

    

在源代码的第4行,进行了实例化自己的Looper,如果继续追踪源代码翻看其getLooper方法你会发现,如果一个Handler在与HandlerThread进行绑定时,发现Looper为空,Handler则会一直等待直到Looper被创建出来为止,然后才继续执行后续的代码。所以我们重写了HandlerThread的run方法,肯定就不会去创建Looper对象,那么绑定的Handler就会永远处于等待状态,自然而然就不会打印出”当前子线程“信息了。这也是为什么我们要使用HandlerThread这个特殊的线程,因为使用这个,我们不必关心多线程会混乱,Looper会为空等一系列问题,只要去关心我们要实现的逻辑就行了。

好了,现在做一下简单的总结吧。

 小结:
1. Handler与哪个线程的Looper相关联,那么它的消息处理逻辑就在与之相关的线程中执行,相应的消息的走向也就在相关联的MessageQueue中。(最常见的就是Handler与主线程关联,那么接收Looper回传的消息后的逻辑就会在主线程中执行)
2. 当主线程中需要与子线程进行通信时(比如将耗时操作放在子线程中),建议使用HandlerThread。同时要注意,千万不要去重写它的run方法。

二、一个主线程与子线程互相通信的例子

知识点都说完了。下面我们来写一个具体的例子实践一下吧。新建一个项目,修改它的MainActivity代码,如下:

package com.example.handldertest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.widget.TextView;
public class ThreadHandlerActivity extends Activity{
    
    
    private TextView tv;
    private Handler mHandler;//与子线程关联的Handler
    private Handler handler;//与主线程关联的Handler
    
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            tv = new TextView(this);
            tv.setText("Handler实验");
            setContentView(tv);
        
            //实例化一个特殊的线程HandlerThread,必须给其指定一个名字
            HandlerThread thread = new HandlerThread("handler thread");
            thread.start();//千万不要忘记开启这个线程
            //将mHandler与thread相关联
            mHandler = new Handler(thread.getLooper()){
                public void handleMessage(android.os.Message msg) {
                    Log.d("我是子线程----->", Thread.currentThread()+"");
                    handler.sendEmptyMessage(1);//发送消息给主线程
                };
            };
            
            handler = new Handler(){
                public void handleMessage(android.os.Message msg) {
                    Log.d("我是主线程----->", Thread.currentThread()+"");
                    mHandler.sendEmptyMessage(1);//发送消息给子线程
                };
            };
            mHandler.sendEmptyMessage(1);//发送消息
            handler.sendEmptyMessage(1);//发送消息
    }
}


注释很详细,不解释 了。运行程序,结果如下:


这样子,就会一直循环下去,轮流打印出主线程和子线程。




Android主线程、子线程通信(Thread+handler)

Android是基于Java的,所以也分主线程,子线程!
主线程:实现业务逻辑、UI绘制更新、各子线程串连,类似于将军;
子线程:完成耗时(联网取数据、SD卡数据加载、后台长时间运行)操作,类似于小兵;

一、子线程向主线程发消息(Thread+handler):


1、主线程中定义Handler:

Handler mHandler = new Handler(){  
  
    @Override  
    public void handleMessage(Message msg) {  
        super.handleMessage(msg);  
        switch (msg.what) {  
        case 0:  
            //do something,refresh UI;  
            break;  
        default:  
            break;  
        }  
    }  
      
};

    
2、子线程处理完耗时操作之后发消息给主线程,更新UI:

mHandler.sendEmptyMessage(0);  

这样在子线程与主线程任务分工的条件下完成了消息交互;

二、主线程向子线程发送消息(Thread+handler):

主线程碰到耗时操作要子线程完成,此时发通知给子线程,操作步骤如下:

1、子线程中定义Handler,Handler定义在哪个线程中,就跟那个线程绑定,在线程中绑定Handler需要调用Looper.prepare();方法,主线程中不调用是因为主线程默认帮你调用了;

public class LoopThread implements Runnable {  
  
    public Handler mHandler = null;  
  
    @Override  
    public void run() {  
        Looper.prepare();  
        mHandler = new Handler() {  
            public void handleMessage(Message msg) {  
                String result = NetUtil.getJsonContent("北京");  
                //完成了获取北京天气的操作;  
                Log.i("test", "handler"+result);  
            }  
        };  
        Looper.loop();  
    }  
  
}



其中Looper.prepare();和Looper.loop();维护了一个消息队列,等待消息注入并在子线程中执行;

2、主线程中这样调用:


lThread.mHandler.sendEmptyMessage(0);  

主线程向子线程发消息,让子线程执行指定的操作,在Android中还有一种方法,即:HandlerThread,看下面的例子:

HandlerThread handlerThread = new HandlerThread("jerome");  
handlerThread.start();  
  
/** 
 * 这里要将HandlerThread创建的looper传递给threadHandler,即完成绑定; 
 */  
threadHandler = new Handler(handlerThread.getLooper()) {  
  
    @Override  
    public void handleMessage(Message msg) {  
        super.handleMessage(msg);  
        switch (msg.what) {  
        case 0:  
这儿可以做耗时的操作;  
            Log.i("jerome", "hello,I am sub thread");  
            break;  
        default:  
            break;  
        }  
    }  
};


下面我们一起来看一篇Android Design Support Library 02 — CollapsingToolbarLayout&&CardView问题解决办法。

Material Design的第二篇更新啦!这次介绍两个控件CollapsingToolbarLayout&&CardView

1、CollapsingToolbarLayout

5.0之后,折叠效果的App出现了,前段时间google在material design的设计中也推出了这个控件。
Ok,还是先上视频!

 代码如下 复制代码
<android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="256dp"
        android:fitsSystemWindows="true">
        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="#30469b"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">
  
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@mipmap/bg"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.7"  />
  
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin" />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

官方提示,使用CollasingToolbarLayout和Toolbar,title用CollapsingToolbar来设置
This setup uses CollapsingToolbarLayout’s app:layout_collapseMode=”pin” to ensure that the Toolbar itself remains pinned to the top of the screen while the view collapses. Even better, when you use CollapsingToolbarLayout and Toolbar together, the title will automatically appear larger when the layout is fully visible, then transition to its default size as it is collapsed. Note that in those cases, you should call setTitle() on the CollapsingToolbarLayout, rather than on the Toolbar itself.

2、CardView

实现了卡片式的并且有阴影效果。

 代码如下 复制代码

<android.support.v7.widget.CardView
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_margin="@dimen/card_margin">
 
       <LinearLayout
           style="@style/Widget.CardContent"
           android:layout_width="match_parent"
           android:layout_height="wrap_content">
 
           <TextView
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:text="界冢伊奈帆(かいづか いなほ)"
               android:textAppearance="@style/TextAppearance.AppCompat.Title" />
 
           <TextView
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:text="主人公。居住在地..." />
 
           <ImageView
               android:id="@+id/iv_ynf"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content" />
 
       </LinearLayout>
</android.support.v7.widget.CardView>

在Android开发中,有时我们可能会遇到一个ListView中出现不同样式的item,那么我们如何处理不同的样式呢?本文分享两个实例。

还是先看效果图吧


我们再使用listview时,大多时候listview的item大多时候都是一种样式,在很多app中也很常见,但有时候根据需求,可能数据的数量不一样,同个类型的数据显示的位置不同,亦或者有的item需要图片,有的不需要,但是这些又必须在同一个listview中显示,这时我们就需要在listview中显示多种样式的item,首先我们需要考虑的是如何将不同数量的数据装载到ArrayList<~>中呢,先看看下面的listViewItem,。

package com.example.keranbin.myapplication;
import java.util.HashMap;
import java.util.Map;
public class lIstViewItem
{
    //用于区分listview显示的不同item,告诉适配器我这是什么类型,listview适配器根据type决定怎么显示
    public int type;
    //将要显示的数据用HashMap包装好
    public HashMap map ;
    
    public lIstViewItem(int type, HashMap map)
    {
        this.type = type;
        this.map = map;
    }
}


我们通过自定义一个listItem,即可将所有不同类型,不同数量的数据先组装成统一类型listItem即可,然后用arrayList.add(listitem)即可。

/**
     * 这里我们用三种不同的样式进行测试
     **/
    private ArrayList getDatas() {
        viewItemsArraylists = new ArrayList();
        viewItemsArraylists.add(new lIstViewItem(2, getHashMapThreeType("汪星人", "汪星人喜欢吃骨头", "2015-10-18")));
        viewItemsArraylists.add(new lIstViewItem(1, getHashMapSecondType("喵星人", "喵星喜欢吃鱼")));
        viewItemsArraylists.add(new lIstViewItem(0, getHashMapFirstType("猴子")));
        viewItemsArraylists.add(new lIstViewItem(0, getHashMapFirstType("老虎")));
        viewItemsArraylists.add(new lIstViewItem(1, getHashMapSecondType("老母鸡", "老母鸡喜欢吃虫子")));
        return viewItemsArraylists;
    }
    
    //第一种样式,只传输一个数据
    private HashMap getHashMapFirstType(String firstTheme) {
        HashMap hashMap = new HashMap();
        hashMap.put("Theme", firstTheme);
        return hashMap;
    }
    //第二种样式,传输两个数据
    private HashMap getHashMapSecondType(String secondTheme, String secondContent) {
        HashMap hashMap = new HashMap();
        hashMap.put("Theme", secondTheme);
        hashMap.put("Content", secondContent);
        return hashMap;
    }
    //第三种样式,传输三个数据
    private HashMap getHashMapThreeType(String threeTheme, String threeContent, String date) {
        HashMap hashMap = new HashMap();
        hashMap.put("Theme", threeTheme);
        hashMap.put("Content", threeContent);
        hashMap.put("Date", date);
        return hashMap;
    }

    
剩下的就是listViewAdapter的事情啦,和显示一种样式的listViewAdapter不同的一点是我们重写实现父类baseAdapter的两个方法。

//返回当前布局的样式type
    @Override
    public int getItemViewType(int position) {
        return listDatas.get(position).type;
    }
    //返回你有多少个不同的布局
    @Override
    public int getViewTypeCount() {
        return 3;
    }

    
然后在getView中根据需要进行判断决定显示那种样式即可

package com.example.keranbin.myapplication;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.ArrayList;
/**
 * Created by keranbin on 2015/10/13.
 */
public class ListViewAdapter extends BaseAdapter {
    private LayoutInflater mLayoutInflater;
    private Context context;
    private ArrayList listDatas;
    public ListViewAdapter(Context context, ArrayList listDatas) {
        this.listDatas = listDatas;
        mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    //返回当前布局的样式type
    @Override
    public int getItemViewType(int position) {
        return listDatas.get(position).type;
    }
    //返回你有多少个不同的布局
    @Override
    public int getViewTypeCount() {
        return 3;
    }
    @Override
    public int getCount() {
        return listDatas.size();
    }
    @Override
    public Object getItem(int position) {
        return listDatas.get(position);
    }
    @Override
    public long getItemId(int position) {
        return position;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        lIstViewItem listItem = listDatas.get(position);
        int Type = getItemViewType(position);
        ViewHolderfirstType viewHolderfirstType = null;
        ViewHoldersecondType viewHoldersecondType = null;
        ViewHolderThreeType viewHolderThreeType = null;
        if (convertView == null) {
            switch (Type) {
                case 0:
                    viewHolderfirstType = new ViewHolderfirstType();
                    convertView = mLayoutInflater.inflate(R.layout.activity_first_type_item, null);
                    viewHolderfirstType.tv_first_theme= (TextView) convertView.findViewById(R.id.tv_first_theme);
                    viewHolderfirstType.tv_first_theme.setText(listItem.map.get("Theme").toString());
                    convertView.setTag(viewHolderfirstType);
                    break;
                case 1:
                    viewHoldersecondType = new ViewHoldersecondType();
                    convertView = mLayoutInflater.inflate(R.layout.activity_second_type_item, null);
                    viewHoldersecondType.tv_second_content = (TextView) convertView.findViewById(R.id.tv_second_content);
                    viewHoldersecondType.btn_second_theme = (Button) convertView.findViewById(R.id.btn_second_theme);
                    viewHoldersecondType.tv_second_content.setText(listItem.map.get("Theme").toString());
                    viewHoldersecondType.btn_second_theme.setText(listItem.map.get("Content").toString());
                    convertView.setTag(viewHoldersecondType);
                    break;
                case 2:
                    viewHolderThreeType = new ViewHolderThreeType();
                    convertView = mLayoutInflater.inflate(R.layout.activity_three_type_item, null);
                    viewHolderThreeType.tv_three_content = (TextView) convertView.findViewById(R.id.tv_three_content);
                    viewHolderThreeType.et_three_theme= (EditText) convertView.findViewById(R.id.et_three_theme);
                    viewHolderThreeType.tv_three_time= (TextView) convertView.findViewById(R.id.tv_three_time);
                    viewHolderThreeType.et_three_theme.setText(listItem.map.get("Theme").toString());
                    viewHolderThreeType.tv_three_content.setText(listItem.map.get("Content").toString());
                    viewHolderThreeType.tv_three_time.setText(listItem.map.get("Date").toString());
                    convertView.setTag(viewHolderThreeType);
                    break;
            }
        }else{
            switch (Type){
                case 0:
                    viewHolderfirstType= (ViewHolderfirstType) convertView.getTag();
                    viewHolderfirstType.tv_first_theme.setText(listItem.map.get("Theme").toString());
                    break;
                case 1:
                    viewHoldersecondType= (ViewHoldersecondType) convertView.getTag();
                    viewHoldersecondType.tv_second_content = (TextView) convertView.findViewById(R.id.tv_second_content);
                    viewHoldersecondType.btn_second_theme = (Button) convertView.findViewById(R.id.btn_second_theme);
                    viewHoldersecondType.tv_second_content.setText(listItem.map.get("Theme").toString());
                    viewHoldersecondType.btn_second_theme.setText(listItem.map.get("Content").toString());
                    break;
                case 2:
                    viewHolderThreeType= (ViewHolderThreeType) convertView.getTag();
                    viewHolderThreeType.tv_three_content = (TextView) convertView.findViewById(R.id.tv_three_content);
                    viewHolderThreeType.et_three_theme= (EditText) convertView.findViewById(R.id.et_three_theme);
                    viewHolderThreeType.tv_three_time= (TextView) convertView.findViewById(R.id.tv_three_time);
                    viewHolderThreeType.et_three_theme.setText(listItem.map.get("Theme").toString());
                    viewHolderThreeType.tv_three_content.setText(listItem.map.get("Content").toString());
                    viewHolderThreeType.tv_three_time.setText(listItem.map.get("Date").toString());
                    break;
            }
        }
        return convertView;
    }
    class ViewHolderfirstType {
        TextView tv_first_theme;
    }
    class ViewHoldersecondType {
        TextView tv_second_content;
        Button btn_second_theme;
    }
    class ViewHolderThreeType {
        EditText et_three_theme;
        TextView tv_three_content;
        TextView tv_three_time;
    }
}


第一种样式页面组件主要是一个TextView.

                


第二种样式页面组件主要是一个TextView和一个button.

                        


第三种样式页面组件主要是两个TextView和一个EditText.

                                


activity_main.xml文件非常简单,就一个listView。

    


下面是MainActivity的代码

package com.example.keranbin.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.HashMap;
public class MainActivity extends Activity {
    private ListView listView;                               //页面listview
    private ListViewAdapter listViewAdapter;                 //listview适配器
    private ArrayList viewItemsArraylists;     //Arraylist主要装载的是传给适配器的数据集合
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化页面组件及一些数据
        initView();
        //为listview设置适配器
        ListViewAdapter listViewAdapter = new ListViewAdapter(MainActivity.this, getDatas());
        listView.setAdapter(listViewAdapter);
    }
    //初始化页面组件及一些数据
    private void initView() {
        listView = (ListView) this.findViewById(R.id.listView);
        listViewAdapter = new ListViewAdapter(MainActivity.this, getDatas());
    }
    /**
     * 这里我们用三种不同的样式进行测试
     **/
    private ArrayList getDatas() {
        viewItemsArraylists = new ArrayList();
        viewItemsArraylists.add(new lIstViewItem(2, getHashMapThreeType("汪星人", "汪星人喜欢吃骨头", "2015-10-18")));
        viewItemsArraylists.add(new lIstViewItem(1, getHashMapSecondType("喵星人", "喵星喜欢吃鱼")));
        viewItemsArraylists.add(new lIstViewItem(0, getHashMapFirstType("猴子")));
        viewItemsArraylists.add(new lIstViewItem(0, getHashMapFirstType("老虎")));
        viewItemsArraylists.add(new lIstViewItem(1, getHashMapSecondType("老母鸡", "老母鸡喜欢吃虫子")));
        return viewItemsArraylists;
    }
    //第一种样式,只传输一个数据
    private HashMap getHashMapFirstType(String firstTheme) {
        HashMap hashMap = new HashMap();
        hashMap.put("Theme", firstTheme);
        return hashMap;
    }
    //第二种样式,传输两个数据
    private HashMap getHashMapSecondType(String secondTheme, String secondContent) {
        HashMap hashMap = new HashMap();
        hashMap.put("Theme", secondTheme);
        hashMap.put("Content", secondContent);
        return hashMap;
    }
    //第三种样式,传输三个数据
    private HashMap getHashMapThreeType(String threeTheme, String threeContent, String date) {
        HashMap hashMap = new HashMap();
        hashMap.put("Theme", threeTheme);
        hashMap.put("Content", threeContent);
        hashMap.put("Date", date);
        return hashMap;
    }
}

Android ListView存在多个item样式的处理方法


在项目开发的时候,相信大家可能会遇到一个ListView中出现多个不同的布局,遇到这个问题我的大致思路就是创建多个viewholder,在getViewType的时候设置不同位置的item用不同的viewholder,好了不废话那么多直接上代码:

package com.sunny.youdao;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
public class MyAdapter extends BaseAdapter {
    private Context mContext;
    private LinearLayout linearLayout = null;
    private LayoutInflater inflater;
    private List list = new ArrayList();
    private TextView tex;
    private final int VIEW_TYPE = 3;
    private final int TYPE_1 = 0;
    private final int TYPE_2 = 1;
    private final int TYPE_3 = 2;
    public MyAdapter(Context context, List list) {
        // TODO Auto-generated constructor stub
        this.mContext = context;
        this.list = list;
        inflater = LayoutInflater.from(mContext);
    }
    @Override
    public int getCount() {
        // TODO 自动生成的方法存根
        return list.size();
    }
    @Override
    public Object getItem(int position) {
        // TODO 自动生成的方法存根
        return list.get(position);
    }
    @Override
    public long getItemId(int position) {
        // TODO 自动生成的方法存根
        return position;
    }
    
    //每个convert view都会调用此方法,获得当前所需要的view样式
    @Override
    public int getItemViewType(int position) {
        // TODO Auto-generated method stub
        int viewtype = position%6;
        if(viewtype == 0)
        return TYPE_1;
        else if(viewtype < 3)
            return TYPE_2;
        else if(viewtype < 6)
            return TYPE_3;
        else
            return TYPE_1;
    }
    
    //返回样式的数量
    @Override
    public int getViewTypeCount() {
        // TODO Auto-generated method stub
        return 3;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        viewHolder1 holder1 = null;
        viewHolder2 holder2 = null;
        viewHolder3 holder3 = null;
        int type = getItemViewType(position);
        // 无convertView,需要new出各个控件
        if (convertView == null) {
            Log.e("convertView = ", "###convertView为空###");
            // 按当前所需的样式,确定new的布局
            switch (type) {
            case TYPE_1:
                convertView = inflater.inflate(R.layout.listitem1, parent,false);
                holder1 = new viewHolder1();
                holder1.textView = (TextView) convertView.findViewById(R.id.textview1);
                holder1.checkBox = (CheckBox) convertView.findViewById(R.id.checkbox);
                Log.e("convertView = ", "布局样式一");
                convertView.setTag(holder1);
                break;
            case TYPE_2:
                convertView = inflater.inflate(R.layout.listitem2, parent,false);
                holder2 = new viewHolder2();
                holder2.textView = (TextView) convertView.findViewById(R.id.textview2);
                Log.e("convertView = ", "布局样式二");
                convertView.setTag(holder2);
                break;
            case TYPE_3:
                convertView = inflater.inflate(R.layout.listitem3, parent,false);
                holder3 = new viewHolder3();
                holder3.textView = (TextView) convertView.findViewById(R.id.textview3);
                holder3.imageView = (ImageView) convertView.findViewById(R.id.imageview);
                Log.e("convertView = ", "布局样式三");
                convertView.setTag(holder3);
                break;
            }
        } else {
            // 有convertView,按样式,取得不用的布局
            switch (type) {
            case TYPE_1:
                holder1 = (viewHolder1) convertView.getTag();
                Log.e("convertView= ", "布局样式一");
                break;
            case TYPE_2:
                holder2 = (viewHolder2) convertView.getTag();
                Log.e("convertView= ", "布局样式二");
                break;
            case TYPE_3:
                holder3 = (viewHolder3) convertView.getTag();
                Log.e("convertView= ", "布局样式三");
                break;
            }
        }
        // 设置资源
        switch (type) {
        case TYPE_1:
            holder1.textView.setText(Integer.toString(position));
            holder1.checkBox.setChecked(true);
            break;
        case TYPE_2:
            holder2.textView.setText(Integer.toString(position));
            break;
        case TYPE_3:
            holder3.textView.setText(Integer.toString(position));
            holder3.imageView.setBackgroundResource(R.drawable.icon);
            break;
        }
        return convertView;
    }
    
    // 各个布局的控件资源
    class viewHolder1 {
        CheckBox checkBox;
        TextView textView;
    }
    class viewHolder2 {
        TextView textView;
    }
    class viewHolder3 {
        ImageView imageView;
        TextView textView;
    }
}


关于关于Eclipse 和 IDEA 导入library库文件,本教程使用图文并茂来详情讲解,非常实用,做Android开发的同学可以参考一下。

关于Eclipse 和 IDEA 导入library库文件,我们以PullToRefresh(上拉刷新下拉加载)组件的library为例来具体讲解。

PullToRefresh下载地址:https://github.com/chrisbanes/Android-PullToRefresh


我们的目的就是把library文件夹导入到Eclipse或者IDEA中去


一、IDEA 导入library库文件步骤


1、首先我们要有一个项目,没有的就创建一个吧

2、右击项目名称点击Open Module Settings(F4)


3、可以看到这样的界面


接下来在中间部分 点击绿色的加号 导入Module


找到要导入的library类库的目录


点击OK 后,,新的界面选择 第一个选项 Create module from existing sources,然后下一步知道import操作完成


4、然后就可以看到这样的界面,中间界面 多了一个library文件夹


5、接着点击最右边界面的绿色加号按钮 选择第三个Module Dependency,注意中间部分要选择你要导入library库文件的目录,即此时在中间界面选中demo文件夹,在按绿色按钮添加


6、可以看到有library文件夹可以选择 选择OK就行了 然后OK 结束设置


7、这是就可以看到你的项目里多了一个library文件夹


打开library文件夹可以看到文件夹内容都在,


8、我们在主Activity中添加一个library 提供的类检查是否导入成功,不报错可导入成功



二、Eclipse 导入library库文件步骤


1、导入


2、选择 Android/Existing Android Code Into Workspace


3、选择library文件夹目录 ,记得选中 Copy projects into workspace


4、可以看到项目目录多了library


5、右键library 选择properties (在最下面)

点击is Library --》ok


6、然后右击要导入library库文件的的项目 选择properties 添加Add 选择要导入的library文件夹


7、然后使用library库文件提供的类检测是否导入正确 (注意项目和library库文件需要在同一个目录下,即同一个工作空间)



在现在的智能手机应用中,推送功能非常重要,在Android应用开发中,Android消息推送不是很简单。下面和大家探讨一种Android消息推送的比较好的解决方案。

1.消息推送基础

消息推送,就是在互联网上通过定期传送用户需要的信息来减少信息过载的一项新技术。推送技术通过自动传送信息给用户,来减少用于网络上搜索的时间。它根据用户的兴趣来搜索、过滤信息,并将其定期推给用户,帮助用户高效率地发掘有价值的信息

当我们开发需要和服务器交互的移动应用时,基本上都需要和服务器进行交互,包括上传数据到服务器,同时从服务器上获取数据。

一般情况下,客户端与服务器之间通讯客户端是主动的,但这就存在一个问题就是一旦服务器数据有更新或者服务器要下发通知给客户端只能等客户端连接的时候才能实现。这种方式使消息失去了实时性。

如何使客户端能够实时的收到服务器的消息和通知,总体来说有两种方式,第一种是客户端使用Pull(拉)的方式,就是隔一段时间就去服务器上获取一下信息,看是否有更新的信息出现。第二种就是 服务器使用Push(推送)的方式,当服务器端有新信息了,则把最新的信息Push到客户端上。这样,客户端就能自动的接收到消息。 

  虽然Pull和Push两种方式都能实现获取服务器端更新信息的功能,但是明显来说Push方式比Pull方式更优越。因为Pull方式更费客户端的网络流量,更主要的是费电量,还需要我们的程序不停地去监测服务端的变化。  

2. 几种常见的解决方案实现原理

  1)轮询(Pull)方式:客户端定时向服务器发送询问消息,一旦服务器有变化则立即同步消息。

  2)SMS(Push)方式:通过拦截SMS消息并且解析消息内容来了解服务器的命令,但这种方式一般用户在经济上很难承受。

  3)持久连接(Push)方式:客户端和服务器之间建立长久连接,这样就可以实现消息的及时行和实时性。

3、消息推送解决方案概述

  A、C2DM云端推送方案

在Android手机平台上,Google提供了C2DM(Cloudto Device Messaging)服务。Android Cloud to Device Messaging (C2DM)是一个用来帮助开发者从服务器向Android应用程序发送数据的服务。该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。

该方案存在的主要问题是C2DM需要依赖于Google官方提供的C2DM服务器,由于国内的网络环境,这个服务经常不可用。

  B、MQTT协议实现Android推送

  采用MQTT协议实现Android推送功能也是一种解决方案。MQTT是一个轻量级的消息发布/订阅协议,它是实现基于手机客户端的消息推送服务器的理想解决方案。

  wmqtt.jar 是IBM提供的MQTT协议的实现。我们可以从这里(https://github.com/tokudu/AndroidPushNotificationsDemo)下载该项目的实例代码,并且可以找到一个采用PHP书写的服务器端实现(https://github.com/tokudu/PhpMQTTClient)。

  C、RSMB实现推送功能

  Really Small Message Broker (RSMB) ,是一个简单的MQTT代理,同样由IBM提供,其查看地址是:http://www.alphaworks.ibm.com/tech/rsmb。缺省打开1883端口,应用程序当中,它负责接收来自服务器的消息并将其转发给指定的移动设备。SAM是一个针对MQTT写的PHP库。我们可以从这个http://pecl.php.net/package/sam/download/0.2.0地址下载它.

   D、XMPP协议实现Android推送

  Google官方的C2DM服务器底层也是采用XMPP协议进行的封装。XMPP(可扩展通讯和表示协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线探测。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息。

  androidpn是一个基于XMPP协议的java开源Android push notification实现。它包含了完整的客户端和服务器端。但也存在一些不足之处:

  1) 比如时间过长时,就再也收不到推送的信息了。

  2)性能上也不够稳定。

3)如果将消息从服务器上推送出去,就不再管理了,不管消息是否成功到达客户端手机上。

如果我们要使用androidpn,则还需要做大量的工作,需要理解XMPP协议、理解Androidpn的实现机制,需要调试内部存在的BUG。

  E、使用第三方平台

  目前国内、国外有一些推送平台可供使用,但是涉及到收费问题、保密问题、服务质量问题、扩展问题等等,又不得不是我们望而却步。

4、消息推送完美方案

      综合以上论述,在建立Android消息推送方面可谓方案多多,但每一款方案都有其优缺点。但无论如何,还是自己搭建一个推送平台是上策。因为你有、他有不如自己有。

         在搭建自有推送平台上建议使用《九日升Android消息推送组件》(http://www.bjjrs.net/product/13629681868537.html)。该组不仅可以拿来即用,并且还可以提供源码以便扩展,实现自己的特殊需求。

       A、推送原理

    九日升Android消息推送组件基于XMPP协议实现Android推送。XMPP(可扩展通讯和表示协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线探测。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息。

    九日升Android消息推送组件实现原理见下图:

            a.JPG
                                                  图1-消息推送原理图

    九日升Android消息推送组件由服务器部分和客户端部分组成。每一部分都由XMPP协议组件和外部接口组件构成。XMPP协议组件负责服务器和Android客户端间的连接管理、消息通讯,外部接口组件负责接收应用系统、客户端应用的命令,向应用系统发送接收到的通知消息。

    九日升Android消息组件提供基于Tomcat的服务器应用和Android开发jar包。其中基于Tomcat的服务器应用直接在Tomcat上部署即可,Android开发jar包引入Android项目即可。

   B 集成方式

    1)、服务器部署

    九日升Android消息组件Tomcat的服务器应用直接部署在Tomcat中,端口号任意设定。

    2)、客户端jar包引用

    在Android项目中建立libs目录,然后将提供的Android开发jar包复制到该目录即可。见下图:

b.JPG

                                        

图2-jar包引入图

    3)、Android项目AndroidManifest.xml文件修改

  在该文件中增加以下权限:

     <uses-permission android:name="android.permission.READ_PHONE_STATE" />

     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

     <uses-permission android:name="android.permission.INTERNET" />

     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

     <uses-permission android:name="android.permission.VIBRATE" />

   在该文件中注册服务:

     <service android:enabled="true"

     android:name="com.bjjrs.server.NotificationService"

     android:label="NotificationService">

          <intent-filter>

             <action android:name="com.bjjrs.server.NotificationService" />

         </intent-filter>

    </service>

   至此,九日升Android消息组件集成工作完成。

    C、接口方式

    1)、服务器端接口采用基于http协议的访问方式,采用http协议从服务器中获取各种信息,实现通知消息的推送。

如使用以下方式和参数就可以实现各种用户消息的查询:

  http://localhost:8080/user.do?action=getAllUser&isOnline=&userID=&userType=&deptID=&deptName=&realName=

    使用如下方式就可以实现各种消息的推送:

    http://localhost:8080/notification.do?action=pushNoti&userNames=&title=&content=

    2)、Android客户端接口采用广播机制。

    消息接收:当XMPP协议组件接收到推送消息时,将按照一定格式广播该消息,通知客户端其他应用接收并处理该消息。

    消息发送:客户端应用需要向服务器或者其他客户端发送即时消息时,只需按一定格式广播该消息,XMPP组件就会自动接收该消息并发送到指定的其他客户端。

    D、优势特点

    1)、系统集成简单,无需复杂的设置。

    2)、Android客户端应用和九日升Android消息推送组件完全分离,通过接口相互调用,实现模块应用最优化。

    3)、客户端通讯机制采用广播方式,给客户端应用带来极大的灵活性和可扩展性,可以自由处理接收到的推送消息。

    4)、九日升Android消息推送组件在服务器端具备消息存储、消息重发、消息路由等功能,在客户端部分具备断线重连、、收到确认、阅读确认、消息发送、命令执行等功能,确保消息能够推送到客户端,同时也保证客户端能够收到、阅读消息。

   E、 应用范围

  九日升Android消息推送组件可在以下场景中使用:

    1)、用于消息推送。如:通知下达、应急指挥等。

    2)、用户及时消息交互。如在线聊天、工作情况交互等。

    3)、用于远程控制。如控制远程客户端的状态、数据上报等。


Android消息推送机制


1.推送方式基础知识:

当我们开发需要和服务器交互的应用程序时,基本上都需要获取服务器端的数据,比如《地震应急通》就需要及时获取服务器上最新的地震信息。要获取服务器上不定时更新的信息一般来说有两种方法,第一种是客户端使用Pull(拉)的方式,隔一段时间就去服务器上获取信息,看是否有更新的信息出现。第二种就是服务器使用Push(推送)的方式,当服务器端有新信息了,则把最新的信息Push到客户端上。 

虽然Pull和Push两种方式都能实现获取服务器端更新信息的功能,但是明显来说Push is better than pull。因为Pull方式更费客户端的网络流量,更主要的是费电量。  

在开发Android和iPhone应用程序时,我们往往需要从服务器不定的向手机客户端即时推送各种通知消息,iPhone上已经有了比较简单的和完美的推送通知解决方案,我会在以后详细介绍IPhone中的解决方案,可是Android平台上实现起来却相对比较麻烦,最近利用几天的时间对 Android的推送通知服务进行初步的研究。在Android手机平台上,Google提供了C2DM(Cloudto Device Messaging)服务,起初我就是准备采用这个服务来实现自己手机上的推送功能。  

Android Cloud to Device Messaging (C2DM)是一个用来帮助开发者从服务器向Android应用程序发送数据的服务。该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。C2DM服务负责处理诸如消息排队等事务并向运行于目标设备上的应用程序分发这些消息。关于C2DM具体使用过程,我会以后的博文中再详细介绍,这里大家先了解下大致方案情况。

C2DM操作过程图:


但是经过一番研究发现,这个服务存在很大的问题:

1)C2DM内置于Android的2.2系统上,无法兼容老的1.6到2.1系统;

2)C2DM需要依赖于Google官方提供的C2DM服务器,由于国内的网络环境,这个服务经常不可用,如果想要很好的使用,我们的App Server必须也在国外,这个恐怕不是每个开发者都能够实现的; 有了上述两个使用上的制约,导致我最终放弃了这个方案,不过我想利用另外一篇文章来详细的介绍C2DM的框架以及客户端和App Server的相应设置方法,可以作为学习与参考之用。即然C2DM无法满足我们的要求,那么我们就需要自己来实现Android手机客户端与App Server之间的通信协议,保证在App Server想向指定的Android设备发送消息时,Android设备能够及时的收到。

2. 几种常见的解决方案

1)轮询(Pull):应用程序应当阶段性的与服务器进行连接并查询是否有新的消息到达,你必须自己实现与服务器之间的通信,例如消息排队等。而且你还要考虑轮询的频率,如果太慢可能导致某些消息的延迟,如果太快,则会大量消耗网络带宽和电池。

2)SMS(Push):在Android平台上,你可以通过拦截SMS消息并且解析消息内容来了解服务器的意图。这是一个不错的想法,我就见过采用这个方案的应用程序。这个方案的好处是,可以实现完全的实时操作。但是问题是这个方案的成本相对比较高,你很难找到免费的短消息发送网关,关于这个方案的实现。

3)持久连接(Push):这个方案可以解决由轮询带来的性能问题,但是还是会消耗手机的电池。Apple的推送服务之所以工作的很好,是因为每一台手机仅仅保持一个与服务器之间的连接,事实上C2DM也是这么工作的。不过这个方案也存在不足,就是我们很难在手机上实现一个可靠的服务。

Android操作系统允许在低内存情况下杀死系统服务,所以你的通知服务很可能被操作系统Kill掉了。前两个方案存在明显的不足,第三个方案也有不足,不过我们可以通过良好的设计来弥补,以便于让该方案可以有效的工作。毕竟,我们要知道GMail,GTalk以及GoogleVoice都可以实现实时更新的。

3. MQTT协议实现Android推送

采用MQTT协议实现Android推送 MQTT是一个轻量级的消息发布/订阅协议,它是实现基于手机客户端的消息推送服务器的理想解决方案。 wmqtt.jar 是IBM提供的MQTT协议的实现。我们可以从这里下载该项目的实例代码,并且可以找到一个采用PHP书写的服务器端实现。

架构如下所示:


wmqtt.jar 是IBM提供的MQTT协议的实现。我们可以从如下站点下载它。你可以将该jar包加入你自己的Android应用程序中。

4.RSMB实现推送:

Really Small Message Broker (RSMB) ,他是一个简单的MQTT代理,同样由IBM提供。缺省打开1883端口,应用程序当中,它负责接收来自服务器的消息并将其转发给指定的移动设备。

SAM是一个针对MQTT写的PHP库。我们可以从这个下载它.

send_mqtt.php是一个通过POST接收消息并且通过SAM将消息发送给RSMB的PHP脚本。

Really Small Message Broker (RSMB) ,他是一个简单的MQTT代理,同样由IBM提供。缺省打开1883端口,应用程序当中,它负责接收来自服务器的消息并将其转发给指定的移动设备。

5. XMPP协议实现Android推送

这是我在项目中采用的方案。事实上Google官方的C2DM服务器底层也是采用XMPP协议进行的封装。 XMPP(可扩展通讯和表示协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线探测。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息。

androidpn是一个基于XMPP协议的java开源Android push notification实现,我会在以后的博文中详细介绍androidpn。它包含了完整的客户端和服务器端。经过源代码研究我发现,该服务器端基本是在另外一个开源工程openfire基础上修改实现的,不过比较郁闷的是androidpn的文档是由韩语写的,所以整个研究过程基本都是读源码。

实现意图如下图所示:


androidpn 客户端需要用到一个基于java的开源XMPP协议包asmack,这个包同样也是基于openfire下的另外一个开源项目smack,不过我们不需要自己编译,可以直接把androidpn客户端里面的asmack.jar拿来使用。客户端利用asmack中提供的XMPPConnection类与服务器建立持久连接,并通过该连接进行用户注册和登录认证,同样也是通过这条连接,接收服务器发送的通知。

androidpn服务器端也是java语言实现的,基于openfire开源工程,不过它的Web部分采用的是spring框架,这一点与 openfire是不同的。Androidpn服务器包含两个部分,一个是侦听在5222端口上的XMPP服务,负责与客户端的 XMPPConnection类进行通信,作用是用户注册和身份认证,并发送推送通知消息。另外一部分是Web服务器,采用一个轻量级的HTTP服务器,负责接收用户的Web请求。服务器架构如下:


最上层包含四个组成部分,分别是SessionManager,Auth Manager,PresenceManager以及Notification Manager。SessionManager负责管理客户端与服务器之间的会话,Auth Manager负责客户端用户认证管理,Presence Manager负责管理客户端用户的登录状态,NotificationManager负责实现服务器向客户端推送消息功能。

这个解决方案的最大优势就是简单,我们不需要象C2DM那样依赖操作系统版本,也不会担心某一天Google服务器不可用。利用XMPP协议我们还可以进一步的对协议进行扩展,实现更为完善的功能。采用这个方案,我们目前只能发送文字消息,不过对于推送来说一般足够了,因为我们不能指望通过推送得到所有的数据,一般情况下,利用推送只是告诉手机端服务器发生了某些改变,当客户端收到通知以后,应该主动到服务器获取最新的数据,这样才是推送服务的完整实现。


[!--infotagslink--]

相关文章

  • Painter绘制红衣喝酒男水粉画效果教程

    今天小编在这里就来给Painter的这一款软件的使用者们来说一说绘制红衣喝酒男水粉画效果的教程,各位想知道具体绘制步骤的使用者,那么下面就快来跟着小编一起看一看教程...2016-09-14
  • iPhone6怎么激活?两种苹果iPhone6激活教程图文详解

    iPhone6新机需要激活后才可以正常使用,那么对于小白用户来说,iPhone6如何激活使用呢?针对此问题,本文就为大家分别介绍Wifi无线网络激活以及iPhone6连接电脑激活这两种有效的方法,希望本文能够帮助到大家...2022-09-14
  • Photoshop制作雨中野外孤独行走的一头牛海报教程

    今天小编在这里就来给各位photoshop的这一款软件的使用者们来说下制作雨中野外孤独行走的一头牛海报的教程,各位想知道具体制作方法的使用者们,大家就快来看一看小编给...2016-09-14
  • Painter绘制帅气卡通魔法王子漫画教程

    今天小编在这里就来给Painter的这一款软件的使用者们来说一下绘制帅气卡通魔法王子漫画的具体教程,各位想知道绘制步骤的使用者,那么下面就快来跟着小编一起看一看教程...2016-09-14
  • Illustrator鼠绘堆雪人的孩童矢量插画教程

    今天小编在这里就来给各位Illustrator的这一款软件的使用者们来说说鼠绘堆雪人的孩童矢量插画的教程,各位想知道具体绘制方法的使用者们,那么各位就快来跟着小编来看看...2016-09-14
  • 美图秀秀给照片天空加蓝天白云教程一览

    今天小编在这里就来给美图秀秀的这一款软件的使用者们来说下究竟该怎么给照片天空加蓝天白云的教程,各位想知道具体制作步骤的,那么下面就来跟着小编一起看看吧。 ...2016-09-14
  • 安卓手机app添加支付宝支付开发教程

    支付宝支付在国内算是大家了,我们到处都可以使用支付宝了,下文整理介绍的是在安卓app应用中使用支付宝进行支付的开发例子。 之前讲了一篇博客关与支付宝集成获取...2016-09-20
  • llustrator绘制扁平化风格卡通警察护士空姐肖像教程

    今天小编在这里就来给llustrator的这一款软件的使用者们来说一说绘制扁平化风格卡通警察护士空姐肖像的教程,各位想知道具体绘制步骤的使用者们,那么下面就快来跟着小编...2016-09-14
  • Illustrator绘制一个方形的录音机图标教程

    今天小编在这里就来给Illustrator的这一款软件的使用者们来说一下绘制一个方形的录音机图标的教程,各位想知道具体绘制方法的使用者们,那么下面就来看一下小编给大家分...2016-09-14
  • photoshop简单制作一个搞笑的换脸表情包教程

    今天小编在这里就来给photoshop的这一款软件的使用者们来说一说简单制作一个搞笑的换脸表情包的教程,各位想知道具体制作方法的使用者们,那么大家就快来看一看教程吧。...2016-09-14
  • photoshop给手绘画调色变换场景后期教程

    今天小编在这里就来给各位photoshop的这一款软件的使用者们来说说给手绘画调色变换场景的后期教程,各位想知道具体后期处理步骤的使用者们,那么大家就快来跟着小编来看...2016-10-02
  • 美图秀秀让你胸丰满起来处理教程

    今天小编在这里就来给美图秀秀的这一款软件的使用者们来说一下让你胸丰满起来的处理教程,各位想知道具体处理步骤的,那么下面就快来跟着小编一起看一下教程吧。 给...2016-09-14
  • Painter绘制雷神传插画教程

    今天小编在这里就来给Painter的这一款软件的使用者们来说一下绘制雷神传插画的教程,各位想知道具体绘制步骤的使用者,那么下面就快来跟着小编一起看看绘制方法吧。 ...2016-09-14
  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • MySQL中的联合索引学习教程

    联合索引又叫复合索引。对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进...2015-11-24
  • 美图秀秀制作隔离区聊天背景教程

    今天小编在这里就来给美图秀秀的这一款软件的使用者们来说下制作隔离区聊天背景的教程,各位想知道具体方法的,那么下面就快来跟着小编一起看一看吧。 给各位美图秀...2016-09-14
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • MySQL日志分析软件mysqlsla的安装和使用教程

    一、下载 mysqlsla [root@localhost tmp]# wget http://hackmysql.com/scripts/mysqlsla-2.03.tar.gz--19:45:45-- http://hackmysql.com/scripts/mysqlsla-2.03.tar.gzResolving hackmysql.com... 64.13.232.157Conn...2015-11-24
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20