当前位置:首页 > 芯闻号 > 充电吧
[导读]        因为上一节service manager中,对于binder通信的客户端(BpServiceManager)和服务端(service manager进程)已经有比较详细的解释,所以,不

        因为上一节service manager中,对于binder通信的客户端(BpServiceManager)和服务端(service manager进程)已经有比较详细的解释,所以,不再对于Binder通信的client端和server端做分析,有兴趣的同学可以看看MediaPlayerService和MediaPlayer,网上很多资料有讲解。

        当目前为止,所有的代码都是以C/C++语言的,但是,App开发者通常使用Java语言,那么Java是如何使用Binder通信的呢?

AIDL         很多时候,我们是通过aidl(Android Interface Define Language)间接的使用Binder机制的。例如,我们准备了下面这样一个aidl文件:

package com.ray.example;

interface RInterface {
    void hello(String message);
}

       经过IDE的编译,我们会得到下面这样的Java文件:

/*___Generated_by_IDEA___*/

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /home/ray/Learning&Study/BinderProProject/SearchApp/src/main/java/com/ray/example/RInterface.aidl
 */
package com.ray.example;
public interface RInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.ray.example.RInterface
{
private static final java.lang.String DESCRIPTOR = "com.ray.example.RInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.ray.example.RInterface interface,
 * generating a proxy if needed.
 */
public static com.ray.example.RInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.ray.example.RInterface))) {
return ((com.ray.example.RInterface)iin);
}
return new com.ray.example.RInterface.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_hello:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.hello(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.ray.example.RInterface
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public void hello(java.lang.String message) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(message);
mRemote.transact(Stub.TRANSACTION_hello, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_hello = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public void hello(java.lang.String message) throws android.os.RemoteException;
}

        编译产生的java文件提供了三个对象:

RInterface接口:继承自IInterface接口,并且有一个hello的成员函数。作为server和client之间的约定,双方都会使用。

RInterface.Stub抽象类:继承自Binder类,并且提供了onTransact函数的实现,以及静态函数asInterface。

RInterface.Stub.Proxy类:实现了RInterface接口,实现了hello函数。

XXXX.Stub         主要由Server端实现,Server端通过继承本类,来提供具体的处理逻辑。通常,在Server端,我们会有如下代码:

package com.ray.example;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

/**
 * Created by ray on 2/7/14.
 */
public class RServer extends RInterface.Stub {

    public final String TAG_RAY = "ray";

    @Override
    public void hello(String message) throws RemoteException {
        Log.i(TAG_RAY,"Hello~ " + message);
    }
}

        这里通过重载来hello函数来提供具体的处理逻辑。而hello函数是如何被调用的呢?这就需要回顾RInterface.Stub类对于onTransact函数的重载:

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_hello:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.hello(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

        当onTransact函数被以特定的参数调用时,hello函数会被调用:

static final int TRANSACTION_hello = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);

        Android系统约定,code必须大于等于

    int FIRST_CALL_TRANSACTION  = 0x00000001;

        并且,小于等于

    int LAST_CALL_TRANSACTION   = 0x00ffffff;

        否则,code可能和Binder协议的一些保留code冲突。         至于onTransact函数是如何被调用,就需要涉及到Binder类的内部实现,我们稍后分析。 XXXX.Stub.Proxy         主要由Client端使用,顾名思义,担当Server的代理角色。         通常,在Clent端,会有类似如下的代码:

package com.ray.example;

import android.os.IBinder;
import android.os.RemoteException;

/**
 * Created by ray on 2/7/14.
 */
public class RClient {

    private RInterface mRInterface;

    public RClient (IBinder binder){
        mRInterface = RInterface.Stub.asInterface(binder);
    }

    public void sayHello(String message) throws RemoteException {
        mRInterface.hello("Ray");
    }
}

        而,我们知道Rinterface.Stub.asInterface函数会构造一个RInterface.Stub.Proxy类的实例,并返回:

public static com.ray.example.RInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.ray.example.RInterface))) {
return ((com.ray.example.RInterface)iin);
}
return new com.ray.example.RInterface.Stub.Proxy(obj);
}

        所以,客户端的mRInterface实际上是RInterface.Stub.Proxy。而调用Proxy对于hello的实现:

