文档章节

Android 使用Messenger跨进程通信框架

IamOkay
 IamOkay
发布于 2014/12/04 23:05
字数 2241
阅读 247
收藏 2

一.通过Binder绑定形式的通信

 

上一篇说道Binder机制的通信框架,也说过Messenger的底层实现自AIDL,因此对于跨进程通信中,Messenger是一种比较高级的框架,可以说对于一个app开发者来说重要性不言而喻

服务端模型

public class BackgroundService extends Service
{

       private Messenger mMessenger = null;//监听数据
       
       private Messenger replyMessenger = null //响应数据
       
       
        @Override
	public IBinder onBind(Intent intent) 
	{
	    if(mMessenger==null)
	    {
	       mMessenger = new Messenger(new ServerHandler());
	    }
	return mMessenger.getBinder();
	}

	
	 @Override
	public boolean onUnbind(Intent intent) 
	{
	  return super.onUnbind(intent);
	}
	
    //注意,将ServerHandler静态化,以防止内存泄露
    public static class ServerHandler extends Handler
    {
        @Override
	public void handleMessage(Message msg) 
	{
		if (msg.what>0) 
		{
			try 
			{
			  replyMessenger = msg.replyTo;
			  replyMessenger.send(Message.obtain(null,0x000001, HttpStatus.SC_OK, 0));
			  LogUtils.e("[后台数据]=>报告地瓜,土豆收到 <MSGID[FlightID:"+msg.obj+",arg1:"+msg.arg1+"]>");
			}
			catch (RemoteException e)
			{
			e.printStackTrace();
			}
		
		}
		else
		{
			super.handleMessage(msg);
		}
	}    
    }
}

 

客户端模型

public class MainActivity extends Activity
{

   private Activity activity = null;
   private final int RETRY_INTERVAL_TIME = 2 * 1000;

   private final Messenger clientMessenger= new Messenger(new ClientHandler());
   private Messenger replyMessenger = null;
        
 @Override
protected void onCreate(Bundle savedInstanceState)
{
  super.onCreate(savedInstanceState);
  activity = this;
  startBackgroundService(this);
  //启动广播是异步的,因此必须通过不断轮询的方式来检测广播是否已启动
  bindgroundService(); 
}

public static class ClientHandler extends Handler
{
       @Override
	public void handleMessage(Message msg) 
	{
	    if (msg.what == HttpStatus.SC_OK)
	     {
	        //msg.arg1就是remoteInt
	       Log.i("TAG", "服务端响应数据:"+msg.arg1);
	   } else {
	       super.handleMessage(msg);
	  }
    }
}

/**绑定服务*/
	private ServiceConnection  serviceConnection = new ServiceConnection() 
	{
		@Override
		public void onServiceDisconnected(ComponentName name)
		{
			replyMessenger = null;
			LogUtils.d("ServiceClient>>service onServiceDisconnected :连接失败");
		}
		
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) 
		{
			replyMessenger = new Messenger(service);
			LogUtils.d("ServiceClient>>service onServiceConnected : 连接成功");
			if(replyMessenger !=null)
			{
				LogUtils.e("[前台数据]==>土豆土豆,我是地瓜,收到请回应!");

				Message msgTo =  Message.obtain(null,200);
				msgTo.obj = "[现在可以开始上传数据]";
				msgTo.arg1 = 1024;
				msgTo.replyTo = clientMessenger;
				try 
				{
					replyMessenger.send(msgTo);
				} catch (RemoteException e) {
					e.printStackTrace();
				}
			}
		}
	}; 

/**
 * 启动后台服务
 * @param context
  */
  private void startBackgroundService(Context context)
 {
	if(!isServiceRunning(BackgroundService.class.getName(),context))
	{
	Intent targetIntent = new Intent(context,RCCBackgroundService.class);
	//targetIntent.setAction(TARGET_SERVICE_ACTION);
	context.startService(targetIntent);
	}
}