@Override public void hello(java.lang.String message) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(message);
mRemote.transact(Stub.TRANSACTION_hello, _data, _reply, 0);//mRemote的类型为IBinder
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}

       很明显的,是通过Binder机制转发请求,但是mRemote是如何实现transaction函数的呢?我们暂时还不知道。 小结 aidl文件编译成java文件时,提供了三个对象:XXXX接口,这个接口根据aidl文件的内容生成,作为通信双方的约定。XXXX.Stub抽象类,这个抽象类继承自Binder类。它实现了从Binder中读取到数据以后,呼叫业务逻辑处理代码的功能。Server端通过继承并实现业务逻辑来使用它。XXXX.Stub.Proxy类,这个类封装了一个IBinder接口,它实现了把Client端请求通过Binder发送到Server端的功能。Client可以通过XXXX.Stub.asInterface函数来获得XXXX.Stub.Proxy实例。 IBinder接口         IBinder接口的声明,并不难理解:

public interface IBinder {

    int FIRST_CALL_TRANSACTION  = 0x00000001;

    int LAST_CALL_TRANSACTION   = 0x00ffffff;
    
    //异步binder
    int FLAG_ONEWAY             = 0x00000001;
    
    public String getInterfaceDescriptor() throws RemoteException;
    
    public boolean pingBinder();
    
    public boolean isBinderAlive();
    
    public IInterface queryLocalInterface(String descriptor);
    
    public void dump(FileDescriptor fd, String[] args) throws RemoteException;
    
    public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException;
    //进行binder通信
    public boolean transact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException;
    //死亡通知
    public interface DeathRecipient {
        public void binderDied();
    }
    //注册死亡通知
    public void linkToDeath(DeathRecipient recipient, int flags)
            throws RemoteException;
    //注销死亡通知
    public boolean unlinkToDeath(DeathRecipient recipient, int flags);
}

Binder类                 Binder类的声明有点长,所以,这边仅仅列出几个重点函数:

public class Binder implements IBinder {
    /* mObject is used by native code, do not remove or rename */
    private int mObject;
    private IInterface mOwner;
    private String mDescriptor;

    // client端的进程id
    public static final native int getCallingPid();
    // client端的用户id
    public static final native int getCallingUid();    
    // 清除client端的进程id和用户id
    public static final native long clearCallingIdentity();

    public Binder() {
        init();    
    }

    /**
     * Default implementation is a stub that returns false.  You will want
     * to override this to do the appropriate unmarshalling of transactions.
     *
     * If you want to call this, call transact().

*/    protected boolean onTransact(int code, Parcel data, Parcel reply,            int flags) throws RemoteException {        if (code == INTERFACE_TRANSACTION) {            reply.writeString(getInterfaceDescriptor());            return true;        } else if (code == DUMP_TRANSACTION) {            ParcelFileDescriptor fd = data.readFileDescriptor();            String[] args = data.readStringArray();            if (fd != null) {                try {                    dump(fd.getFileDescriptor(), args);                } finally {                    try {                        fd.close();                    } catch (IOException e) {                        // swallowed, not propagated back to the caller                    }                }            }            // Write the StrictMode header.            if (reply != null) {                reply.writeNoException();            } else {                StrictMode.clearGatheredViolations();            }            return true;        }        return false;    }    /**     * Default implementation rewinds the parcels and calls onTransact.  On     * the remote side, transact calls into the binder to do the IPC.     */    public final boolean transact(int code, Parcel data, Parcel reply,            int flags) throws RemoteException {        if (false) Log.v("Binder", "Transact: " + code + " to " + this);        if (data != null) {            data.setDataPosition(0);        }        boolean r = onTransact(code, data, reply, flags);        if (reply != null) {            reply.setDataPosition(0);        }        return r;    }    protected void finalize() throws Throwable {        try {            destroy();        } finally {            super.finalize();        }    }        private native final void init();    private native final void destroy();    // Entry point from android_util_Binder.cpp's onTransact    private boolean execTransact(int code, int dataObj, int replyObj,            int flags) {        Parcel data = Parcel.obtain(dataObj);        Parcel reply = Parcel.obtain(replyObj);        // theoretically, we should call transact, which will call onTransact,        // but all that does is rewind it, and we just got these from an IPC,        // so we'll just call it directly.        boolean res;        try {            res = onTransact(code, data, reply, flags);        } catch (RemoteException e) {            reply.setDataPosition(0);            reply.writeException(e);            res = true;        } catch (RuntimeException e) {            reply.setDataPosition(0);            reply.writeException(e);            res = true;        } catch (OutOfMemoryError e) {            RuntimeException re = new RuntimeException("Out of memory", e);            reply.setDataPosition(0);            reply.writeException(re);            res = true;        }        reply.recycle();        data.recycle();        return res;    }

        到目前为止,我们似乎还没发现Java的Binder类和native层的C++Binder接口存在什么联系,不过,我们还没看过Binder类的原生方法。 android_os_Binder_init

static void android_os_Binder_init(JNIEnv* env, jobject obj)
{
    JavaBBinderHolder* jbh = new JavaBBinderHolder();
    if (jbh == NULL) {
        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
        return;
    }
    ALOGV("Java Binder %p: acquiring first ref on holder %p", obj, jbh);
    jbh->incStrong((void*)android_os_Binder_init);
    env->SetIntField(obj, gBinderOffsets.mObject, (int)jbh);
}

        init函数构造了一个JavaBBinderHolder实例,看起来奥秘应该就在这个JavaBBinderHolder中了。不过,在此之前,我们先解释下

    env->SetIntField(obj, gBinderOffsets.mObject, (int)jbh);

       首先,需要从gBinderoffsets变量说起:

static struct bindernative_offsets_t
{
    // Class state.
    jclass mClass;
    jmethodID mExecTransact;

    // Object state.
    jfieldID mObject;

} gBinderOffsets;

        它有三个成员。而这三个成员的含义,我们可以从下面的代码中了解:

const char* const kBinderPathName = "android/os/Binder"

static int int_register_android_os_Binder(JNIEnv* env)
{
    jclass clazz;

    clazz = env->FindClass(kBinderPathName);//clazz即Java Binder类
    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Binder");

    gBinderOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
    gBinderOffsets.mExecTransact
        = env->GetMethodID(clazz, "execTransact", "(IIII)Z");//mExecTransact指向Binder类的execTrasaction函数
    assert(gBinderOffsets.mExecTransact);

    gBinderOffsets.mObject
        = env->GetFieldID(clazz, "mObject", "I");//mObject指向Binder类的mObject成员
    assert(gBinderOffsets.mObject);

    return AndroidRuntime::registerNativeMethods(//注册原生函数
        env, kBinderPathName,
        gBinderMethods, NELEM(gBinderMethods));
}

        而register_android_os_Binder函数则会在Dalvik虚拟机启动的时候执行。相似的,还有gBinderInternalOffsets和gBinderProxyOffsets。所以,我们现在可以知道:

    env->SetIntField(obj, gBinderOffsets.mObject, (int)jbh);

        的作用就是把JavaBBinderHolder的实例地址,保存到Binder的mObject成员中。其实,类似这样的处理手法在Android中非常常见,例如MessageQueue和NactiveMessageQueue。 android_os_Binder_destroy         理解了init,destory就不难理解了:

static void android_os_Binder_destroy(JNIEnv* env, jobject obj)
{
    JavaBBinderHolder* jbh = (JavaBBinderHolder*)
        env->GetIntField(obj, gBinderOffsets.mObject);//从Binder.mObject获得jbh
    if (jbh != NULL) {
        env->SetIntField(obj, gBinderOffsets.mObject, 0);
        ALOGV("Java Binder %p: removing ref on holder %p", obj, jbh);//设置Binder.mObject=0
        jbh->decStrong((void*)android_os_Binder_init);//通过强引用计数,控制自己的生命周期
    } else {
        // Encountering an uninitialized binder is harmless.  All it means is that
        // the Binder was only partially initialized when its finalizer ran and called
        // destroy().  The Binder could be partially initialized for several reasons.
        // For example, a Binder subclass constructor might have thrown an exception before
        // it could delegate to its superclass's constructor.  Consequently init() would
        // not have been called and the holder pointer would remain NULL.
        ALOGV("Java Binder %p: ignoring uninitialized binder", obj);
    }
}

JavaBBinderHolder         JavaBBinderHolder的实现比较简单:

class JavaBBinderHolder : public RefBase
{
public:
    spget(JNIEnv* env, jobject obj)
    {
        AutoMutex _l(mLock);
        spb = mBinder.promote();
        if (b == NULL) {
            b = new JavaBBinder(env, obj);//构造JavaBBinder实例,这里的obj参数为java的Binder类
            mBinder = b;
            ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%dn",
                 b.get(), b->getWeakRefs(), obj, b->getWeakRefs()->getWeakCount());
        }

        return b;
    }

    spgetExisting()
    {
        AutoMutex _l(mLock);
        return mBinder.promote();
    }

private:
    Mutex           mLock;
    wpmBinder;
};

        不过,它引入了下一个角色:JavaBBinder。 JavaBBinder

class JavaBBinder : public BBinder
{
public:
    JavaBBinder(JNIEnv* env, jobject object)
        : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object))
    {
        ALOGV("Creating JavaBBinder %pn", this);
        android_atomic_inc(&gNumLocalRefs);
        incRefsCreated(env);
    }

    bool    checkSubclass(const void* subclassID) const
    {
        return subclassID == &gBinderOffsets;
    }

    jobject object() const
    {
        return mObject;
    }

protected:
    virtual ~JavaBBinder()
    {
        ALOGV("Destroying JavaBBinder %pn", this);
        android_atomic_dec(&gNumLocalRefs);
        JNIEnv* env = javavm_to_jnienv(mVM);
        env->DeleteGlobalRef(mObject);
    }

    virtual status_t onTransact(
        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0)
    {
        JNIEnv* env = javavm_to_jnienv(mVM);

        ALOGV("onTransact() on %p calling object %p in env %p vm %pn", this, mObject, env, mVM);

        IPCThreadState* thread_state = IPCThreadState::self();
        const int strict_policy_before = thread_state->getStrictModePolicy();
        thread_state->setLastTransactionBinderFlags(flags);

        //printf("Transact from %p to Java code sending: ", this);
        //data.print();
        //printf("n");
        jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
            code, (int32_t)&data, (int32_t)reply, flags);
        jthrowable excep = env->ExceptionOccurred();

        if (excep) {
            report_exception(env, excep,
                "*** Uncaught remote exception!  "
                "(Exceptions are not yet supported across processes.)");
            res = JNI_FALSE;

            /* clean up JNI local ref -- we don't return to Java code */
            env->DeleteLocalRef(excep);
        }

        // Restore the Java binder thread's state if it changed while
        // processing a call (as it would if the Parcel's header had a
        // new policy mask and Parcel.enforceInterface() changed
        // it...)
        const int strict_policy_after = thread_state->getStrictModePolicy();
        if (strict_policy_after != strict_policy_before) {
            // Our thread-local...
            thread_state->setStrictModePolicy(strict_policy_before);
            // And the Java-level thread-local...
            set_dalvik_blockguard_policy(env, strict_policy_before);
        }

        jthrowable excep2 = env->ExceptionOccurred();
        if (excep2) {
            report_exception(env, excep2,
                "*** Uncaught exception in onBinderStrictModePolicyChange");
            /* clean up JNI local ref -- we don't return to Java code */
            env->DeleteLocalRef(excep2);
        }

        // Need to always call through the native implementation of
        // SYSPROPS_TRANSACTION.
        if (code == SYSPROPS_TRANSACTION) {
            BBinder::onTransact(code, data, reply, flags);
        }

        //aout << "onTransact to Java code; result=" << res << endl
        //    << "Transact from " << this << " to Java code returning "
        //    << reply << ": " << *reply << endl;
        return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION;
    }

    virtual status_t dump(int fd, const Vector& args)
    {
        return 0;
    }

private:
    JavaVM* const   mVM;
    jobject const   mObject;
};

        原来JavaBBinder继承自BBinder。现在我们终于看到了Native层的Binder接口。从前面几节的内容,我们知道,BBinder代表着用户空间的Binder实体。所以,JavaBBinder也是代表用户空间的Binder实体。         抛开其他次要的部分不谈,我们只关注onTransact函数:

   virtual status_t onTransact(
        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0)
    {
        ......

        jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
            code, (int32_t)&data, (int32_t)reply, flags);//调用Binder实例的execTransact方法

        ......
    }