/**
* 绑定后台服务
*/
private void bindgroundService()
{
	if(replyMessenger ==null && isServiceRunning(BackgroundService.class.getName()))
	{
		Log.e("[前台数据]","[绑定服务]");
		activity.getWindow().getDecorView().postDelayed(new Runnable()
		{
			@Override
			public void run()
			{
				Intent intent = new Intent(activity, BackgroundService.class);
				intent.setAction(WifiNetworkChangedReceiver.TARGET_SERVICE_ACTION);
				activity.bindService(intent, serviceConnection, Service.BIND_AUTO_CREATE);
			}
		}, RETRY_INTERVAL_TIME);
	}else{
		activity.getWindow().getDecorView().postDelayed(new Runnable() {
			@Override
			public void run() {
				replyMessenger = null;
				bindgroundService();
			}
			}, RETRY_INTERVAL_TIME);
		}
	}
	
	
	/**
	 * 检测Service是否已经启动
	 * @param serviceClassName
	 * @return
	 */
	public  boolean isServiceRunning(String serviceClassName)
	{ 
            final ActivityManager activityManager = (ActivityManager)activity.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); 
            final List<RunningServiceInfo> services = activityManager.getRunningServices(Integer.MAX_VALUE); 
     
            for (RunningServiceInfo runningServiceInfo : services) 
            { 
                if (runningServiceInfo.service.getClassName().equals(serviceClassName))
                { 
                	LogUtils.e("-ServiceClient->服务已经启动,开始绑定后台服务");
                    return true; 
                } 
            } 
            LogUtils.e("->ServiceClient->服务未启动,等待重新尝试绑定服务!");
            return false; 
     }
	
}

 

二.非绑定形式的通信

Android 5.0的调度作业JobScheduler

原理是通过Parceable参数传递

客户端

public class MainActivity extends Activity {

    private static final String TAG = MainActivity.class.getSimpleName();

    public static final int MSG_UNCOLOR_START = 0;
    public static final int MSG_UNCOLOR_STOP = 1;
    public static final int MSG_COLOR_START = 2;
    public static final int MSG_COLOR_STOP = 3;

    public static final String MESSENGER_INTENT_KEY
            = BuildConfig.APPLICATION_ID + ".MESSENGER_INTENT_KEY";
    public static final String WORK_DURATION_KEY =
            BuildConfig.APPLICATION_ID + ".WORK_DURATION_KEY";

    private EditText mDelayEditText;
    private EditText mDeadlineEditText;
    private EditText mDurationTimeEditText;
    private RadioButton mWiFiConnectivityRadioButton;
    private RadioButton mAnyConnectivityRadioButton;
    private CheckBox mRequiresChargingCheckBox;
    private CheckBox mRequiresIdleCheckbox;

    private ComponentName mServiceComponent;

    private int mJobId = 0;