        用最简单的视角去分析onTransact函数,我们会发现,它会调用Binder类的execTransact函数,而前面,我们有看到execTransact函数会调用Binder类的onTransact函数,这样,最终就会执行Service的业务逻辑,处理Client的请求。至于JavaBBinder的onTransact函数何时被调用,看过前面两节内容的读者应该就心中有数了,稍后我们会更加具体的分析。         这样,我们现在已经知道在Server端,native层的binder接口是何如与Java接口交互的。接下来,让我们看看Client端 BinderProxy         BinderProxy类同样在frameworks/base/core/android/os/Binder.java中:

final class BinderProxy implements IBinder {
    public native boolean pingBinder();
    public native boolean isBinderAlive();
    
    public IInterface queryLocalInterface(String descriptor) {
        return null;
    }
    
    public native String getInterfaceDescriptor() throws RemoteException;
    public native boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;
    public native void linkToDeath(DeathRecipient recipient, int flags)
            throws RemoteException;
    public native boolean unlinkToDeath(DeathRecipient recipient, int flags);

    public void dump(FileDescriptor fd, String[] args) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeFileDescriptor(fd);
        data.writeStringArray(args);
        try {
            transact(DUMP_TRANSACTION, data, reply, 0);
            reply.readException();
        } finally {
            data.recycle();
            reply.recycle();
        }
    }
    