    // Handler for incoming messages from the service.
    private IncomingMessageHandler mHandler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample_main);

        // Set up UI.
        mDelayEditText = (EditText) findViewById(R.id.delay_time);
        mDurationTimeEditText = (EditText) findViewById(R.id.duration_time);
        mDeadlineEditText = (EditText) findViewById(R.id.deadline_time);
        mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered);
        mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any);
        mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging);
        mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle);
        mServiceComponent = new ComponentName(this, MyJobService.class);

        mHandler = new IncomingMessageHandler(this);
    }

    @Override
    protected void onStop() {
        // A service can be "started" and/or "bound". In this case, it's "started" by this Activity
        // and "bound" to the JobScheduler (also called "Scheduled" by the JobScheduler). This call
        // to stopService() won't prevent scheduled jobs to be processed. However, failing
        // to call stopService() would keep it alive indefinitely.
        stopService(new Intent(this, MyJobService.class));
        super.onStop();
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Start service and provide it a way to communicate with this class.

        //主动发送Messenger,传递handler的IMessenger到Service
        Intent startServiceIntent = new Intent(this, MyJobService.class);
        Messenger messengerIncoming = new Messenger(mHandler);
        startServiceIntent.putExtra(MESSENGER_INTENT_KEY, messengerIncoming);
        startService(startServiceIntent);
    }

    /**
     * Executed when user clicks on SCHEDULE JOB.
     */
    public void scheduleJob(View v) {
        JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);

        String delay = mDelayEditText.getText().toString();
        if (!TextUtils.isEmpty(delay)) {
            builder.setMinimumLatency(Long.valueOf(delay) * 1000);
        }
        String deadline = mDeadlineEditText.getText().toString();
        if (!TextUtils.isEmpty(deadline)) {
            builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);
        }
        boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
        boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
        if (requiresUnmetered) {
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
        } else if (requiresAnyConnectivity) {
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
        }
        builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked());
        builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());

        // Extras, work duration.
        PersistableBundle extras = new PersistableBundle();
        String workDuration = mDurationTimeEditText.getText().toString();
        if (TextUtils.isEmpty(workDuration)) {
            workDuration = "1";
        }
        extras.putLong(WORK_DURATION_KEY, Long.valueOf(workDuration) * 1000);

        builder.setExtras(extras);

        // Schedule job
        Log.d(TAG, "Scheduling job");
        JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        tm.schedule(builder.build());
    }

    /**
     * Executed when user clicks on CANCEL ALL.
     */
    public void cancelAllJobs(View v) {
        JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        tm.cancelAll();
        Toast.makeText(MainActivity.this, R.string.all_jobs_cancelled, Toast.LENGTH_SHORT).show();
    }

    /**
     * Executed when user clicks on FINISH LAST TASK.
     */
    public void finishJob(View v) {
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        List<JobInfo> allPendingJobs = jobScheduler.getAllPendingJobs();
        if (allPendingJobs.size() > 0) {
            // Finish the last one
            int jobId = allPendingJobs.get(0).getId();
            jobScheduler.cancel(jobId);
            Toast.makeText(
                    MainActivity.this, String.format(getString(R.string.cancelled_job), jobId),
                    Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(
                    MainActivity.this, getString(R.string.no_jobs_to_cancel),
                    Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * A {@link Handler} allows you to send messages associated with a thread. A {@link Messenger}
     * uses this handler to communicate from {@link MyJobService}. It's also used to make
     * the start and stop views blink for a short period of time.
     */
    private static class IncomingMessageHandler extends Handler {

        // Prevent possible leaks with a weak reference.
        private WeakReference<MainActivity> mActivity;

        IncomingMessageHandler(MainActivity activity) {
            super(/* default looper */);
            this.mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity mainActivity = mActivity.get();
            if (mainActivity == null) {
                // Activity is no longer available, exit.
                return;
            }
            View showStartView = mainActivity.findViewById(R.id.onstart_textview);
            View showStopView = mainActivity.findViewById(R.id.onstop_textview);
            Message m;
            switch (msg.what) {
                /*
                 * Receives callback from the service when a job has landed
                 * on the app. Turns on indicator and sends a message to turn it off after
                 * a second.
                 */
                case MSG_COLOR_START:
                    // Start received, turn on the indicator and show text.
                    showStartView.setBackgroundColor(getColor(R.color.start_received));
                    updateParamsTextView(msg.obj, "started");

                    // Send message to turn it off after a second.
                    m = Message.obtain(this, MSG_UNCOLOR_START);
                    sendMessageDelayed(m, 1000L);
                    break;
                /*
                 * Receives callback from the service when a job that previously landed on the
                 * app must stop executing. Turns on indicator and sends a message to turn it
                 * off after two seconds.
                 */
                case MSG_COLOR_STOP:
                    // Stop received, turn on the indicator and show text.
                    showStopView.setBackgroundColor(getColor(R.color.stop_received));
                    updateParamsTextView(msg.obj, "stopped");

                    // Send message to turn it off after a second.
                    m = obtainMessage(MSG_UNCOLOR_STOP);
                    sendMessageDelayed(m, 2000L);
                    break;
                case MSG_UNCOLOR_START:
                    showStartView.setBackgroundColor(getColor(R.color.none_received));
                    updateParamsTextView(null, "");
                    break;
                case MSG_UNCOLOR_STOP:
                    showStopView.setBackgroundColor(getColor(R.color.none_received));
                    updateParamsTextView(null, "");
                    break;
            }
        }

        private void updateParamsTextView(@Nullable Object jobId, String action) {
            TextView paramsTextView = (TextView) mActivity.get().findViewById(R.id.task_params);
            if (jobId == null) {
                paramsTextView.setText("");
                return;
            }
            String jobIdText = String.valueOf(jobId);
            paramsTextView.setText(String.format("Job ID %s %s", jobIdText, action));
        }

        private int getColor(@ColorRes int color) {
            return mActivity.get().getResources().getColor(color);
        }
    }
}

服务端

public class MainActivity extends Activity {

    private static final String TAG = MainActivity.class.getSimpleName();

    public static final int MSG_UNCOLOR_START = 0;
    public static final int MSG_UNCOLOR_STOP = 1;
    public static final int MSG_COLOR_START = 2;
    public static final int MSG_COLOR_STOP = 3;

    public static final String MESSENGER_INTENT_KEY
            = BuildConfig.APPLICATION_ID + ".MESSENGER_INTENT_KEY";
    public static final String WORK_DURATION_KEY =
            BuildConfig.APPLICATION_ID + ".WORK_DURATION_KEY";

    private EditText mDelayEditText;
    private EditText mDeadlineEditText;
    private EditText mDurationTimeEditText;
    private RadioButton mWiFiConnectivityRadioButton;
    private RadioButton mAnyConnectivityRadioButton;
    private CheckBox mRequiresChargingCheckBox;
    private CheckBox mRequiresIdleCheckbox;

    private ComponentName mServiceComponent;

    private int mJobId = 0;

    // Handler for incoming messages from the service.
    private IncomingMessageHandler mHandler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample_main);

        // Set up UI.
        mDelayEditText = (EditText) findViewById(R.id.delay_time);
        mDurationTimeEditText = (EditText) findViewById(R.id.duration_time);
        mDeadlineEditText = (EditText) findViewById(R.id.deadline_time);
        mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered);
        mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any);
        mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging);
        mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle);
        mServiceComponent = new ComponentName(this, MyJobService.class);

        mHandler = new IncomingMessageHandler(this);
    }

    @Override
    protected void onStop() {
        // A service can be "started" and/or "bound". In this case, it's "started" by this Activity
        // and "bound" to the JobScheduler (also called "Scheduled" by the JobScheduler). This call
        // to stopService() won't prevent scheduled jobs to be processed. However, failing
        // to call stopService() would keep it alive indefinitely.
        stopService(new Intent(this, MyJobService.class));
        super.onStop();
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Start service and provide it a way to communicate with this class.
        Intent startServiceIntent = new Intent(this, MyJobService.class);
        Messenger messengerIncoming = new Messenger(mHandler);
        startServiceIntent.putExtra(MESSENGER_INTENT_KEY, messengerIncoming);
        startService(startServiceIntent);
    }

    /**
     * Executed when user clicks on SCHEDULE JOB.
     */
    public void scheduleJob(View v) {
        JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);

        String delay = mDelayEditText.getText().toString();
        if (!TextUtils.isEmpty(delay)) {
            builder.setMinimumLatency(Long.valueOf(delay) * 1000);
        }
        String deadline = mDeadlineEditText.getText().toString();
        if (!TextUtils.isEmpty(deadline)) {
            builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);
        }
        boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
        boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
        if (requiresUnmetered) {
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
        } else if (requiresAnyConnectivity) {
            builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
        }
        builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked());
        builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());

        // Extras, work duration.
        PersistableBundle extras = new PersistableBundle();
        String workDuration = mDurationTimeEditText.getText().toString();
        if (TextUtils.isEmpty(workDuration)) {
            workDuration = "1";
        }
        extras.putLong(WORK_DURATION_KEY, Long.valueOf(workDuration) * 1000);

        builder.setExtras(extras);

        // Schedule job
        Log.d(TAG, "Scheduling job");
        JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        tm.schedule(builder.build());
    }

    /**
     * Executed when user clicks on CANCEL ALL.
     */
    public void cancelAllJobs(View v) {
        JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        tm.cancelAll();
        Toast.makeText(MainActivity.this, R.string.all_jobs_cancelled, Toast.LENGTH_SHORT).show();
    }

    /**
     * Executed when user clicks on FINISH LAST TASK.
     */
    public void finishJob(View v) {
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        List<JobInfo> allPendingJobs = jobScheduler.getAllPendingJobs();
        if (allPendingJobs.size() > 0) {
            // Finish the last one
            int jobId = allPendingJobs.get(0).getId();
            jobScheduler.cancel(jobId);
            Toast.makeText(
                    MainActivity.this, String.format(getString(R.string.cancelled_job), jobId),
                    Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(
                    MainActivity.this, getString(R.string.no_jobs_to_cancel),
                    Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * A {@link Handler} allows you to send messages associated with a thread. A {@link Messenger}
     * uses this handler to communicate from {@link MyJobService}. It's also used to make
     * the start and stop views blink for a short period of time.
     */
    private static class IncomingMessageHandler extends Handler {

        // Prevent possible leaks with a weak reference.
        private WeakReference<MainActivity> mActivity;

        IncomingMessageHandler(MainActivity activity) {
            super(/* default looper */);
            this.mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity mainActivity = mActivity.get();
            if (mainActivity == null) {
                // Activity is no longer available, exit.
                return;
            }
            View showStartView = mainActivity.findViewById(R.id.onstart_textview);
            View showStopView = mainActivity.findViewById(R.id.onstop_textview);
            Message m;
            switch (msg.what) {
                /*
                 * Receives callback from the service when a job has landed
                 * on the app. Turns on indicator and sends a message to turn it off after
                 * a second.
                 */
                case MSG_COLOR_START:
                    // Start received, turn on the indicator and show text.
                    showStartView.setBackgroundColor(getColor(R.color.start_received));
                    updateParamsTextView(msg.obj, "started");

                    // Send message to turn it off after a second.
                    m = Message.obtain(this, MSG_UNCOLOR_START);
                    sendMessageDelayed(m, 1000L);
                    break;
                /*
                 * Receives callback from the service when a job that previously landed on the
                 * app must stop executing. Turns on indicator and sends a message to turn it
                 * off after two seconds.
                 */
                case MSG_COLOR_STOP:
                    // Stop received, turn on the indicator and show text.
                    showStopView.setBackgroundColor(getColor(R.color.stop_received));
                    updateParamsTextView(msg.obj, "stopped");

                    // Send message to turn it off after a second.
                    m = obtainMessage(MSG_UNCOLOR_STOP);
                    sendMessageDelayed(m, 2000L);
                    break;
                case MSG_UNCOLOR_START:
                    showStartView.setBackgroundColor(getColor(R.color.none_received));
                    updateParamsTextView(null, "");
                    break;
                case MSG_UNCOLOR_STOP:
                    showStopView.setBackgroundColor(getColor(R.color.none_received));
                    updateParamsTextView(null, "");
                    break;
            }
        }

        private void updateParamsTextView(@Nullable Object jobId, String action) {
            TextView paramsTextView = (TextView) mActivity.get().findViewById(R.id.task_params);
            if (jobId == null) {
                paramsTextView.setText("");
                return;
            }
            String jobIdText = String.valueOf(jobId);
            paramsTextView.setText(String.format("Job ID %s %s", jobIdText, action));
        }

        private int getColor(@ColorRes int color) {
            return mActivity.get().getResources().getColor(color);
        }
    }
}

 

对于有疑问说,使用Messenger没有AIDL方便,因为AIDL是IPC代理方式的,这个需要按照情况来决定,对于小型项目,没必要使用IPC代理,但对于大项目最好使用IPC代理,因为IPC代理这种方式是面向接口的服务。

 

三、Messenger、Binder、AIDL的区别

①Messenger和AIDL相比,都可以使用Parcelable传递复杂类型的数据,AIDL相对底层,灵活性比较好。

②Messenger底层封装自AIDL

③AIDL和Messenger都是通过Binder机制通信

④Messenger使用的是同一个线程的Handler和消息队列,因此无法处理多线程并发任务,而AIDL可以。

④Messenger可以实现在无绑定服务的情况下和Service交互

 

 

 

© 著作权归作者所有

IamOkay

IamOkay

粉丝 203
博文 483
码字总数 403198
作品 0
海淀
程序员
私信 提问
Android中Messenger的使用

我们使用Handler都是在一个进程中使用的,如何跨进程使用Handler? 其实这个问题不难解决,自己动手对binder进行一些封装就可以简单实现。但是当你看系统源码,就会发现,其实这些android都已...

cwr
2015/02/12
6.6K
1
PieBridge,一个高效、小巧的基于Bundle的Android进程间通信IPC框架

## PieBridge A efficient, light, and easy-to-use framework for Android Inter-Process Communication (IPC). 一套高效小巧易用的基于Bundle的Android进程间通信IPC框架。 这几天学习了爱......

kuangfrank
2018/08/19
0
0
四大组件之Service(三)-Service的跨进程调用

版权声明:本文为博主原创文章,禁止转载,违者必究。 https://blog.csdn.net/anddlecn/article/details/51671035 第4节 远程调用 之前提到过:如果站在Service与触发Service运行的那个组件的...

anddlecn
2016/06/14
0
0
Android Messenger 跨进程通信

如果你需要在不同进程间通信,你可以在Service中使用Messenger来实现进程中通信。 如果使用这种方式,Service中需要定义一个Handler对象(负责对客户端发送过来的Message进行响应)。 Messenge...

一寨之主
2014/09/01
150
0
基于JSON RPC的一种Android跨进程调用解决方案了解一下?

基于JSON RPC的一种Android跨进程调用解决方案了解一下? 基于JSON RPC的一种Android跨进程调用解决方案了解一下? 简介 今天上午,看票圈有朋友分享爱奇艺的跨进程通信框架——Andromeda,觉...

流水不腐小夏
2018/05/30
0
0

没有更多内容

加载失败,请刷新页面

加载更多

左边竖条的实现方法

下面这个图形,只使用一个标签,可以有多少种实现方式: 假设我们的单标签是一个 div : 1 < div > div> 定义如下通用CSS: 1 2 3 4 5 6 div{ position : relative ; width : 200px ; height ...

前端老手
26分钟前
2
0
java利用ECHARTS.JS在前台显示图表

步骤1: (1)在java后台,使用MSQL分组函数,列出所有线在对应的点的值, (2)组成的Map如图所示: 注意: key为0的value表示X轴需要的数据;key为其他的值表示图表线条的名字,value为x轴的点对应的y...

文文1
28分钟前
6
0
解题博客

https://blog.csdn.net/hk2291976/article/category/9265848

素雷
53分钟前
4
0
linux-ubuntu下使用linuxdeployqt+appimagetool将qt程序打包成xxx.AppImage文件

下文中提及的inuxdeployqt patchelf appimagetool工具及示例下载地址: 链接: https://pan.baidu.com/s/1BGm_btMIe75uW9hOC09Xlg 提取码: 7ayh 需要创建目录及文件 xxx.AppDir xxx.AppDir/Ap...

shzwork
58分钟前
5
0
javascript-ASCII码混合四位随机验证码

// 产生一个随机字符库:数字大写小写的数量是对应的 function randomStr(){ // 产生库 var strData = ""; for(var i=0;i<4;i++){ var num = random(0,9); var az = String.fromCharCode(ra......

ACKo
58分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部