    public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeFileDescriptor(fd);
        data.writeStringArray(args);
        try {
            transact(DUMP_TRANSACTION, data, reply, FLAG_ONEWAY);
            reply.readException();
        } finally {
            data.recycle();
            reply.recycle();
        }
    }

    BinderProxy() {
        mSelf = new WeakReference(this);
    }
    
    @Override
    protected void finalize() throws Throwable {
        try {
            destroy();
        } finally {
            super.finalize();
        }
    }
    
    private native final void destroy();
    
    private static final void sendDeathNotice(DeathRecipient recipient) {
        if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
        try {
            recipient.binderDied();
        }
        catch (RuntimeException exc) {
            Log.w("BinderNative", "Uncaught exception from death notification",
                    exc);
        }
    }
    
    final private WeakReference mSelf;
    private int mObject;
    private int mOrgue;
}

        这里,我们只关心transact函数的实现:

static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
        jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
    ......

    IBinder* target = (IBinder*)
        env->GetIntField(obj, gBinderProxyOffsets.mObject);//和前面介绍过的gBinderOffsets相似,gBinderProxyOffsets的mObject成员指向BinderProxy实例的mObject成员
    if (target == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException", "Binder has been finalized!");
        return JNI_FALSE;
    }

    ......

    status_t err = target->transact(code, *data, reply, flags);//关键的调用

    ......
}

        从上面的代码可以看到,BinderProxy.mObject成员中保存了C++的IBinder对象的指针,然后通过这个IBinder对象调用transact函数,进行binder通信。transact函数的实现,上一个章节中有介绍,所以BinderProxy的分析也到此为止。
总结 通常,我们(应用开发者)通过aidl来进行binder通信,而aidl实现了 XXXX接口的声明XXXXStub的实现,是对于Binder类的继承,通过onTransact函数来呼叫业务逻辑代码XXXXStub.Proxy的实现,它拥有一个IBinder,通过这个IBinder来实现请求的转发Binder类的mObject成员指向原生层的JavaBBinderHolder实例,而JavaBinderHolder实例的get函数,可以生成JavaBBinder实例,JavaBBinder继承自BBinder,是Server端的Binder实体BinderProxy类的mObject成员指向原生层的IBinder实例,而IBinder通常是一个BpBinder实例,是Client端的Binder代理
        仅仅从以上的分析,可能大家无法把整个Java层和C++层Binder通信的过程理清楚,不过没关系,后面我们会分析Service和Binder通信的关系。从中,我们应该可以看到一个完整的Java层Binder通信过程。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

罗德与施瓦茨与SmartViser携手开发了一种用于测试符合欧盟销售的智能手机和平板电脑的新Energy Efficiency Index(EEI)标签法规的解决方案。该解决方案的核心是R&S CMX500,这是...

关键字: 智能手机 Android iOS

(全球TMT2023年8月24日讯)2023年8月23日,时值实时3D引擎Unity在华设立合资公司Unity中国一周年之际,Unity中国正式推出Unity中国版引擎——团结引擎。Unity全球CEO John Ri...

关键字: UNITY CE Android 开发者

报告显示:全球电商 App 获客花费接近50亿美元 北京2023年8月23日 /美通社/ -- 全球营销衡量与体验管理平台 AppsFlyer 近日发布《2023 电商 App 营销现状报告》。尽管面临全球经...

关键字: APPS BSP iOS Android

数字机顶盒是一种数字技术下的多媒体娱乐中心,可以实现电视节目接收、播放、存储、网络应用等多种功能。随着科技的发展,数字机顶盒的设计方案也在不断进步和优化。本文将介绍数字机顶盒设计的几种实现方案。

关键字: 数字机顶盒 Android Linux

21ic 近日获悉,原小米 9 号创始员工李明在社交媒体平台公布了旗下首款产品乐天派桌面机器人,为全球首款 Android 桌面机器人,面向极客和发烧友的 AI + 机器人。据悉,李明两个月前宣布创业并进军 AI 领域,...

关键字: 小米 Android 桌面机器人 AI

尽管安装增长放缓,全球游戏 App 获客花费仍高达 267 亿美元 经济低迷导致 2023 游戏 App 营销优先考虑收入指标,用户增长次之 北京2023年3月9日 /美通社/ -- 今天,全球营销衡量与体验管理平台...

关键字: APPS iOS Android BSP

量子计算领域的新里程碑,来了! 谷歌科学家证明,通过增加量子比特的数量,就能降低量子计算的错误率。

关键字: 谷歌 Android Windows

「卫星通讯」正在被普及到每一台智能手机当中。普及的动机并非是消费市场的一个刚需,其实更像是将差异化的功能「抹平」成一个标配。时下,支持「卫星通讯」功能的智能手机只有苹果的 iPhone 14 系列与华为的 Mate 50...

关键字: 卫星通讯 Android 智能手机 iPhone

Android是Google开发的操作系统,支持多种指令集架构 (ISA),包括Arm和x86,多数使用Android的设备都采用Arm架构芯片组。新兴RISC-V架构是免费开放指令集架构,任何人都可用它设计芯片,且无需...

关键字: 谷歌 Android RISC-V架构

智能手机并非每年都取得重大进展,这导致越来越多的人将手机保留两年、三年或四年。不过,普通的 Android 手机能否在遇到问题之前使用那么久?

关键字: Android 安卓 谷歌 智能手机
关闭
关闭